Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accurately measure a Javascript Function performance while displaying the output to user

As you can see in code below, when I increase the size of the string it leads to a 0 milliseconds difference. And moreover there is an inconsistency as the string count goes on increasing.

Am I doing something wrong here?

let stringIn = document.getElementById('str');
let button = document.querySelector('button');

button.addEventListener('click', () => {
  let t1 = performance.now();
  functionToTest(stringIn.value);
  let t2 = performance.now();
  console.log(`time taken is ${t2 - t1}`);
});

function functionToTest(str) {
  let total = 0;
  for(i of str) {
   total ++;
  }
  return total;
}
<input id="str">
<button type="button">Test string</button>

I tried using await too, but result is the same (see code snippet below). The function enclosing the code below is async:

let stringArr = this.inputString.split(' ');
let longest = '';
const t1 = performance.now();
let length = await new Promise(resolve => {
  stringArr.map((item, i) => {
    longest = longest.length < item.length ? longest : item;
    i === stringArr.length - 1 ? resolve(longest) : '';
  });
});
const diff = performance.now() - t1;
console.log(diff);
this.result = `The time taken in mili seconds is ${diff}`;

I've also tried this answer as, but it is also inconsistent.

As a workaround I tried using console.time feature, but It doesn't allow the time to be rendered and isn't accurate as well.


Update: I want to build an interface like jsPerf, which will be quite similar to it but for a different purpose. Mostly I would like to compare different functions which will depend on user inputs.

like image 805
Black Mamba Avatar asked Aug 29 '18 05:08

Black Mamba


2 Answers

There are 3 things which may help you understand what happening:

  1. Browsers are reducing performance.now() precision to prevent Meltdown and Spectre attacks, so Chrome gives max 0.1 ms precision, FF 1ms, etc. This makes impossible to measure small timeframes. If function is extremely quick - 0 ms is understandable result. (Thanks @kaiido) Source: paper, additional links here

  2. Any code(including JS) in multithreaded environment will not execute with constant performance (at least due to OS threads switching). So getting consistent values for several single runs is unreachable goal. To get some precise number - function should be executed multiple times, and average value is taken. This will even work with low precision performance.now(). (Boring explanation: if function is much faster than 0.1 ms, and browser often gives 0ms result, but time from time some function run will win a lottery and browser will return 0.1ms... longer functions will win this lottery more often)

  3. There is "optimizing compiler" in most JS engines. It optimizes often used functions. Optimization is expensive, so JS engines optimize only often used functions. This explains performance increase after several runs. At first function is executed in slowest way. After several executions, it is optimized, and performance increases. (should add warmup runs?)


I was able to get non-zero numbers in your code snipet - by copy-pasting 70kb file into input. After 3rd run function was optimized, but even after this - performance is not constant

time taken is 11.49999990593642
time taken is 5.100000067614019
time taken is 2.3999999975785613
time taken is 2.199999988079071
time taken is 2.199999988079071
time taken is 2.099999925121665
time taken is 2.3999999975785613
time taken is 1.7999999690800905
time taken is 1.3000000035390258
time taken is 2.099999925121665
time taken is 1.9000000320374966
time taken is 2.300000051036477

Explanation of 1st point

Lets say two events happened, and the goal is to find time between them. 1st event happened at time A and second event happened at time B. Browser is rounding precise values A and B and returns them.

Several cases to look at:

A       B       A-B         floor(A)    floor(B)    Ar-Br      
12.001  12.003  0.002       12          12          0
11.999  12.001  0.002       11          12          1
like image 64
Andrii Muzalevskyi Avatar answered Sep 22 '22 01:09

Andrii Muzalevskyi


Browsers are smarter than we think, there are lot's of improvements and caching techniques take in place for memory allocation, repeatable code execution, on-demand CPU allocation and so on. For instance V8, the JavaScript engine that powers Chrome and Node.js caches code execution cycles and results. Furthermore, your results may get affected by the resources that your browser utilizes upon code execution, so your results even with multiple executions cycles may vary.

As you have mentioned that you are trying to create a jsPerf clone take a look at Benchmark.js, this library is used by the jsPerf development team.

Running performance analysis tests is really hard and I would suggest running them on a Node.js environment with predefined and preallocated resources in order to obtain your results.

like image 36
vorillaz Avatar answered Sep 18 '22 01:09

vorillaz