为何使用performance.now()测试的JS函数执行时间不随数组长度线性增长?
异常耗时的核心原因
1. V8 引擎 JIT 编译的预热效应
V8 引擎启动阶段不会直接把 JS 代码编译成高性能机器码,先用解释器执行保证启动速度。只有当同一个函数被反复调用、达到引擎预设的优化阈值后,才会将其编译为优化后的机器码。
- 测试前两次调用(对应 10 万、20 万长度数组)时,
getMaxSequence还处于未优化的解释执行状态,因此耗时偏高 - 到第三次调用时,引擎刚好完成对该函数的 JIT 优化,后续执行速度直接大幅下降,直接打破了线性增长的预期
2. 数组与循环的优化滞后
V8 对 JS 数组有分级内存优化策略,存储连续小整数的 PACKED_SMI_ELEMENTS 类型数组访问速度最快。
- 前两次遍历数组时,引擎还未识别出数组的连续整数特征,默认使用通用、低效率的访问模式
- 连续遍历几次后,引擎确认了数组的元素类型和顺序访问规律,会切换到紧凑存储 + CPU 缓存预取的优化策略;同时识别出循环内的条件判断永远走“递增”分支,提前做分支预测,进一步减少判断开销,拉低了后续执行耗时
3. 测试方法缺陷放大了结果波动
- 未执行预热用例就直接开始正式测试,把冷启动阶段的未优化耗时直接计入结果
- 每个数组长度的用例仅执行 1 次,没有重复测试取平均值。JS 单线程执行会被系统进程调度、GC 停顿、CPU 睿频波动等因素干扰,单次测试的误差本身就很大
- 提前将所有测试数组存在
randoms常量中,长度更大的数组在遍历前已经被预读到 CPU 缓存中,进一步降低了大数组的实际测试耗时
正确测试的调整方案
- 先运行 3-5 次任意长度的测试用例做预热,等引擎完成 JIT 优化后再开始正式统计
- 每个长度的用例重复执行 3-10 次,去掉最高、最低误差值后取平均耗时
- Node 环境下可加
--expose-gc参数启动,每次测试完成后主动调用global.gc()避免垃圾回收干扰 - 按需生成对应长度的测试数组,不要提前缓存所有测试用例,避免缓存预取影响结果准确性
内容的提问来源于stack exchange,提问作者Jarek Mazur
相关产品推荐
相关产品推荐

