如何在Spectre和Meltdown之后使用JavaScript获取微秒级时间?

14

情况

当编写高性能的JavaScript代码时,Chrome等标准分析工具并不总是足够的。它们似乎只提供函数级别的粒度,而且深入查找所需信息可能需要花费相当多的时间。

在.NET中,StopWatch类为我提供了我所需的:任意代码片段的次微秒分辨率计时。

对于JavaScript,performance.now()曾经是衡量性能的一种非常好的方法,但由于Spectre和Meltdown的出现,所有主要的浏览器都将分辨率降低到甚至不到一毫秒。

引用MDN关于performance.now()的话:

时间戳实际上并不高分辨率。为了减轻像Spectre这样的安全威胁,浏览器目前对结果进行不同程度的四舍五入。(Firefox在Firefox 59中开始四舍五入到2毫秒。)有些浏览器也可能稍微随机化时间戳。精度可能在未来版本中再次提高;浏览器开发人员仍在研究这些定时攻击以及如何最好地减轻它们。

问题

我需要微秒级的精度计时。在编写本文时,浏览器似乎没有提供任何选项或标志来禁用这些安全测量。也许我搜索的术语不正确,但我遇到的唯一文章都是关于安全问题的解释以及这些缓解措施如何解决它们。

我在这里并不关心安全方面- 我正在对自己的机器上的性能关键的JavaScript代码进行基准测试,我关心的唯一事情就是尽可能准确地测量,同时尽可能少的付出努力。

现有解决方法

有两个选择:

  1. 安装没有实施这些缓解措施的旧版浏览器

例如,我将不得不使用一个旧版本的FireFox进行基准测试,而使用新版本的Chrome进行浏览。那不太实际,因为我需要在所有浏览器中进行测试(最好还要在所有浏览器中进行基准测试)。此外,新的优化并未在旧版浏览器中实现,因此基准测试可能是无意义的。

  1. 使用WebWorkers实现自定义计时器

我看到了各种旧的博客文章,但似乎没有一篇达到我所需的高精度(毕竟以前有performance.now())。

问题

我如何在不使用旧版浏览器、虚拟机等方法的情况下获得一个有效的预Spectre performance.now()?

是否有任何JavaScript编码技术或库能够实现微秒级精度?

这三种浏览器是否有任何选项或标志可以禁用这些安全措施?

我最终希望能够准确地测量不同代码片段之间的相对性能,因此,如果有一种解决方案可以给我提供ticks而不是微秒,只要它是准确的并且在各个浏览器中都可以工作,那么这也是可以接受的。


2
考虑到您所描述的内容似乎是相当低级的时间计算,可能并不完全依赖于在浏览器中运行(在那里访问DOM、执行ajax等操作比任何正在运行的代码慢得多),您是否可以在node.js环境中工作?您可以使用process.hrtime - James Thorpe
3
我认为你最好的选择是寻找现代浏览器中重新启用高精度计时的旋钮;他们可能已经使其可用以满足像你这样的用例。我不知道怎么做,但我猜测是可能的。不过,你可能需要从源代码构建自己的Firefox和Chromium,并禁用该选项。你主要关心在自己的浏览器上使用它,在一两台机器上,因为这是用于基准测试的。 - Peter Cordes
2
@PeterCordes JavaScript不支持线程,所有概念上的操作都在单个线程中的消息循环中发生 - 虽然现在有像服务工作者这样的东西(可以被视为线程),但你必须在它们之间发布消息 - 没有保证消息会被多快地看到。 - James Thorpe
1
@PeterCordes 感谢您提供的有用见解。我目前正在研究如何构建Chromium :) 如果我设法为Chrome和FF找到解决方案,我会将其发布为答案。希望在此期间有其他人提供现有的解决方案,但这可能只是一厢情愿,哈哈。 - Fred Kleuver
2
是的,你可能会这么说,但我已经在 Google 上搜索了有关禁用精度限制的所有内容 - 完全没有结果是导致我提出这个问题的原因。我认为我在 chromium 中找到了提交:https://github.com/chromium/chromium/commit/a77687fd89adc1bc2ce91921456e0b9b59388120 - 对我来说看起来相当硬编码。 - Fred Kleuver
显示剩余18条评论
2个回答

6

自Firefox 79版本以来,如果您的服务器发送两个标头(header)与网页一起使用,您可以使用高分辨率计时器(high resolution timers):

Starting with Firefox 79, high resolution timers can be used if you cross-origin isolate your document using the Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

These headers ensure a top-level document does not share a browsing context group with cross-origin documents. COOP process-isolates your document and potential attackers can't access to your global object if they were opening it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.

Ref: https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

目前该页面上没有提到,但经过一些实验,我得出结论:在存在这些标头的情况下,定时器的精度为20µs,比默认情况下获得的精度高50倍。

(() => {
  const start = performance.now();
  let diff;
  while ((diff = performance.now() - start) === 0);
  return diff;
})();

使用这些头信息,该函数返回0.02或非常接近于0.02的值;如果没有这些头信息,则返回1。


这些标头现在在Chrome中提供了更好的计时器精度,详情请参见https://web.dev/coop-coep。 - tangobravo
Safari 15.2(iOS 和 Mac 都是)在启用这些头文件的情况下获得了0.02ms的间隔,不启用时为1ms。启用这些标头后,Chrome 的间隔缩短到0.005ms(M1 Mac 上的 Chrome 97似乎存在一个导致0.125ms间隔的错误,但这个问题似乎已经在Chrome Canary中得到修复)。 - tangobravo

5
火狐浏览器有一个名为"privacy.reduceTimerPrecision"的配置设置,可以禁用Spectre漏洞的缓解措施。您可以使用Firefox的about:config页面将其切换为false(在地址栏中输入"about:config")。通过MDN上的提示找到了这个方法。

我在发布这个问题时就读到了 FireFox 中的这个选项,但当时似乎还没有实现。不过现在已经有了。 需要注意的是,切换该选项并不能完全禁用 Spectre 缓解措施,它只将其限制在 20 微秒,这也是 v59 中缓解措施的限制。虽然比默认的 1-2 毫秒要好得多,但仍然不及之前的 5 微秒。 - Fred Kleuver

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