【问题标题】:Does JavaScript provide a high resolution timer?JavaScript 是否提供高分辨率计时器?
【发布时间】:2011-07-29 15:43:11
【问题描述】:

JavaScript 是否提供高分辨率计时器?

我已经从零开始编写了一些游戏引擎,一些使用 C 语言,一些使用 Java,还有一些使用 Flash。在动画和交互式图形方面,我总是遵循相同的基本模型。使用以下设计创建一个基本类/结构:

void init() { /* Called once, preload essential resources here. */ }
void update(double time) { /* Updates game/animation state using high resolution time. */ }
void render(double time) { /* Updates screen graphics using high resolution time. */ }

void run()
{
    double time;
    init();
    while (!done)
    {
        time = queryTime();
        update(time);
        render(time);
    }
}

时间对于平滑动画和游戏状态计算非常重要。在本机代码 Windows 中,我使用QueryPerformanceCounter()QueryPerformanceFrequency() 在每个游戏循环中执行queryTime() 的角色,并通过时间来更新/渲染。在 Java 中,我使用 System.nanoTime()

JavaScript 中的等价物是什么?也就是说,像queryTime() 这样的一些函数会返回一个高精度(亚毫秒)的时间值。据我所知,在 JavaScript 中可以获得的最佳准确度约为 15 毫秒……这对于动画来说太可怕了。

【问题讨论】:

标签: javascript animation


【解决方案1】:

几乎所有现代浏览器都提供高分辨率计时器。这是“高分辨率时间”W3C 标准:http://www.w3.org/TR/hr-time/#sec-DOMHighResTimeStamp

它允许您通过调用 window.performance.now() 获得亚毫秒级的精确时间戳。这会返回一个以毫秒为单位的时间戳,但它是一个浮点数,因此您仍然可以获得亚毫秒级的分辨率。

非常旧的浏览器可能会实现此标准的“前缀”版本,例如用于实现 window.performance.webkitNow()

的基于 WebKit 的浏览器

这里有一些代码,您可以使用它来获取准确的时间戳(如果可用)并回退到标准精度:

if (window.performance.now) {
    console.log("Using high performance timer");
    getTimestamp = function() { return window.performance.now(); };
} else {
    if (window.performance.webkitNow) {
        console.log("Using webkit high performance timer");
        getTimestamp = function() { return window.performance.webkitNow(); };
    } else {
        console.log("Using low performance timer");
        getTimestamp = function() { return new Date().getTime(); };
    }
}

getTimestamp();

请注意,此 getTimestamp() 函数不会返回表示当前日期/时间的值。返回值只能用于测量时间段,方法是减去两个不同的时间戳。 例如

var t1 = getTimestamp();
//... some other code
var t2 = getTimestamp();
console.log("Time delta: " + (t2 - t1));

【讨论】:

  • 截至 2014 年 3 月,Chrome 的 window.performance.now() 仍然只提供毫秒分辨率。请参阅 Oleg 的回答。
  • '看起来 Chrome performance.now() 现在有微秒级分辨率。
  • 请注意,由于 Spectre 和其他威胁,此值实际上不是高分辨率。 Firefox, for instance, rounds to the millisecond..
【解决方案2】:

@h3r3's answer——这是正确的答案。

毫秒是您在 JavaScript 中所能期望的最佳时间。而且,就像你说的,它不是很准确。请参阅堆栈溢出问题Microsecond timing in JavaScript

timer.js 声称提供高达微秒的分辨率,但它仅适用于Google Chrome

更新:timer.js支持微秒分辨率。它只是将毫秒计数乘以 1000。

抱歉,没有更好的消息!

