【问题标题】:Microsecond timing in JavaScriptJavaScript 中的微秒计时
【发布时间】:2011-06-04 00:44:39
【问题描述】:

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

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

(鉴于 JS 中毫秒计时的准确性较差,我不会屏住呼吸!)

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

【问题讨论】:

  • 你想在需要微秒精度的浏览器中做什么?一般来说,浏览器行为的性能保证并不那么精确。
  • 不会发生。即使存在微秒精度,您也不能完全相信它。我能想象的唯一可靠的用例是 chrome 中的本机客户端,但你并不关心 JS API。还喜欢把“顿悟”当成一流的浏览器,无视IE。
  • 在 javascript 中“获取”时间需要一些时间,返回时间也是如此 - 如果您在重绘或处理事件的网页上,延迟会增加。我什至不会指望最近的 10 毫秒精度。
  • 比如说,以超高速弹出弹出窗口?基本上,问题在于,仅凭一个人访问网站这一事实,就让外部方过多地访问用户机器是一个严重的问题。
  • 它并不比 setInterval( popup, 0 ) 更“脆弱”,它足够快,问题基本上是等价的。是否也应该删除毫秒精度? kennebec:你的评论很有道理,谢谢。

标签: javascript


【解决方案1】:

正如 Mark Rejhon 的回答中提到的,现代浏览器中有一个可用的 API 将亚毫秒分辨率的计时数据暴露给脚本:W3C High Resolution Timer,又名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 小很多)。

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

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

【讨论】:

  • polyfill 很可能会使用 Date.now(),所以考虑到 IE9 和数百万用户,这仍然是最佳选择,为什么要混合第三方库
  • 我的挂钟原子的。
  • new Date.getTime() 不是一个东西。 new Date().getTime() 是。
  • 我真的很喜欢这个回复。我进行了一些测试并提出了一个示例,您可以将其放入控制台中,以查看在使用它时仍然会产生剧烈的碰撞。 (请注意,即使每次运行像 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');
  • 重温我的 2012 年 评论。 performance.now() 现在又被 Meltdown/Spectre 变通方法稍微弄糊涂了。由于安全原因,某些浏览器的 performance.now() 严重下降。我认为我的技术可能再次与许多合法的基准测试用例重新获得了一些相关性,但受到计时器模糊限制。也就是说,一些浏览器现在具有一些 2012 年不存在的开发人员性能分析功能/扩展。
【解决方案2】:

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

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

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

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

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

显然,如果您要进行多次传递,您可以简单地运行多次传递(例如 10 次传递),然后除以 10 以获得 0.1 毫秒的精度。这是获得更好精度的常用方法 - 进行多次传递,然后将总时间除以传递次数。

但是...如果由于异常独特的情况而只能通过特定测试的单个基准测试,我发现这样做可以获得 0.1(有时是 0.01 毫秒)的精度:

初始化/校准:

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

以亚毫秒精度为基准:

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

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

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

事实上,我在某些系统上目睹了接近微秒的精度,但我还不相信它。但是 0.1 毫秒的精度在我是唯一浏览器页面的空闲四核系统上似乎工作得相当可靠。我遇到了一个科学测试案例,我只能一次性通过(由于发生了独特的变量),并且需要精确计时每次通过,而不是平均多次重复通过,所以这就是我这样做的原因。

我做了几次 pre-pass 和 dummy pass(也是为了稳定 dynarec),以验证 0.1ms 精度的可靠性(保持稳定几秒钟),然后在基准测试发生时让我的手离开键盘/鼠标,然后进行了几次后期处理以验证 0.1ms 精度的可靠性(再次保持稳定)。这也验证了诸如电源状态更改或其他东西之类的事情在前后之间没有发生,从而干扰了结果。在每个基准测试通过之间重复前测和后测。在此基础上,我几乎可以肯定两者之间的结果是准确的。当然,这不能保证,但它表明在网络浏览器中某些情况下准确的

这种方法只在非常非常利基的情况下有用。即便如此,它实际上也不是 100% 无限保证的,你可以获得非常值得信赖的准确度,甚至是结合多层内部和外部验证的科学准确度。

【讨论】:

  • 过去,以极高的精度进行计时很复杂,因为我们只有Date.now()+new Date()。但现在我们有performance.now()。虽然很明显您已经找到了一些很酷的方法来破解更多功能,但这个答案基本上已经过时了。此外,不要推荐任何与繁忙循环相关的内容。只是不要那样做。我们不需要更多。
  • 大多数浏览器降低了他们的 performance.now() 实现的精度,以暂时缓解缓存定时攻击。我想知道这个答案在安全研究中是否仍然具有重要意义。
  • 重温我自己的评论。哇,我早在 performance.now() 之前就在 2012 年 发布了上述内容。但是现在这又被 Meltdown/Spectre 的变通方法稍微弄糊涂了。由于安全原因,某些浏览器的 performance.now() 严重下降。我认为上述技术可能再次为大量合法的基准测试用例重新获得了一些相关性,但受到计时器模糊限制。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    edit——这个答案很旧;标准已经取得进步,并且可以使用更新的设施作为准确时间问题的解决方案。即便如此,应该记住,在真正的实时操作系统领域之外,普通的非特权代码对其对计算资源的访问的控制是有限的。衡量绩效与(必然)不同预测绩效。

    再次编辑 — 有一段时间我们有 performance.now(),但目前(现在是 2022 年)浏览器出于安全原因降低了该 API 的准确性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-04-05
      • 2018-10-11
      • 2012-04-19
      • 1970-01-01
      • 1970-01-01
      • 2012-03-12
      • 2012-02-12
      相关资源
      最近更新 更多