16

In JavaScript, I noticed that the ES6 for ... of loop has a much different performance than the traditional for (start; stop; step) loop.

Benchmark

const n = 10000;
const arr = Array(n).fill().map((e, i) => i); // [0, n)

console.log('n =', n);

let sum1 = 0;
console.time('for let i');
for (let i = 0; i < arr.length; i++) {
  sum1 += arr[i];
}
console.timeEnd('for let i');

let sum2 = 0;
console.time('for of');
for (let v of arr) {
  sum2 += v;
}
console.timeEnd('for of');

Results

n = 10
for let i: 0.350ms
for of: 0.015ms
-----
n = 100
for let i: 0.354ms
for of: 0.023ms
-----
n = 1000
for let i: 0.429ms
for of: 0.111ms
-----
n = 10000
for let i: 1.048ms
for of: 2.138ms
-----
n = 100000
for let i: 9.452ms
for of: 13.644ms

(Tested using Node.js v10.11.0)

As you can see, as n increases, the speed of the for-of loop decreases at a faster rate than the standard for loop. Why is the for-of loop faster for smaller arrays and slower for larger ones?

Brenden
  • 319
  • 1
  • 11
  • 2
    Your timings are very likely just wrong. – Veedrac Oct 22 '18 at 04:32
  • @Veedrac https://repl.it/@BrendenCampbell/ForOfPerfTest accounting for variable CPU availability on repl.it, in the majority of tests, my results are repeatable. – Brenden Oct 22 '18 at 13:38
  • 7
    Try switching which of the loops is first. If your timings are real, this will change nothing. – Veedrac Oct 22 '18 at 17:28
  • 1
    "*ES6 `for ... of loop` has a much different performance than the traditional `for (start; stop; step)` loop*" - on a standard array, [they should perform exactly the same](https://youtu.be/EhpmNyR2Za0?t=17m15s). Please report it as a bug. – Bergi Dec 24 '18 at 20:10
  • @Veedrac you're right. after switching order I see exactly opposite. first loop(for-of) looks way slower – skyboyer Dec 24 '18 at 20:13
  • you're also accessing `arr.length` in the for loop; try setting that value to a local variable. – Derek Dec 25 '18 at 02:21
  • If you want to squeeze some extra performance out, you can grab the length once, at the start of the iteration. Checking length on each iteration takes some time. `for (let i = 0, len = arr.length; i < len; i++) { ... }` – Burg Feb 09 '23 at 19:23

4 Answers4

7

When benchmarking smaller values, the overhead operations can have a bigger impact on the test.

For example, if variable initialization and memory allocation takes 0.1 ms, this is negligible with n > 1000, however significant with n = 10.

In this case, the for/of operator allows the V8 engine to optimize the loop operation (reducing the overheads like above). For example it may pre-load the array items onto the stack or similar.

The for/let operation will process each item independent of the whole array, and is more explicit in variable usage (reducing the amount of optimization the engine can do).

Brenden
  • 319
  • 1
  • 11
Russell
  • 17,481
  • 23
  • 81
  • 125
  • What if you just swap `for let` and `for of` blocks in benchmark code and execute it with `n=10` and `n=100`? Can you explain why timings then change that drastically and why now `for let` is quicker? :) – Kostiantyn Dec 25 '18 at 02:56
5

I'd suggest taking a look into microbenchmarking concept, and also getting familiar with this great answer for microbenchmarking in JS.

In short, when testing something small like for loops for timings, you can easily get your test logic interfered with other ongoing processes under the hood. For example, if you swap the order of execution in your test case so that for of is executed before for let, you may notice surprising timings change for smaller n values (spoiler: in this case for let wins the race for n=10 and n=100)

So, the answer to your why question: for let is slower in your listing, because it is closer to the program start and executes on more 'cold' vm, also warming it up for the consequent for let statement. The bigger the n is, the lesser this side effect contributes to the measured execution time.

That's why microbenchmarks imply execution of the series of identical tests instead of just a single one - to make small side effects not that significant on the higher scale

Additionally, note that canonical unit of measurement of performance of statements is "total operations per unit of time", instead of absolute time per single set of operations

Example microbenchmark for your topic can be found here

Kostiantyn
  • 1,856
  • 11
  • 13
3

The for-of loops only over the data (like the cells of an array), not the objects themselves. Increasing performance greatly.

The other loops loop over objects. The for-of loop makes the internal code simpler.

Instead of accessing the memory addresses (a hex number) of the array, which takes longer for larger arrays of n size. Those types of loops are more suitable for smaller quantities.

The for-of loop iterates over the numerical data and NOT the objects, which is much more efficient at looping through a ton of elements. You apply the necessary loop type to the specific situation of your code.

It's like a horse and a car. The horse is good for a journey less than 100 miles and not a 100+ mile one. When a car can do that better.


Refer to this link for more info. - https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/

Compiler v2
  • 3,509
  • 10
  • 31
  • 55
0

Let me try to explain, in this case for is faster than for...of because operation is much simpler, detailed explanation is can be found here:

for(var VariableDeclarationList; Expression; Expression) Statement

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

So what happens is here:

  • Check i < arr.lenght
  • i plus one

But things are a bit different for...of

for(var ForBinding in Expression) Statement

let sum2 = 0;
console.time('for of');
for (let v of arr) {
  sum2 += v;
}

As I know, for..of use object-specific iterator and loops over the values generated by that so this will take a bit more time than for so things are little interesting here. If you want to learn more about this please check difference on runtime semantics:

for runtime semantics: here

for...of runtime semantics: here

This case is only valid user defined Symbol.iterators, but the built-in iterators should be optimized to work better in a for...of instead of a for "thanks for @Aryter"

So why your test is not accurate? Because your generating for...of after for loop so V8 engine is doing some optimization there.

My assumption of that optimization is related with JavaScript properties and make significant speed difference V8 engine when object is small which more detailed can be found here V8 Engine fast properties

Hope is more clear now...

Whatatimetobealive
  • 1,353
  • 10
  • 15
  • 2
    If your down voting please leave your comment and reason so I can learn too. – Whatatimetobealive Dec 25 '18 at 00:26
  • 1
    Maybe the downvotes are because you start with `for is always faster than for...of` while the benchmarks in the OP show that's not true. – Mark Dec 25 '18 at 00:51
  • I edited, and said 'in this case' hope is more clear. I also explained why is benchmark is not correct. – Whatatimetobealive Dec 25 '18 at 00:52
  • 1
    It's not just that, you're not actually *explaining* anything. The OP most likely wants to know **why**. I expected to see a profiler run of the code, not a "well, it is because it is!" explanation for a bounty question, personally, and that's why I downvoted you. – Sébastien Renauld Dec 25 '18 at 00:54
  • 2
    You forgot to mention that `for` also has to do `arr[i]`. And your statement would be correct for user defined `Symbol.iterator`s, but the built-in iterators should be optimized to work better in a `for...of` instead of a `for`. If we talk just about semantics, `for` rebinds `i` every time and accesses `arr[i]`, `for...of` rebinds `v` every time, and accesses `arr[i]` internally, but it is never that simple, so talking about the "simplicity" of an action doesn't always correlate to the speed. – Artyer Dec 25 '18 at 00:56
  • @Artyer thanks I added your comment and I edited my answer to clarify `V8 engine is doing some optimization there` – Whatatimetobealive Dec 25 '18 at 03:51