【讨论】:

  • 好的跟进。如果浏览器不支持准确的计时,那么推广 html+javascript+canvas 的人们如何期望开发人员创建漂亮的动画,或者使用 html+javascript+canvas 为浏览器创建实时交互式图形?动画应该基于时间而不是帧数。基于帧数的动画会导致它们在不同的硬件/系统配置上以不同的速度运行。它也没有那么顺利。随着时间的推移,您可以插入增量。
  • 据我所知,Flash 也没有亚毫秒级计时。我认为没有人期望与原生应用程序具有同等的多媒体性能,而是朝着正确的方向迈出一步。
  • 很高兴知道 timer.js 至少做了这么多,乘以 1000 非常困难。
  • 看起来我们在 2017 年越来越近了:developer.mozilla.org/en-US/docs/Web/API/Performance/now ... 授予高分辨率时间 API 为一些复杂的缓存攻击开辟了潜力,所以这是一种祝福和诅咒:@ 987654326@
【解决方案3】:

刚刚我搜索这样的解决方案,并找到了这个线程。正如我所看到的,所有测量结果都不正确,因为没有人知道 console.log 是如何工作的。根据我的经验(和实践),它花费了太多时间,甚至异步工作。

只需比较以下代码示例:

var old = 0; 

for (var i=0; i<10; i++) {
    var t = window.performance.now(); 
    var d = t - old; 
    old = t; 
    console.log(t + ' += ' +d); 
}

还有这个

var old = 0; 
var out = [];
for (var i=0; i<10; i++) {
    var t = window.performance.now(); 
    var d = t - old; 
    old = t; 
    out.push(t + ' += ' +d)
}
console.log(out);

