Home > Enterprise >  Understanding how to optimize the performance of JS Arrow Functions in different contexts
Understanding how to optimize the performance of JS Arrow Functions in different contexts

Time:12-27

I'm trying to understand why arrow functions might run faster or slower in different javascript contexts.

Given the following three options:

  1. JS Statement
  • (e.g. paragraph.textContent = 'Loop iteration ' (i 1))
  1. JS Anonymous Arrow Function
  • (e.g. (i) => paragraph.textContent = 'Loop iteration ' (i 1))
  1. JS Arrow Function assigned to Named Variable
  • (e.g. myFunction(i))

I would have guessed (entirely wrongly) that the raw JS Statement might run the fastest, followed by the Arrow Function assigned to Named Variable, and that the Anonymous Arrow Function might run the slowest.

So I wrote a quick to test to verify that my assumptions were correct:

let paragraph = document.querySelector('p');
let button = document.querySelector('button');

let rawStatement = document.querySelector('.rawStatement');
let anonymousFunction = document.querySelector('.anonymousFunction');
let functionAssignedToVariable = document.querySelector('.functionAssignedToVariable');

const runRawStatement = () => {

  let scriptDurations = [];
  
  for (let h = 0; h < 100; h  ) {
    
    let scriptStart = window.performance.now();
    
    for (let i = 0; i < 10000; i  ) {
      paragraph.textContent = 'Loop iteration '   (i   1);
    }
    
    let scriptEnd = window.performance.now();
    
    scriptDurations.push((scriptEnd - scriptStart));
  }
    
  return ((scriptDurations.reduce((a, b) => a   b, 0)) / 100);
}

const runAnonymousFunction = () => {

  let scriptDurations = [];

  for (let h = 0; h < 10; h  ) {

    let scriptStart = window.performance.now();

    for (let i = 0; i < 10000; i  ) {
      (i) => paragraph.textContent = 'Loop iteration '   (i   1);
    }

    let scriptEnd = window.performance.now();

    scriptDurations.push((scriptEnd - scriptStart));
  }

  return ((scriptDurations.reduce((a, b) => a   b, 0)) / 10);
}

const runFunctionAssignedToVariable = () => {

  let scriptDurations = [];

  for (let h = 0; h < 10; h  ) {

    const myFunction = (i) => paragraph.textContent = 'Loop iteration '   (i   1);

    let scriptStart = window.performance.now();

    for (let i = 0; i < 10000; i  ) {
      myFunction(i);
    }

    let scriptEnd = window.performance.now();

    scriptDurations.push((scriptEnd - scriptStart));
  }

  return ((scriptDurations.reduce((a, b) => a   b, 0)) / 10);
}

const runTimers = () => {

  let runRawStatementDuration = runRawStatement();
  document.querySelector('.rawStatement').textContent = runRawStatementDuration   'ms';
  
  let runRawFunctionDuration = runAnonymousFunction();
  document.querySelector('.anonymousFunction').textContent = runRawFunctionDuration   'ms';
    
  let runVariableFunctionDuration = runFunctionAssignedToVariable();
  document.querySelector('.functionAssignedToVariable').textContent = runVariableFunctionDuration   'ms';
  
  button.textContent = 'Re-run Script';
}
  
button.addEventListener('click', runTimers, false);
button {
  display: inline-block;
  cursor: pointer;
}

table {
  margin: 12px 0;
  border-collapse: collapse;
}

th,
td {
  padding: 6px;
  border: 1px solid rgb(0, 0, 0);
}

button {
  display: inline-block;
  margin-right: 6px;
}
<table>
<thead><th>Script Type</th><th>Average of 1000 run-throughs</th></thead>
<tr><td>JS Statement</td><td ></td></tr>
<tr><td>Anonymous Arrow Function</td><td ></td></tr>
<tr><td>Arrow Function assigned to Named Variable</td><td ></td></tr>
</table>

<button type="button">Run Script</button>
<em>(N.B. script takes about 5 seconds to run)</em>

<p></p>

However, consistently, the anonymous function is the fastest (by a degree of magnitude), followed (a long way behind) by the same anonymous function assigned to a variable and the slowest code to run is usually the raw JS statement.

Can anyone explain why the results consistently show that the anonymous function runs more slowly when assigned to a variable and why both the functions are faster than the raw JS statement.

CodePudding user response:

The anonymous function case is running faster because you've made a mistake in it: You're creating the function, but you don't ever run it. As a result, less code is being run, which is why it runs so much faster. To make for a more equal test, change the code to:

for (let i = 0; i < 10000; i  ) {
  ((i) => paragraph.textContent = 'Loop iteration '   (i   1))(i);
}

With that change, the runtimes will be much closer, though still not equal. When i run it (and from your comment, when you run it), the statement code is still the slowest, by a small amount.

To speculate on why that is: Modern javascript engines (V8 for example) do a lot of stuff to optimize code behind the scenes, and one example is that if a function is run multiple times, it will try to rewrite your function to be more optimized. For example, the variable i could in principle be any type, so the compiled code that perfectly represents the javascript code would need typechecks and such so it can work with whatever you pass in. But once V8 has seen the function run a few times it can notice that i is always an integer, and it may create an version of the compiled code which is optimized to work with integers, but that's all it can do.

I speculate that it's better able to optimize your code when it's in a function form, than when it's written inline. But i would not rely on this: v8 is complicated and constantly improving/changing, so what might be the optimal javascript hack today might not be optimized in a couple years. Focus on writing code that's easy for us puny meat-humans to understand, and let the wizards of V8 handle the rest :)

  • Related