0

Lets say we have an array of 200 000 elements for example... Now we want to iterate it in different ways and check the fastest one. I've heard that if we will save array.length in variable before loop we will reduce execution time, so i tried the code below

let sum = 0
for (let i = 0; i < arr.length; ++i) sum += arr[i]

against

let sum = 0
for (let i = 0, l = arr.length; i < l; ++i) sum += arr[i]

But i got the same result as if in both cases js reads length value just once in the very beginning.

Then i decided to check, what if during loop we will change an array, removing last element.

let sum = 0
for (let i = 0; i < arr.length; ++i) {
  sum += arr[i]
  if (i === 100) arr.pop()
}

against

let sum = 0
for (let i = 0, l = arr.length; i < l; ++i) {
  sum += arr[i]
  if (i === 100) arr.pop()
}

So i expected that second case now should work faster because in first case js inevitably should check array.length each time and i was much suprised that it is not works faster but even slower - from 10 to 15 %. For me it is unexplainable. Any ideas?

Tests: https://jsbench.me/tfkefwjuw2

Dmitry Reutov
  • 2,995
  • 1
  • 5
  • 20
  • You need to test on multiple browser vendors because many times the performance difference boils down to the underlaying browser implementation, For instance, I ran your test on Safari `13.1.1` and all of them scored pretty much the same with only the last one slightly behind `934 ops` vs `938 ops` for the first two – Ricky Spanish Aug 29 '20 at 19:34
  • @RickySpanish, sorry, i accidently changed my tests, one second – Dmitry Reutov Aug 29 '20 at 19:47
  • @RickySpanish, please check now, there are just two cases i mentioned in topic – Dmitry Reutov Aug 29 '20 at 19:51
  • i checked your new bench v2, and on Safari the second test case is 11% faster at `944 op/s` while the first one `837 op/s`. It made me curious, though, so I tried on `chromium 86` which ran both tests _WAY_ faster, but more interestingly the first test case was 10% faster at `4010 op/s`, while the second one scored `3633 op/s` Now i tried Firefox too, which again like Safari (albeit way faster) the second test wins at `4174 op/s` 8% faster... There's something that chromium does that makes the first test case faster, although i can't tell exactly what gives – Ricky Spanish Aug 29 '20 at 21:34
  • @interesting, i have only opera and chrome, both show the same results – Dmitry Reutov Aug 29 '20 at 22:17
  • Yeah they should show the same because they're both chromium based with v8 engine, Firefox however is gecko based and has it's own js engine called SpiderMonkey, Safari is webkit based with JavaScriptCore aka Nitro as it's engine. Pretty much every other browser is just a rebranded chromium nowadays – Ricky Spanish Aug 29 '20 at 23:41

1 Answers1

1

The problem is that

let sum = 0
for (let i = 0, l = arr.length; i < l; ++i) {
  sum += arr[i]
  if (i === 100) arr.pop()
}

is now incorrect, as it loops beyond the end of the array. The sum will be NaN in the end. The correct solution would have l = arr.length - 1 so that this doesn't happen.

Now why does it becomes so slow? Because array accesses in javascript are only fast (get compiled to a fast path with pointer addressing) when the element exists. When you miss, the code will get de-optimised. Since JSbench runs the same code multiple times, so even if the deoptimisation happens only at the last of 200000 iterations, the subsequent runs will be much slower.

See this talk for details, which even explicitly spells out "Avoid out-of-bounds reads" on one slide. In general, don't use non-idiomatic code to improve performance. i < arr.length is idiomatic, and JS engines will optimise it well.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • @Bergi, it is true about error, but i changed it to `if (i === 1000) { arr.pop(), l-- }` which shoud fix the problem but perfomance is the same – Dmitry Reutov Aug 29 '20 at 20:56
  • @DmitryReutov Performance did improve considerably for me, from 1100 runs/s to 1250 runs/s. But yeah, the original code with `i < arr.length` is still faster at 1400 runs/s. No idea why specifically that is. – Bergi Aug 29 '20 at 21:01
  • it is weird...anyway thanks for new information, i'm gonna read it – Dmitry Reutov Aug 29 '20 at 21:02
  • @DmitryReutov What browser are you using? The resources I linked were about the internals of V8 (the most popular engine, used in Google Chrome and Node.js), but the generic advice is the same for all engines. – Bergi Aug 29 '20 at 21:06
  • i can check outputs on several, but yes, chrome is priority – Dmitry Reutov Aug 29 '20 at 21:11
  • You might want to [ask a new question](https://stackoverflow.com/questions/ask) about why even the updated code still shows a difference and tag it with [[tag:V8]] so that @jmrk will answer with low-level insights. Btw I also tried a `const l = array.length-1` version and it's similar to the conditional `l--` (still slower than `< arr.length`), so not modifying `l` doesn't help much either. – Bergi Aug 29 '20 at 21:15