Measuring the Performance of JavaScript Functions
Tags: javascript, webperf
Published: 2020-05-03 10:20:04


> This post was [first published on my blog](https://felixgerschau.com/measuring-the-performance-of-java-script-functions).

Measuring the time it takes to execute a function is always a good idea to **prove** that some implementation is more performant than the other. It's also a good way to ensure that performance didn't suffer after some change and to track down bottlenecks.

Good performance contributes to good user experience. And a good user experience makes users come back. Like [this research](http://www.mcrinc.com/Documents/Newsletters/201110_why_web_performance_matters.pdf) for example shows, **88% of online consumers** are less likely to return after a poor user experience due to performance issues.

That's why it's important to be able to recognize bottlenecks in your code and measure the improvements. Especially when developing JavaScript for the browser it's important to be aware that every line of JavaScript you write can potentially block the DOM since it's a single-threaded language.

In this article, I will explain how you can measure the performance of your functions and what to do with the results, you get from them.

The functions I mention here are good for debugging JavaScript functions at a low level. If you want to make sure that your application stays fast, even after more features get added, consider the implementation of a [performance budget](https://felixgerschau.com/getting-into-performance-budgets).

## Perfomance.now

The performance API provides access to the [DOMHighResTimeStamp](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp) through its function `performance.now()`, which returns the time passed since the page loaded in milliseconds, with a precision of up to **5µs** (in the fractional).

So in practice you need to take two timestamps, save them in a variable and then rest the second from the first one:

``` javascript
const t0 = performance.now();
for (let i = 0; i < array.length; i++) 
{
  // some code
}
const t1 = performance.now();
console.log(t1 - t0, 'milliseconds');
```

Output (Chrome):

``` terminal
0.6350000001020817 "milliseconds"
```

Output (Firefox):

``` terminal
1 milliseconds
```

Here we can see that the result in Firefox is quite different to Chrome. This is because Firefox, as of version 60, is reducing the precision of the performance API to 2ms. You can find more information about this at the end of this post.

The performance API offers much more functionality than only returning a timestamp. It's able to measure *navigation timing, user timing or resource timing*.
Check [this article](https://blog.logrocket.com/how-to-practically-use-performance-api-to-measure-performance/) out which explains it more in detail.

For our use-case, however, we only want to measure the performance of a single function so a timestamp will be enough.

##### Isn't that the same as Date.now?

Now you could think, hey, I could also use `Date.now` for that.

Yes you can, but that has drawbacks.

`Date.now` returns, in *milliseconds*, the time passed since the Unix epoch ("1970-01-01T00:00:00Z") and depends on the system clock. This doesn't only mean that it's **not as precise**, but it's also **not always incrementing**. Here's how a WebKit engineer (Tony Gentilcore) explains it:

> Perhaps less often considered is that Date, based on system time, isn't ideal for real user monitoring either. Most systems run a daemon which regularly synchronizes the time. It is common for the clock to be tweaked a few milliseconds every 15-20 minutes. At that rate about 1% of 10 second intervals measured would be inaccurate.

## Console.time

This API is really easy to use. Simply put `console.time` before and `console.timeEnd` after the code you want to measure, calling the function with the same `string` argument. You can use up to 10,000 timers simultaneously on one page.

The precision is the same as of the performance API but this again depends on the browser.

``` javascript
console.time('test');
for (let i = 0; i < array.length; i++) {
  // some code
}
console.timeEnd('test');
```

This automatically generates a human-readable output like the following:

Output (Chrome):
``` terminal
test: 0.766845703125ms
```

Output (Firefox):
``` terminal
test: 2ms - timer ended
```

The outputs here are again very similar to the performance API.

`console.time` has the advantage that it is **easier to use** since it doesn't require calculating the difference between two timestamps manually.

## Reduced Time Precision

If you measure your function with the APIs mentioned above in different browsers you might notice that the **results vary**.

This is due to browsers trying to **protect users** against [timing attacks](https://en.wikipedia.org/wiki/Timing_attack) and [fingerprinting](https://pixelprivacy.com/resources/browser-fingerprinting/),
which can be used by hackers to identify users if the timestamp is too accurate.

Browsers like Firefox, for example, try to prevent this by [reducing the precision](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp) to 2ms (version 60).

## Things to Keep in Mind

Now you have the tools you need to measure how fast your JavaScript functions are. But there are some pitfalls that are better to be avoided:

### Divide and Conquer

You noticed something is slow when filtering some results but you don't know where the bottleneck is.

Instead of making wild guesses what part of the code is slow, you can use these above-mentioned functions to measure it.

To trace it down first place your `console.time` statements around the block of code that is slow. Then measure how different parts of them perform. If one is slower than the others, continue there and go deeper every time until you find the bottleneck.

The less code you have between those statements, the less likely it is that you're tracking something you're not interested in.

### Be Aware of the Input Values

In a real-world application, the input values of a given function can change a lot. Just measuring the speed of the function for any random value doesn't give us *any* valuable data we can actually use.

Be sure to run your code with the same input values.

### Run the Function Multiple Times

Let's say you have a function that iterates over an array, does some calculations with each of its values and returns an array with the results. You want to find out whether `forEach` or a simple `for` loop is more performant.

Those are the functions:

``` javascript
function testForEach(x) {
  console.time('test-forEach');
  const res = [];
  x.forEach((value, index) => {
    res.push(value / 1.2 * 0.1);
  });

  console.timeEnd('test-forEach')
  return res;
}

function testFor(x) {
  console.time('test-for');
  const res = [];
  for (let i = 0; i < x.length; i ++) {
    res.push(x[i] / 1.2 * 0.1);
  }

  console.timeEnd('test-for')
  return res;
}
```

And you test them like this:

``` javascript
const x = new Array(100000).fill(Math.random());
testForEach(x);
testFor(x);
```

If you run the above functions in Firefox you'll get an output similar to this:

``` terminal
test-forEach: 27ms - timer ended
test-for: 3ms - timer ended
```

Looks like forEach is way slower, right?

Let's have a look if we run the same functions twice, using the same input:

``` javascript
testForEach(x);
testForEach(x);
testFor(x);
testFor(x);
```

``` terminal
test-forEach: 13ms - timer ended
test-forEach: 2ms - timer ended
test-for: 1ms - timer ended
test-for: 3ms - timer ended
```

If we call the `forEach` test a second time it performs just as well as the `for` loop. It's probably not worth using `forEach` anyway, given the slower initial value.

### ...and in multiple browsers

If we run the above code in Chrome the result suddenly looks different:

``` terminal
test-forEach: 6.156005859375ms
test-forEach: 8.01416015625ms
test-for: 4.371337890625ms
test-for: 4.31298828125ms
```

That's because Chrome and Firefox have different JavaScript engines have different kind of performance optimizations. It's good to be aware of these differences.

In this case, Firefox is doing a better job of optimizing the use of `forEach` with the same input.

`for` performs better on both engines so it's probably better to stick to the `for` loop.

This is a good example of why you should measure in multiple engines. If you only measured in Chrome you might have come to the conclusion that `forEach` is not so bad in comparison to `for`.

### Throttle your CPU

Those values don't seem like much. Be aware that your development machine is usually much faster than the average mobile phone that your website is being viewed on.

To get a feeling of how this looks like, browsers have a feature that lets you throttle your CPU performance.

With this those 10 or 50ms quickly become 500ms.

### Measure Relative Performance

These raw results actually don't only depend on your hardware but also on the current load on your CPU and your JavaScript thread. Try to focus on the relative improvement of your measurements since these numbers can look very different the next time you restart your computer.

## Conclusion

In this article, we have seen some JavaScript APIs we can use to measure performance and then how to use them in the *"real world"*. For simple measurements, I find that it's easier to use `console.time`.

I feel like many frontend developers don't give enough thought to performance on a daily basis, even though it has a direct impact on revenue.

How do you make sure that you don't forget about performance in daily business? Feel free to send me an email or tweet with your ideas!