JavaScript中的微秒计时


99

JavaScript中是否有具有微秒级分辨率的计时功能?

我知道适用于Chrome 的timer.js,希望能够为其他友好的浏览器提供解决方案,例如Firefox,Safari,Opera,Epiphany,Konqueror等。我对支持任何IE均不感兴趣,但可以提供包括 IE 在内的答案受欢迎的。

(鉴于JS中毫秒级计时的准确性很差,因此我不屏住呼吸!)

更新:timer.js宣传微秒分辨率,但仅将毫秒读数乘以1,000。通过测试和代码检查验证。失望了 :[


2
您要在要求微秒精度的浏览器中做什么?通常,浏览器行为的性能保证并不那么精确。
Yuliy 2011年

4
不会发生。即使存在,您也不可相信微秒的精度。我可以想象的唯一可靠的用例是chrome中的本机客户端,但是您不必在意JS API。也喜欢将“主显节”视为一流的浏览器,而忽略了IE。
雷诺斯2011年

6
“获取” JavaScript中的时间和返回它一样需要一些时间-如果您正在重新绘制或处理事件的网页上,则延迟会增加。我什至不指望最近的10毫秒精度。
kennebec

1
比如说,以超高速抛出弹出窗口?基本上,问题在于仅凭一个人访问网站这一事实就可以使外部方对用户计算机的访问过多,这是一个严重的问题。
尖尖的

1
它比setInterval(popup,0)没有更多的“脆弱性”,它足够快以至于问题基本上是等效的。是否也应删除毫秒精度?肯尼贝克:您的评论很有意义,谢谢。
mwcz 2011年

Answers:


134

正如Mark Rejhon的答案所暗示的那样,现代浏览器中有一个API可以将亚毫秒级分辨率的计时数据公开给脚本:W3C High Resolution Timer,又名window.performance.now()

now()Date.getTime()在两个重要方面优于传统:

  1. now()是具有毫秒级分辨率的双精度型,表示自页面导航开始以来的毫秒数。它以分数形式返回微秒数(例如,值1000.123为1秒和123微秒)。

  2. now()在单调增加。因为这是重要的Date.getTime()可能是跳跃式前进或后退,甚至在随后的调用。值得注意的是,如果OS的系统时间已更新(例如原子时钟同步),Date.getTime()则也会更新。 now()保证总是单调增加,因此它不受操作系统的系统时间的影响-始终是挂钟时间(假设挂钟不是原子钟...)。

now()可几乎每一个地方,用在new Date.getTime()+ new DateDate.now()是。唯一的例外是,Datenow()时代不混合,如Date基于UNIX的时期(自1970年以来的毫秒数),而now()就是因为你的页面的导航启动(所以它会远小于毫秒数Date)。

now()在Chrome稳定版,Firefox 15+和IE10中受支持。也有几种填充料


1
polyfills很有可能会使用Date.now(),因此考虑到IE9以及数百万的用户,这仍然是最佳选择,为什么
还要

4
我的挂钟原子的。
程序员

4
new Date.getTime()不是什么 new Date().getTime()是。
,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

2
重温我对2012年的评论。现在,Meltdown / Spectre解决方法再次使performance.now()变得有些模糊。由于安全原因,某些浏览器已经严重降低了performance.now()。我认为我的技术可能已经重新获得了很多相关的基准测试用例的相关性,但受到计时器模糊限制。就是说,某些浏览器现在具有2012
。– Mark Rejhon

20

现在有了一种新的测量javascript中微秒的方法:http : //gent.ilcore.com/2012/06/better-timer-for-javascript.html

但是,在过去,我发现了一种粗略的方法,可以从毫秒计时器中获得JavaScript的0.1毫秒精度。不可能?不。继续阅读:

我进行了一些高精度的实验,需要对计时器的准确性进行自我检查,发现使用某些系统上的某些浏览器,我能够可靠地获得0.1毫秒的精度。

我发现在快速系统(例如,i7四核,其中几个核处于空闲状态,只有浏览器窗口)上的现代GPU加速的Web浏览器中-我现在可以相信计时器的精度是毫秒。实际上,在闲置的i7系统上,它变得如此精确,我已经能够可靠地获得完全相同的毫秒数,超过1000次尝试。仅当我尝试执行诸如加载额外网页或其他操作之类的操作时,毫秒级的精度才会降低(而且我可以通过前后的时间检查来成功地发现自己降低的精度,以查看是否我的处理时间突然延长到1或更多毫秒-这可以帮助我使可能受到CPU波动不利影响的结果无效)。

在i7四核系统上的某些GPU加速浏览器中(当浏览器窗口是唯一窗口时),它变得如此精确,以至于我希望我可以在JavaScript中访问一个0.1ms的精度计时器,因为精度现在终于可以了在某些高端浏览系统上,这些计时器精度对于某些类型的需要高精度的小众应用程序值得,并且这些应用程序能够针对精度偏差进行自我验证。

显然,如果您要进行多次扫描,则可以简单地运行多次扫描(例如10次扫描),然后除以10以得到0.1毫秒的精度。这是提高精度的常用方法-进行多次遍历,然后将总时间除以遍历次数。

但是...如果由于异常特殊的情况我只能对特定测试进行一次基准测试合格,我发现通过执行以下操作可以得到0.1(有时是0.01ms)的精度:

初始化/校准:

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

将一次传递基准化为亚毫秒精度:

  1. 运行繁忙的循环以等待计时器增加到下一个毫秒(将计时器与下一个毫秒间隔的开始对齐)。这个繁忙的循环持续不到一毫秒。
  2. 执行您要精确确定基准时间的任务。
  3. 检查计时器。这为您提供了整数毫秒。
  4. 运行最后一个繁忙循环以递增计数器,同时等待计时器递增。这个繁忙的循环持续不到一毫秒。
  5. 将此计数器值除以初始化后的原始计数器值。
  6. 现在您得到了毫秒的小数部分!

警告:不建议在Web浏览器中使用忙循环,但是幸运的是,这些忙循环每次运行的时间都少于1毫秒,并且只能运行几次。

诸如JIT编译和CPU波动之类的变量会增加大量的不准确性,但是如果您运行几次初始化遍历,则将具有完全的动态重新编译,最终计数器会稳定到非常准确的水平。确保所有情况下所有繁忙循环的功能都完全相同,以便繁忙循环中的差异不会导致差异。在开始信任结果之前,请确保多次执行所有代码行,以使JIT编译器已经稳定到完全动态重新编译(dynarec)的程度。

实际上,我在某些系统上目睹了接近微秒的精度,但是我还不相信它。但是,在闲置的四核系统(我是唯一的浏览器页面)上,0.1毫秒的精度似乎相当可靠。我来到一个科学测试用例中,我只能进行一次通过(由于出现了唯一变量),并且需要精确地确定每次通过的时间,而不是平均多次重复通过,所以这就是我这样做的原因。

我进行了几次预传和伪传(也用于解决dynarec的问题),以验证0.1ms精度的可靠性(保持稳定状态几秒钟),然后将手放在键盘/鼠标上,而没有出现基准测试,然后进行了几次通过后验证0.1ms精度的可靠性(再次保持稳定)。这也可以验证在前后之间没有发生诸如电源状态更改或其他内容之类的事情,从而干扰结果。在每个基准测试通过之间重复前测和后测。基于此,我几乎可以肯定两者之间的结果是准确的。当然,我们不能保证,但是这表明在某些情况下,Web浏览器可以达到<0.1ms的精确度。

此方法仅在非常非常特殊的情况下有用。即使如此,从字面上看,它也不是100%无限保证的,与多层内部和外部验证结合使用时,您可以获得非常值得信赖的准确性,甚至科学准确性。


3
过去,以极高的精度进行计时非常复杂,因为我们只有Date.now()+new Date()。但是现在我们有了performance.now()。很显然,您已经找到了一些破解更多功能的好方法,但是这个答案实际上已经过时了。另外,请勿推荐与忙碌循环相关的任何内容。只是不要那样做。我们不需要更多。
史蒂文·卢

1
大多数浏览器降低了performance.now()实现的精度,以暂时缓解缓存计时攻击。我想知道这个答案在安全研究中是否仍然有意义。
齐凡

2
重温我自己的评论。哇,我在2012年在performance.now()之前发布了以上内容。但是现在,Meltdown / Spectre的解决方法又使它有点模糊了。由于安全原因,某些浏览器已经严重降低了performance.now()。我认为上述技术可能已经重新获得了许多相关的合法基准测试用例的相关性,但受到计时器模糊限制。
Mark Rejhon

3

答案通常是“否”。如果您在某些服务器端环境(即不在浏览器中)中使用JavaScript,那么所有赌注都已关闭,您可以尝试做任何您想做的事情。

编辑 -这个答案很旧;标准已经发展,并且可以使用更新的设备来解决精确时间问题。即使这样,应该记住,在真正的实时操作系统的范围之外,普通的非特权代码对其访问计算资源的控制有限。衡量性能与预测性能不同(有必要)。


2

这是一个示例,显示了我的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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.