1

I setup a simple benchmark for comparing performance of for (const x of arr) and for (let i = 0; i < arr.length; i++) loops. (benchmark link)

Setup code

const arr = [...new Array(10000)].map(() => Math.random());

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

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

for (const x of arr)

let sum = 0;
for (const x of arr) {
    sum += x;
}

I have re-run the benchmark several times over, and each time, the for-of loop is almost 70% slower than the for-length loop. (~150k ops/sec vs ~43k ops/sec). This is very surprising to me.

What is the reason for this loop being significantly slower?

I have looked at the related thread and the answers there dive into pros/cons of micro-benchmarking, and the conclusion was that switching the test order results in flipping the result. My case is different because I have large arrays (so no micro-bench issues) and I am using a test framework (jsbench.me) to avoid cold start issues.

Gaurang Tandon
  • 6,504
  • 11
  • 47
  • 84
  • 1
    I'm not big on Javascript, but wouldn't that be coming from the fact that the `for-of` loop uses Iterators (and all the overhead that comes with it) underneath whereas the `for-length` loop simply iterates over a memory section ? – Noah Boegli Jul 28 '22 at 07:16
  • You can optimize for...length loop even further if you save value of `arr.length` to a variable, because arr.length is an accessor (get/set method) – Aziz Hakberdiev Jul 28 '22 at 07:33
  • Just have a look at the specification for the _"why?"_ -> [`for`](https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-runtime-semantics-forloopevaluation), [`for...of`](https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-runtime-semantics-forinofloopevaluation) – Andreas Jul 28 '22 at 07:34
  • @AzizHakberdiev That really shouldn't make any difference. Accessors have a time complexity of `O(1)` and even if it would make a difference, this would likely be optimized by the JIT compiler. – Ivar Jul 28 '22 at 08:00
  • @Ivar bigO does not make any sense. Say, two functions have complexity O(1), but one of them calculates sum of two number, the other does division operation. First one will be way faster – Aziz Hakberdiev Jul 28 '22 at 08:09
  • @AzizHakberdiev True, but that doesn't mean that calling `.length` repeatedly is any slower. By all means modify the benchmark provided in the question to check if it makes a difference. (And make sure to run the test multiple times because differences this small can easily swap the outcome.) – Ivar Jul 28 '22 at 08:17
  • @Ivar I have around 14-15% improvemnt – Aziz Hakberdiev Jul 28 '22 at 08:31
  • 1
    @AzizHakberdiev That's definitely not something I see. All outcomes are very close, twice using `array.length` in the array condition is slightly faster ([1](https://i.stack.imgur.com/MIbOM.png), [2](https://i.stack.imgur.com/KdSsv.png)), once using a separate variable is faster ([3](https://i.stack.imgur.com/OWN3P.png)). – Ivar Jul 28 '22 at 09:28

1 Answers1

1

for...of accesses @iterator method (obj[Symbol.iterator] to access), which is a generator function that contains the for...length loop inside with yield statements

Aziz Hakberdiev
  • 180
  • 2
  • 9
  • "_which is a generator function that contains the for...length loop inside with yield statements_" - Do you have any source for that? I'm pretty confident that the array iterator's code is native and depend on the JS engine that implements it. – Ivar Jul 28 '22 at 08:06
  • 2
    @Ivar, https://chromium.googlesource.com/v8/v8.git/+/3.31.25/src/array-iterator.js?autodive=0%2F%2F even if array iterator is native, it still consumes resources to behave like generators – Aziz Hakberdiev Jul 28 '22 at 08:23