【讨论】:

    【解决方案4】:

    因为在搜索 JavaScript 和高分辨率计时器时会出现这种情况,所以值得注意的是 window.performance.now 现在可以正常工作(至少在 Google Chrome v. 26 中)并提供约微秒的分辨率。

    var d = 0;
    var p = window.performance;
    for(var i=0; i<10; i++) {
        d = -1 * (p.now() - p.now());
        console.log(d*1000);
    }
    

    它给了我这个(以微秒为单位):

    5.0000089686363935
    3.9999722503125668
    1.00000761449337
    1.00000761449337
    1.00000761449337
    1.9999861251562834
    1.9999861251562834
    1.00000761449337
    1.00000761449337
    0.9999785106629133
    

    我对几组 10k+ 结果进行了一些统计。在我的机器上,最小值约为 1 微秒,平均值约为 1.25 微秒 (MacBook Air)。在 100+ 微秒标记处偶尔会出现高异常值,但在 10 微秒以上的结果频繁出现。

    因此,高分辨率计时器现在能够以微秒分辨率计时减法。

    【讨论】:

    • 也许这取决于操作系统。 Windows 上的 Chrome 33 仍然只提供毫秒。
    • 我在 Windows 7 上的 Chrome 35 上运行了完全相同的代码,结果全为零。
    【解决方案5】:

    Node.js 也有一个高分辨率的计时器。

    process.hrtime()
    

    documentation for details

    【讨论】:

      【解决方案6】:

      使用递归requestAnimationFrame,而不是while (true)/setInterval。它将比基于超时的动画运行更流畅。如果您需要动画在较慢路径上运行,它会提供时间戳。

      【讨论】:

      • 好的,感谢您提供的信息。我的代码是伪代码,用于解释我通常如何设计游戏的结构。获得您的好评就足够了。谢谢。
      【解决方案7】:

      截至目前(2013 年 2 月 25 日),Chrome 24 的高性能时间质量非常糟糕。

      var old = 0; 
      
      for (var i=0; i<10; i++) {
          var t = window.performance.now(); 
          var d = t - old; 
          old = t; 
          console.log(t + ' += ' +d); 
      }
      

      输出如下内容:

      609689.000000013 += 609689.000000013
      609689.9999999441 += 0.9999999310821295
      609689.9999999441 += 0
      609689.9999999441 += 0
      609689.9999999441 += 0
      609690.9999999916 += 1.0000000474974513
      609690.9999999916 += 0
      609690.9999999916 += 0
      609691.9999999227 += 0.9999999310821295
      609691.9999999227 += 0
      

      这表明

      1) 采样很少发生

      2) 精度还是在1ms左右,不在微秒范围内。

      【讨论】:

      • 2013 年 12 月 16 日,Chrome 31 - 仍然是同样的情况,performance.now() 仅返回 \d{6}.(999999999|000000000)\d{2}
      • Chrome 的错误报告在这里:code.google.com/p/chromium/issues/detail?id=158234
      • 解决了吗? 2014 年 10 月 21 日,OS X 上的 Chrome 38 对我来说运行良好(平均打印 += 0.2)。
      【解决方案8】:

      这是您的代码在 JavaScript 中的样子,因此它不会阻塞用户界面,也不会使用不能跨浏览器工作的 window.requestAnimationFrame

      /* Called once, preload essential resources here. */
      function init() {}
      
      /* Updates game/animation state */
      function update(time) {}
      
      /* Updates screen graphics  */
      function render(time) {}
      
      window.onload = function()
      {
          var time;
          var done = false;
      
          init();
          // Using setTimeout passing zero makes the animate function run
          // as soon as possible, but yielding first.
          setTimeout(animate, 0);
      
          function animate () {
              time = new Date();
              update(time);
              render(time);
              if (!done) {
                  setTimeout(animate, 0);
              }
          }
      }
      

      这种方法的一个问题是,animate() 的调用频率可能比屏幕更新的频率高(在 60 Hz 时,它不会比每 16 毫秒的 abot 更新频率更高),从而导致额外的渲染永远无法实现屏幕。这就是为什么你应该尽可能坚持使用 requestAnimationFrame。

      【讨论】:

      • 顺便说一下,当你将0的延迟传递给setTimeout时,实际上并不是零延迟。例如,在 Firefox 5 中是 4 毫秒。请参阅about:config 设置中的dom.min_timeout_value
      • @katspaugh:我在回答中明确表示,零意味着尽快。关于 about:config 设置的好信息。
      • 警告:setTimeout 仍然会阻止您的 UI。 JS 上下文是单线程的,除非您明确启动 web worker
      • @Domi 任何接触 DOM 的代码都会阻塞 UI。 OP 提出了一个繁忙的循环,它完全阻塞了 UI,并且永远不会将控制权交还给更新屏幕的 UI 线程。每次调用setTimeout 时,您都在给 UI 线程一个更新屏幕的机会,这就是动画的重点。 Web Worker 无权访问 DOM,因此您必须使用消息来更新 DOM。
      【解决方案9】:

      您可以将 CSS 3 转换用于简单的动画,这些动画将获得完整的硬件加速,并在大多数现代浏览器上运行如丝般流畅...如果您希望在 WebGL 上获得流畅的动画,那您就太不走运了,因为 JavaScript 没有亚毫秒级的精度。

      目前,在为网络启用游戏相关技术方面取得了很大进展(例如,请参阅我们正在积极开发的全屏鼠标锁定草案API...也许您可以启动微秒精度计时器的运动在 JavaScript 中 ;-)

      【讨论】:

        【解决方案10】:

        除了已经提供的优秀答案,你还可以看看marky,一个

        基于 performance.mark/measure 的高分辨率 JavaScript 计时器 (461 字节 min+gz)

        它还包括开发工具可视化,并在各种浏览器中进行了测试。

        【讨论】:

          【解决方案11】:

          Node 通过 process.hrtime.bigint() 支持纳秒,在 v10.7.0 中添加。

          来自documentation

          process.hrtime() 方法的bigint 版本以bigint 形式返回当前高分辨率实时(以纳秒为单位)。

          与 process.hrtime() 不同,它不支持额外的时间参数,因为可以直接通过两个 bigint 相减来计算差异。

          import { hrtime } from 'process';
          
          const start = hrtime.bigint();
          // 191051479007711n
          
          setTimeout(() => {
            const end = hrtime.bigint();
            // 191052633396993n
          
            console.log(`Benchmark took ${end - start} nanoseconds`);
            // Benchmark took 1154389282 nanoseconds
          }, 1000);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-04-02
            • 2011-10-31
            • 2020-03-02
            • 2010-09-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多