JavaScript 中的微秒计时

IT技术 javascript
2021-01-20 14:31:41

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

我知道Chrometimer.js,并希望有其他友好浏览器的解决方案,如 Firefox、Safari、Opera、Epiphany、Konqueror 等。我对支持任何 IE 不感兴趣,但包括IE在内的答案受欢迎的。

(鉴于 JS 中毫秒计时的准确性很差,我对这个没有屏住呼吸!)

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

4个回答

正如 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 DateDate.now()是。例外是Datenow()时间不混合,因为Date基于unix-epoch(自 1970 年以来的毫秒数),而now()是自页面导航开始以来的毫秒数(因此它会比 小得多Date)。

now()支持 Chrome stable、Firefox 15+ 和 IE10。还有几种可用的polyfill

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

我真的很喜欢这个回应。我进行了一些测试并提出了一个示例,您可以将其放入控制台以查看在使用它时仍然会发生剧烈的冲突。(注意我在一台好机器上得到 10% 的碰撞,即使console.log在每次运行时做一些像 a 一样昂贵的事情)很难辨认,但在这里复制所有突出显示的代码: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');
2021-03-14 14:31:41
new Date.getTime()不是一回事 new Date().getTime()是。
2021-03-18 14:31:41
重温我2012 年的评论。performance.now() 现在通过 Meltdown/Spectre 变通方法再次有点模糊。由于安全原因,一些浏览器严重降低了 performance.now()。我认为我的技术可能已经在大量合法的基准测试用例中重新获得了一些相关性,但受到计时器模糊的限制。也就是说,某些浏览器现在具有一些 2012 年不存在的开发人员性能分析功能/扩展。
2021-03-21 14:31:41
polyfills 很可能会使用 Date.now(),所以考虑到 IE9 和它的数百万用户,这仍然是最好的选择,那么为什么混合第三方库
2021-03-22 14:31:41
我的挂钟原子的。
2021-04-11 14:31:41

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

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

我正在做一些需要自检计时器精度的高精度实验,并发现我能够在某些系统上的某些浏览器上可靠地获得 0.1 毫秒的精度。

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

它在 i7 四核系统上的某些 GPU 加速浏览器中变得如此准确(当浏览器窗口是唯一的窗口时),我发现我希望我可以在 JavaScript 中访问一个 0.1 毫秒的精确计时器,因为现在终于准确了在一些高端浏览系统上,对于某些类型的需要高精度的利基应用程序,以及应用程序能够自我验证精度偏差的情况,这种计时器精度是值得的。

显然,如果您要进行多次传递,您可以简单地运行多次传递(例如 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.1 毫秒精度的可靠性(再次保持稳定)。这也验证了诸如电源状态更改或其他事情之类的事情在前后之间没有发生,从而干扰了结果。在每一次基准测试之间重复前测和后测。在此基础上,我几乎可以肯定两者之间的结果是准确的。当然,不能保证,但它表明在某些情况下在 Web 浏览器中可以达到 <0.1ms 的精确精度

这种方法只在非常非常小众的情况下有用即便如此,它实际上也不会 100% 无限地保证,当与多层内部和外部验证相结合时,您可以获得非常值得信赖的准确性,甚至是科学的准确性。

大多数浏览器降低了 performance.now() 实现的精度以暂时缓解缓存计时攻击。我想知道这个答案在安全研究中是否还有意义。
2021-03-30 14:31:41
过去要以极高的精度进行计时很复杂,因为我们所拥有的只是Date.now()+new Date()但现在我们有performance.now(). 虽然很明显您已经找到了一些很酷的方法来破解更多功能,但这个答案基本上已经过时了。另外,不要推荐任何与繁忙循环相关的东西。不要那样做。我们不需要更多。
2021-04-01 14:31:41
重新审视我自己的评论。哇,我在2012 年早在 performance.now() 之前就发布了上述内容但是现在,Meltdown/Spectre 的解决方法又使这有点模糊了。由于安全原因,一些浏览器严重降低了 performance.now()。我认为上述技术可能已经在大量合法的基准测试用例中重新获得了一些相关性,但受到计时器模糊的限制。
2021-04-06 14:31:41

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

一般来说,答案是“不”。如果您在某些服务器端环境中(即,不在浏览器中)使用 JavaScript,那么一切都将结束,您可以尝试做任何您想做的事情。

编辑- 这个答案是旧的;标准不断进步,更新的设施可以作为解决准确时间问题的方法。尽管如此,应该记住,在真正的实时操作系统的域之外,普通的非特权代码对其对计算资源的访问的控制是有限的。衡量绩效与预测绩效不同(必然)