如何通过Performance.now()获取微秒级精度并开展关键算法的性能基准测试

作者:袖梨 2026-06-07
performance.now() 返回毫秒级浮点数,小数后三位对应微秒;单次差值易受JIT、GC、节流等干扰,应预热+批量采集+剔除极值后取P50/P90。

performance.now() 不能直接“获取微秒级时间戳”,它返回的是毫秒为单位的浮点数,小数点后三位才对应微秒(如 123.456 表示 123 毫秒 + 456 微秒);所谓“微秒级精度”是测量能力,不是单位。拿它当纳秒或整数微秒用,数据就废了。

为什么直接用 performance.now() 做单次差值测算法不准

你写 const start = performance.now(); fn(); const end = performance.now();,看似简单,实则结果常被干扰:

  • JS 引擎 JIT 预热未完成,头几次执行明显偏慢
  • 垃圾回收(GC)在某次迭代中突然触发,耗时跳变几十毫秒
  • CPU 频率动态升降、后台标签页节流,导致 performance.now() 调用本身被延迟(Chrome 后台每秒最多调 100 次)
  • 单次差值保留太多噪声,Math.round().toFixed(0) 会抹掉关键亚毫秒抖动,掩盖真实瓶颈

正确做关键算法基准测试的三步实操

不是“多跑几次取平均”,而是控制变量 + 统计清洗 + 环境对齐:

  • 强制预热:先调用目标函数至少 10 次,不计入统计,让 V8 完成优化编译
  • 批量采集:运行 100–500 次,每次记录原始 end - start(保留全部小数位),存入数组
  • 剔除极值:排序后丢弃首尾各 10%(比如 500 次就扔掉前 50 和后 50),用剩余值算 P50(中位数)或 P90,比平均值抗干扰强得多

示例片段:

function benchmark(fn, iterations = 200) {  // 预热  for (let i = 0; i < 10; i++) fn();  const times = [];  for (let i = 0; i < iterations; i++) {    const start = performance.now();    fn();    const end = performance.now();    times.push(end - start);  }  times.sort((a, b) => a - b);  const trim = Math.floor(times.length * 0.1);  const trimmed = times.slice(trim, -trim);  return trimmed.reduce((a, b) => a + b, 0) / trimmed.length;}

performance.now() 在 Web Worker 中能用,但别和主线程混着比

Worker 的 performance.timeOrigin 和主线程不同——它从 Worker 创建时刻开始计时,不是页面加载时刻。如果你在主线程打一个点、Worker 里打一个点,再直接相减,差值毫无意义。

  • 跨上下文对比必须统一基准:用 performance.timeOrigin 把两边时间都换算成相对于同一 epoch(比如用主线程的 timeOrigin 加上 Worker 内的 performance.now()
  • 更稳妥的做法:只在同一线程内做差值,Worker 仅用于隔离 CPU 密集任务,避免阻塞主线程影响测量稳定性
  • Service Worker 初始化阶段(fetch 事件前)performance 可能未就绪,务必先检查 typeof performance !== 'undefined'

别依赖 console.time() 做生产级基准测试

console.time() 看起来方便,但它有硬伤:

  • 精度实际依赖浏览器实现,某些旧版 WebView 里退化到 Date.now() 级别(±15ms)
  • 输出日志本身有开销,高频调用(比如每帧都记)会拖慢性能,干扰被测逻辑
  • 无法导出原始数据做统计分析,只能看控制台一行字符串,没法算 P90、标准差等关键指标

真要可视化对齐 DevTools 时间轴,改用 performance.mark() + performance.measure(),它们写入 Performance Timeline,可直接在 Chrome 的 Performance 面板里看到标记位置与渲染帧、网络请求的关系。

真正卡住性能的,往往不是单次函数耗时,而是多次调用的累积抖动、GC 触发时机、或跨线程同步开销——这些只有保留原始浮点精度、做分位数统计、并严格隔离测量环境才能暴露出来。

相关文章

精彩推荐