Javascript Performance Measurement

Measuring the time it takes to execute a function is a good way of proving that one implementation of a mechanism is more productive than another. This allows you to make sure that the performance of the function has not suffered after some changes made to the code. It also helps to look for application performance bottlenecks.

If a web project has high performance, this contributes to its positive perception by users. And if users liked working with the resource, they have the property of returning. For example, in thisThe study showed that 88% of online customers are less likely to return to resources that they encounter with any inconvenience. These inconveniences may well be caused by performance issues.

That’s why web development tools are important tools to help you find performance bottlenecks and measure the results of improvements made to your code. Such tools are especially relevant in JavaScript development. It’s important to know that every line of JavaScript code can potentially block the DOM, as JavaScript is a single-threaded language. In this article I will talk about how to measure the performance of functions, and what to do with the measurement results.





If you think that some calculations are too heavy to perform in the main thread, then you might decide to move them to a service worker or web worker.

Performance.now () method


The Performance interface gives access to a value of type DOMHighResTimeStamp through a method performance.now(). This method returns a timestamp indicating the time in milliseconds that has elapsed since the document began to exist. Moreover, the accuracy of this indicator is about 5 microseconds (fractions of a millisecond).

In order to measure the performance of a code fragment using the method performance.now(), you need to perform two time measurements, save the results of these measurements in variables, and then subtract the results of the first from the results of the second measurement:

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

In Chrome, after executing this code, you can get something like this:

0.6350000001020817 "milliseconds"

In Firefox, like this:

1 milliseconds

As you can see, the measurement results obtained in different browsers are very different. The fact is that in Firefox 60, the accuracy of the results returned by the Performance API is reduced. We will talk more about this.

The Performance interface has much more capabilities than just returning a certain timestamp. These include measuring various aspects of performance represented by such extensions of this interface as the Performance Timeline API , Navigation Timing , User Timing , Resource Timing . Here is the material to find out more about these APIs.

In our case, we are talking about measuring the performance of functions, so we have enough opportunities that the method givesperformance.now().

Date.now () and performance.now ()


Here you may have the thought that you can use the method to measure performance Date.now(). This is indeed possible, but this approach has drawbacks.

The method Date.now()returns the time in milliseconds that has elapsed since the Unix era (1970-01-01T00: 00: 00Z) and depends on the system clock. This means not only that this method is not as accurate as performance.now(), but that it, in contrast performance.now(), returns values ​​that, under certain conditions, can be based on incorrect clock indicators. Here's what Rich Gentlekor, a programmer related to the WebKit engine, says: “Perhaps programmers are less likely to think that readings returned when accessingDatebased on system time, it is absolutely impossible to call ideal for monitoring real applications. Most systems have a daemon that regularly synchronizes time. Adjusting the system clock for a few milliseconds every 15-20 minutes is a common thing. With such a frequency, clock settings of about 1% of the 10-second intervals will be inaccurate. ”

Console.time () method


Time measurement using this API is extremely simple. Enough, before the code whose performance you need to evaluate, call the method console.time(), and after this code - the method console.timeEnd(). In this case, one and the other methods need to pass the same string argument. On one page, up to 10,000 such timers can be used simultaneously.

The accuracy of time measurements made using this API is the same as using the Performance API, but the accuracy that will be achieved in each specific situation depends on the browser.

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

After executing such code, the system will automatically output information about elapsed time to the console.

In Chrome, it will look something like this:

test: 0.766845703125ms

In Firefox, like this:

test: 2ms - timer ended

In fact, everything here is very similar to what we saw while working with performance.now().

The strength of the method console.time()is its ease of use. Namely, we are talking about the fact that its application does not require the declaration of auxiliary variables and finding the difference between the indicators recorded in them.

Reduced time accuracy


If you, using the tools described above, measured the performance of your code in different browsers, then you could pay attention to the fact that the measurement results may vary.

