DEV Community

Alexander Nenashev
Alexander Nenashev

Posted on • Edited on

Avoid iterators/generators to make Javascript fast

If we look at the iterator protocol we'd notice it comes at a cost of creating {value, done} objects, accessing their props, and calls of next() (a function call is generally is an expensive operation).

Given all the hurdles we have a quite inefficient tool to iterate our data. Moreover when we implement an iterator with a generator we suddenly notice that performance got even worse. I guess that's because of the cost of keeping a generator's scope and its state between calls.

Don't get me wrong, iterators and generators are good abstractions, for await is exceptionally amazing when dealing with async data and abstracting backend API calls, so generally used in business logic they bring benefits but when we descend into the realm of transforming data, generic data handling functions and algorithms we should care to avoid them when possible.

Here's a benchmark of several ways to iterate an array and you see the iterator and generator fail considerably. I'm not the only one complaining, I've seen several comments that generators' speed keeps people from using them widely.

class Iter {
  *gen(arr) {
    for (let i = 0; i < arr.length; i++) {
      yield arr[i];
    }
  }
  iter(arr) {
    let i = 0;
    const out = { value: null, done: false };
    const iter = {
      [Symbol.iterator]: () => iter,
      next: () => {
        if (i === arr.length) out.done = true, out.value = null;
        else out.value = arr[i++];
        return out;
      }
    };
    return iter;
  }
}

const $chunk = () => Array.from({length: 10}, (_, i) => i);
const $input = [], arr = $input;
const iter = new Iter;

// @benchmark generator
for(const e of iter.gen(arr));

// @benchmark iterator
for(const e of iter.iter(arr));

// @benchmark for..of
for(const e of arr);

// @benchmark for
for(let i = 0; i < arr.length; i++) arr[i];

// @benchmark forEach
arr.forEach(e => {});

` Chrome/125
-----------------------------------------------------------------------------------------
>                 n=10        |       n=100       |      n=1000       |      n=10000     
for           1.17x x100m 336 |   1.20x  x10m 354 |   1.01x   x1m 235 | ■ 1.00x x100k 218
for..of       1.19x x100m 340 |   1.21x  x10m 355 | ■ 1.00x   x1m 232 |   1.00x x100k 219
forEach     ■ 1.00x x100m 286 | ■ 1.00x  x10m 294 |   1.00x   x1m 233 |   4.54x  x10k  99
iterator     64.34x   x1m 184 |  13.71x   x1m 403 |  10.86x x100k 252 |  10.96x  x10k 239
generator    43.01x   x1m 123 |  36.05x x100k 106 |  45.69x  x10k 106 |  43.58x   x1k  95
----------------------------------------------------------------------------------------- `
` Firefox/126
-----------------------------------------------------------------------------------------
>                 n=10        |       n=100       |      n=1000       |      n=10000     
for         ■ 1.00x x100m 512 | ■ 1.00x  x10m 505 | ■ 1.00x   x1m 453 | ■ 1.00x x100k 449
forEach       1.62x x100m 828 |   1.48x  x10m 746 |   4.26x x100k 193 |   6.57x  x10k 295
for..of      11.07x  x10m 567 |   9.35x   x1m 472 |   9.98x x100k 452 |  10.11x  x10k 454
iterator     23.44x   x1m 120 |  10.93x   x1m 552 |  11.24x x100k 509 |  10.71x  x10k 481
generator    82.62x   x1m 423 |  64.75x x100k 327 |  66.67x  x10k 302 |  68.37x   x1k 307
----------------------------------------------------------------------------------------- `
Enter fullscreen mode Exit fullscreen mode

Open in the playground

One would argue that for..of in Chrome performs nice despite of using an array's iterator but that's an optimization of using a native iterator. Firefox is generally more honest in benchmarks if we want generic evaluation of Javascript code, but about that is in the next post.

Top comments (0)