JavaScript中是否存在与设备无关的可靠代码性能测量方法?
结论
不存在100%不受任何环境因素影响、且和实际执行速度完全对齐的性能测量方式,但你需要的「相同代码运算量固定、跨设备一致、可直接对比两段代码理论执行效率」的统计方案是可以实现的,主要分为两类:
你给出的性能测试代码(格式化后)
var res = 0; function runs(){ var a1 = performance.now(); var x = 0; for(var i=0;i<10**9;i++) { x++; } var a2 = performance.now(); res += (a2-a1); } for(var j=0;j<10;j++){ runs(); } console.log(`=${res/10}`);
你遇到的时间测量波动问题,除了CPU、内存、系统负载的影响外,还有很大概率是JS引擎的JIT优化导致的:上述代码里的x变量仅在函数内部使用、没有外部依赖,引擎会直接判定为死代码,优化后整个for循环都会被跳过,所以会出现首次运行未优化耗时久、后续优化后耗时骤降的情况。
可用的无硬件依赖统计方案
1. 静态操作计数(完全脱离运行环境)
你可以自定义统一的基础操作权重规则(也就是你提到的FPO单位),直接静态统计代码的总运算量,规则只要全局统一即可,参考规则示例:
- 变量初始化/赋值:1 FPO
- 基础数值运算(加减乘除、自增自减):1 FPO
- 逻辑判断(循环条件判断、if分支判断):1 FPO
- 函数调用/返回:各10 FPO
- 对象属性读写:3 FPO
举个例子,上述代码的for循环单次迭代的操作包括:i < 10**9判断、i++、x++,也就是单次循环消耗3 FPO,迭代1e9次总消耗就是3e9 FPO,再叠加函数调用、变量初始化的固定开销,总数值完全固定,不管在任何设备上计算结果都一致,完全满足你要求的「迭代1005次的for循环统计结果一定比1000次更高」的需求。
这种方案的优势是完全不需要运行代码,统计结果100%固定,缺点是无法覆盖JS引擎的优化行为,统计的是理论最大运算量,和实际执行的运算量可能有差异。
2. 运行时字节码计数(贴合实际执行逻辑)
如果需要统计结果贴合代码实际执行的运算量,可以统计JS引擎执行代码时生成的字节码数量,规则如下:
- 固定使用同一个JS引擎(比如Chrome/V8、Firefox/SpiderMonkey)的同一个大版本
- 开启引擎的字节码打印参数(比如V8用
--print-bytecode参数运行代码),统计实际执行的字节码总条数,每条字节码可以统一按1单位计数,也可以按字节码类型设置不同权重
同一段代码在同一个引擎版本下,生成的字节码、实际执行的字节码数量是完全固定的,和CPU、内存、系统负载没有任何关系,且可以反映引擎优化的结果:如果上述代码的for循环被引擎优化删除,统计出来的字节码数量会非常少,比静态计数更贴合实际执行情况。
这种方案的缺点是和引擎版本绑定,不同引擎的统计结果不通用,但只要统一统计规则,对比结果完全可靠。
补充说明
如果你的需求只是对比两段代码的执行效率,不需要固定数值,也可以用同一设备、同一环境下的基准测试:关闭其他后台程序,关闭引擎的动态优化开关,多次运行取平均值,对比的结果也足够可靠,不需要完全脱离硬件环境。
内容的提问来源于stack exchange,提问作者IngoP

