Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get microsecond timings in JavaScript since Spectre and Meltdown

The situation

When writing high-performance JavaScript code, the standard profiling tools offered by Chrome et al are not always sufficient. They only seem to offer function-level granularity and it can be quite time-consuming to drill down and find the information I need.

In .NET the StopWatch class gives me exactly what I need: sub-microsecond resolution timing of arbitrary pieces of code.

For JavaScript performance.now() used to be a pretty good way to measure performance, but in response to Spectre and Meltdown all major browsers have reduced the resolution down to not even a millisecond.

To quote MDN on performance.now():

The timestamp is not actually high-resolution. To mitigate security threats such as Spectre, browsers currently round the result to varying degrees. (Firefox started rounding to 2 milliseconds in Firefox 59.) Some browsers may also slightly randomize the timestamp. The precision may improve again in future releases; browser developers are still investigating these timing attacks and how best to mitigate them.

The problem

I need microsecond precision timings. At the time of writing, browsers don't seem to offer any options or flags to disable these security measurements. Maybe I'm googling the wrong terms but the only articles I come across are explanations of the security problems and how these mitigations address them.

I'm not interested in the security aspect here - I'm benchmarking performance-critical pieces of JavaScript code on my own machine and the only thing I care about is that I get as accurate measurements as possible with as little effort as possible.

Existing workarounds

Two options come to mind:

  1. Install an older version of a browser that doesn't have these mitigations implemented

I'd have to dedicate an old version of FireFox to benchmarking, and a new version of Chrome to browsing for example. That's not practical since I need to test in all browsers (and preferably also benchmark in all browsers). Also, new optimizations are not implemented in old browsers so the benchmarks would be potentially useless.

  1. Implement a custom timer using WebWorkers

I've seen various older blog posts on this but none of them seem to achieve the high precision that I need (after all, there used to be performance.now() for that).

The question

How do I get an effectively pre-Spectre performance.now() without having to resort to older browser versions, Virtual Machines and such?

Are there any coding techniques or libraries in JavaScript that achieve microsecond precision?

Are there any options or flags for the 3 aforementioned browsers that disable these security measures?

I'm ultimately looking for a way to accurately measure the relative performance of different pieces of code compared to one another - so if there is a solution that gives me ticks, rather than microseconds, that would be acceptable too as long as it's accurate and works across browsers.

like image 589
Fred Kleuver Avatar asked May 01 '18 13:05

Fred Kleuver


1 Answers

Since Firefox 79, you can use high resolution timers if you make your server send two headers with your page:

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

It's not mentioned on that page as of this moment, but with a little experiment, I've concluded that the accuracy of the timer is 20µs with those headers present, which is 50x better than the accuracy you get by default.

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

This returns 0.02 or a value very close to 0.02 with those headers, and 1 without.

like image 105
Dogbert Avatar answered Sep 17 '22 18:09

Dogbert