The reason for this is that browsers are trying to protect users from time- based attacks and from browser identification mechanisms ( Browser Fingerprinting ). If the results of the time measurement turn out to be too accurate, this can give attackers the opportunity, for example, to identify users.

In Firefox 60, as already mentioned, the accuracy of time measurement results is reduced . This is done by setting the property privacy.reduceTimerPrecisionvalue to 2 ms.

Something to keep in mind when testing performance


Now you have tools at your disposal to measure the performance of JavaScript functions. But before you get down to business, you need to consider some of the features that we will talk about now.

â–Ť Divide and conquer


Suppose that, filtering some data, you paid attention to the slow operation of the application. But you don't know exactly where the performance bottleneck is.

Instead of speculating on which part of the code is slow, it would be better to find out using the above methods.

In order to see the general picture of what is happening, you first need to use console.time()and console.timeEnd()evaluate the performance of a code block, which, presumably, has a bad effect on performance. Then you need to look at the speed of the individual parts of this block. If one of them looks noticeably slower than the others, you need to pay special attention to it and how to analyze it.

The less code there is between calls to methods that measure time, the lower the likelihood that something that is not relevant to the problem situation will be measured.

â–ŤTake into account the features of the behavior of functions at different input values


In real applications, the data received at the input of a particular function can be very different. If you measure the performance of a function that was passed a randomly selected data set, this will not provide any valuable information that can clarify what is happening.

Functions when researching performance need to be called with input data that resembles real ones as much as possible.

â–Ť Run functions many times


Suppose you have a function that iterates over an array. She performs some calculations using each element of the array, and after that returns a new array with the results of the calculations. Thinking about optimizing this function, you want to know what works faster in your situation - a loop forEachor a regular loop for.

Here are two options for this feature:

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;
}

Test the functions:

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

After running the code, we get the following results:

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

The cycle seemed to forEachbe much slower than the cycle for. After all, the test results indicate exactly this?

In fact, after a single test it’s too early to draw such conclusions. Let's try to call the functions twice:

testForEach(x);
testForEach(x);
testFor(x);
testFor(x);

We get the following:

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

It turns out that the function in which it is used forEach, called the second time, is as fast as the one in which it is used for. But, given the fact that the first forEachfunction calls, the function works much slower, it may still not be worth using.

â–ŤTest performance in different browsers


The above tests were performed in Firefox. But what if you execute them in Chrome? The results will be completely different:

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

The fact is that the Chrome and Firefox browsers are based on different JavaScript engines that implement different performance optimizations. It is very useful to know about these differences.

In this case, Firefox has better optimization forEachwith similar input. And the cycle foris faster than forEachin both Chrome and Firefox. As a result, it is probably better to dwell on the variant of function c for.

This is a good example, demonstrating the importance of measuring performance in different browsers. If you evaluate the performance of some code only in Chrome, you can come to the conclusion that the cycle forEach, in comparison with the cycle for, is not so bad.

â–Ť Apply artificial limits to system resources


The values ​​obtained in our experiments do not look particularly large. But be aware that computers that are used for development are usually much faster than, say, the average mobile phone that they use to browse the web.

In order to put yourself in the place of a user who does not have the fastest device, use the capabilities of the browser to artificially limit system resources. For example - to reduce processor performance.

With this approach, 10 or 50 milliseconds can easily turn into 500.

â–Ť Measure relative performance


Performance measurements usually depend not only on the hardware, but also on the current processor load, and on the workload of the main thread of the JavaScript application. Therefore, try to rely on relative indicators characterizing the change in performance, since the absolute indicators obtained when analyzing the same code fragment at different times can vary greatly.

Summary


In this article, we looked at some JavaScript APIs designed to measure performance. We talked about how to use them to analyze real code. I believe that in order to perform some simple measurements, it is easiest to use console.time().

I have the feeling that many front-end developers do not pay enough attention to measuring the performance of their projects. And they should constantly monitor the relevant indicators, as productivity affects the success and profitability of projects.

Dear readers! If you constantly monitor the performance of your projects, please tell us how you do it.


All Articles