JavaScript中的微秒级计时

117

JavaScript中是否有微秒级分辨率的计时函数?

我知道在Chrome浏览器中有一个名为timer.js的解决方案,但我希望其他浏览器也有类似的解决方案,如Firefox、Safari、Opera、Epiphany和Konqueror等。我对支持IE不感兴趣,但包括IE在内的答案也可以考虑。

(鉴于JS中毫秒级计时的精度较低,我对这个问题并不抱有太大期望!)

更新:timer.js声称具有微秒级分辨率,但实际上只是将毫秒读数乘以1,000。通过测试和代码检查已验证。令人失望。:[


2
你在浏览器中尝试做什么需要微秒级的准确性?一般来说,浏览器的性能保证并不是那么精确。 - Yuliy
5
不可能发生。即使微秒级的精度存在,你也无法完全信任它。我能想象到的唯一实际的用例是在Chrome中的本机客户端,但那时你并不关心JS API。同时,我们喜欢将“Epiphany”视为一流的浏览器,而忽略IE。 - Raynos
6
在JavaScript中获取时间需要一定的时间,并且返回它也需要一定的时间-如果您正在处理重绘或事件处理的网页,延迟会增加。我甚至不指望最近的10毫秒能够准确无误。 - kennebec
1
比如说,以超高速度弹出弹窗?基本上,问题在于仅仅因为一个人访问网站,就给外部方太多地访问用户计算机的权限是一个严重的问题。 - Pointy
1
它并不比 setInterval(popup, 0) 更“脆弱”,因为后者足够快,问题基本上是等价的。毫秒级精度也应该被移除吗?kennebec:你的评论很有道理,谢谢。 - mwcz
显示剩余10条评论
4个回答

156
作为Mark Rejhon答案中所提到的,现代浏览器中有一个API可以向脚本公开亚毫秒级别的时间数据:W3C高分辨率计时器,又称为window.performance.now()now()比传统的Date.getTime()更好,有两个重要的方面:
  1. now()是一个双精度数值,具有亚毫秒分辨率,表示自页面导航开始以来的毫秒数。它返回小数部分中的微秒数(例如,1000.123的值为1秒和123微秒)。

  2. now()单调递增。这很重要,因为Date.getTime()可能会在后续调用中向前或向后跳跃。值得注意的是,如果操作系统的系统时间被更新(例如原子钟同步),Date.getTime()也会被更新。now()保证始终单调递增,因此不受操作系统的系统时间影响——它将始终是挂钟时间(假设您的挂钟不是原子钟...)。

now() 可以在几乎所有使用 new Date.getTime()+ new Date 以及 Date.now() 的地方使用。唯一的例外是,Datenow() 的时间不能混用,因为 Date 基于 unix-epoch(自1970年以来的毫秒数),而 now() 是自您页面导航开始以来的毫秒数(因此比 Date 小得多)。

now() 支持 Chrome 稳定版、Firefox 15+ 和 IE10。还有几个 polyfills 可用。

注意:在使用 Web Workers 时,window 变量不可用,但仍然可以使用 performance.now()


1
使用 polyfills 的可能性最大是使用 Date.now(),因此考虑到 IE9 及其数百万用户,这仍然是最佳选择,为什么要混合第三方库呢? - Vitaliy Terziev
4
我的挂钟是原子钟。 - user6560716
4
new Date.getTime()不是正确的语法。应该使用new Date().getTime() - The Qodesmith
我真的很喜欢这个回答。我运行了几个测试,并且想出了一个示例,您可以将其放入控制台中以查看在使用此功能时仍会发生显著的冲突。(请注意,即使在每次运行时执行像console.log这样昂贵的操作,我在一台好机器上也得到了10%的冲突)很难辨认,但请复制此处所有突出显示的代码:last=-11; same=0; runs=100; for(let i=0;i<runs;i++) { let now = performance.now(); console.log('.'); if (now === last) { same++; } last = now; } console.log(same, 'were the same'); - bladnman
6
回顾我在2012年的评论。由于Meltdown/Spectre的解决方案,performance.now()现在又有点模糊了。由于安全原因,一些浏览器严重降低了performance.now()的性能。我认为我的技巧对于许多合法的基准测试用例可能再次具有一定的相关性,但要考虑计时器模糊限制。尽管如此,一些浏览器现在拥有一些在2012年不存在的开发者性能分析功能/扩展程序。 - Mark Rejhon

21
现在有一种新的方法可以在JavaScript中测量微秒:http://gent.ilcore.com/2012/06/better-timer-for-javascript.html。过去,我发现了一种简单的方法,可以从毫秒计时器中获得0.1毫秒精度的JavaScript。不可能吗?并非如此。请继续阅读:
我正在进行一些需要自我检查计时器准确性的高精度实验,并发现我能够可靠地获得某些浏览器在某些系统上的0.1毫秒精度。
我发现,在现代GPU加速的Web浏览器以及快速系统(例如i7四核,其中有几个核心处于空闲状态,只有浏览器窗口)上,我现在可以信任计时器的毫秒精度。事实上,在空闲的i7系统上,它变得如此精确,我已经能够可靠地获得超过1000次相同的毫秒。仅当我尝试加载额外的Web页面或其他内容时,毫秒精度才会降低,而且我能够通过进行前后时间检查来成功捕捉自己的精度下降,以查看我的处理时间是否突然延长到1毫秒或更多(这有助于我无效的结果可能已经受到CPU波动的不利影响)。
在某些GPU加速浏览器上的i7四核系统中(当浏览器窗口是唯一窗口时),它变得如此准确,以至于我发现我希望我可以在JavaScript中访问0.1ms精度计时器,因为高端浏览系统上的准确性最终使得这种计时器精度对于某些需要高精度的利基应用程序非常有价值,并且应用程序能够自行验证准确性偏差。
显然,如果要进行多次测试,则可以简单地运行多个测试(例如10个测试),然后除以10,以获得0.1毫秒精度。这是获得更好精度的常见方法-多次测试并将总时间除以测试次数。

然而...如果由于异常情况只能对特定测试进行一次基准测试,我发现通过以下方法可以获得0.1(有时为0.01毫秒)的精度:

初始化/校准:

  1. 运行一个忙循环等待计时器增加到下一个毫秒(将计时器与下一个毫秒间隔对齐)。这个忙循环持续时间少于一毫秒。
  2. 运行另一个忙循环,在等待计时器递增的同时递增一个计数器。计数器告诉您一毫秒内发生了多少次计数器递增。这个忙循环持续时间为一整毫秒。
  3. 重复上述步骤,直到数字变得非常稳定(加载时间、JIT编译器等因素)。注意:数字的稳定性给出了在空闲系统上可达到的精度。如果需要自行检查精度,可以计算方差。不同浏览器的方差大小不同,更快的系统方差更大,而较慢的系统方差更小。一致性也有所不同。您可以知道哪些浏览器比其他浏览器更一致/准确。较慢的系统和繁忙的系统将导致初始化过程中方差更大。如果浏览器不能提供足够的精度以允许0.1毫秒或0.01毫秒的测量,这可以为您提供显示警告消息的机会。计时器偏差可能是一个问题,但是一些系统上的整数毫秒计时器递增非常准确(恰好在点上),这将产生非常一致的校准值,可以信任。
  4. 保存最终计数器值(或最后几次校准的平均值)

对一次基准测试进行亚毫秒精度:

  1. 运行一个忙循环等待计时器增加到下一个毫秒(将计时器与下一个毫秒间隔对齐)。这个忙循环持续时间少于一毫秒。
  2. 执行要精确基准测试时间的任务。
  3. 检查计时器。这给出整数毫秒。
  4. 运行最后一个忙循环,在等待计时器递增的同时递增一个计数器。这个忙循环持续时间少于一毫秒。
  • 将此计数器的值除以初始化时的原始计数器值。
  • 现在你得到了毫秒数的小数部分!
  • 警告:不建议在 Web 浏览器中使用繁忙循环,但幸运的是,这些繁忙循环每次只运行不到 1 毫秒,并且只运行很少的次数。

    像 JIT 编译和 CPU 波动等变量会带来巨大的不准确性,但如果你运行多个初始化过程,你将获得完全的动态重新编译,并且最终计数器会稳定在非常准确的值。确保所有繁忙循环对于所有情况都是完全相同的功能,以便繁忙循环的差异不会导致结果差异。确保在开始信任结果之前,执行所有代码行多次,以允许 JIT 编译器已经稳定为全动态重新编译(dynarec)。

    实际上,我亲眼见证了某些系统精度接近微秒,但我还不太相信它。但是,在一个空闲的四核系统上,只有我一个浏览器页面时,0.1 毫秒的精度似乎非常可靠。我找到了一个科学测试案例,我只能做一次通过(由于出现独特的变量),并且需要精确计时每个通过,而不是平均多次重复通过,这就是我这样做的原因。

    我进行了几次预处理和虚拟处理(也为了解决 dynarec),以验证可靠的 0.1 毫秒精度(保持数秒钟稳定),然后将手离开键盘/鼠标,当基准测试发生时,然后进行了几次后处理以验证可靠的 0.1 毫秒精度(再次保持稳定)。这还验证了类似电源状态更改或其他事物是否在前后之间发生,干扰结果。在每个基准测试通过之间重复进行预测试和后测试。

    总之,我相当确定中间的结果是准确的。当然没有保证,但这表明,在某些情况下,Web 浏览器中的准确小于 0.1 毫秒的精度是可能的。

    这种方法只在非常、非常特定的情况下有用。即使如此,它也无法百分之百地无限保证,但当与多层内部和外部验证相结合时,可以获得相当可靠的准确性,甚至是科学准确性。


    3
    以前要获得更高精度的计时是很复杂的,因为我们只有Date.now()或者+new Date()。但现在我们有了performance.now()。虽然你已经找到一些很酷的方法来突破更多的能力,但这个回答基本上已经过时了。此外,请不要推荐任何与忙等待相关的内容。就是不要这样做。我们不需要更多的这种东西。 - Steven Lu
    4
    大多数浏览器减少了performance.now()实现的精度,以暂时缓解缓存定时攻击。我想知道这个答案在安全研究中是否仍具有意义。 - Qi Fan
    2
    重新审视我的评论。哇,我在 2012年 发布了上述内容,早在 performance.now() 函数之前。但现在,由于 Meltdown/Spectre 的解决方案,这种情况又有些模糊了。一些浏览器由于安全原因严重降低了 performance.now() 的性能。我认为上述技术可能对许多合法的基准测试用例再次具有重要意义,但需要考虑计时器模糊限制。 - Mark Rejhon

    4

    下面是一个示例,展示了我用于 node.js 的高分辨率计时器:

     function startTimer() {
       const time = process.hrtime();
       return time;
     }
    
     function endTimer(time) {
       function roundTo(decimalPlaces, numberToRound) {
         return +(Math.round(numberToRound + `e+${decimalPlaces}`)  + `e-${decimalPlaces}`);
       }
       const diff = process.hrtime(time);
       const NS_PER_SEC = 1e9;
       const result = (diff[0] * NS_PER_SEC + diff[1]); // Result in Nanoseconds
       const elapsed = result * 0.0000010;
       return roundTo(6, elapsed); // Result in milliseconds
     }
    

    使用方法:

     const start = startTimer();
    
     console.log('test');
    
     console.log(`Time since start: ${endTimer(start)} ms`);
    

    通常情况下,您可以使用以下方法:

     console.time('Time since start');
    
     console.log('test');
    
     console.timeEnd('Time since start');
    

    如果您正在计时涉及循环的代码段,您无法访问console.timeEnd()的值以便将定时器结果相加。虽然可以这样做,但它会变得混乱,因为您必须注入您的迭代变量的值,如i,并设置一个条件来检测循环是否完成。
    以下是一个示例,因为它可能会有用:
     const num = 10;
    
     console.time(`Time til ${num}`);
    
     for (let i = 0; i < num; i++) {
       console.log('test');
       if ((i+1) === num) { console.timeEnd(`Time til ${num}`); }
       console.log('...additional steps');
     }
    

    引用:https://nodejs.org/api/process.html#process_process_hrtime_time

    该方法返回一个包含当前高精度实际时间的数组,其中第一项是秒数,第二项是纳秒数。这个方法主要用于衡量程序运行时间和性能优化。

    3
    一般而言,答案是否定的。如果你是在一些服务器端环境(即不在浏览器中)使用 JavaScript,那么就可以尝试想做什么就做什么。
    编辑 - 这个答案已经过时,标准已经进步,新的解决方案可用于解决精确时间的问题。即使如此,应记住,在真正实时操作系统的领域之外,普通非特权代码对其访问计算资源的控制受到限制。测量性能并不一定等同于预测性能。
    再次编辑- 有一段时间我们有了 performance.now(),但目前(现在是2022年),浏览器为安全原因降低了该API的精度。

    因为精度问题导致了安全性出现了一些问题? - Dee
    1
    @Dee 是的,有些基于时间的攻击只有在非常准确的时间可用时才能起作用。我不是受过训练的黑客,所以无法提供详细信息,但您可以通过搜索“时间攻击”来获取相关信息。 - Pointy

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接