82

I was trying to find the fastest way of running a for loop with its own scope. The three methods I compared were:

var a = "t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t".split();

// lodash .each -> 1,294,971 ops/sec
lodash.each(a, function(item) { cb(item); });

// native .forEach -> 398,167 ops/sec
a.forEach(function(item) { cb(item); });

// native for -> 1,140,382 ops/sec
var lambda = function(item) { cb(item); };
for (var ix = 0, len = a.length; ix < len; ix++) {
  lambda(a[ix]);
}

This is on Chrome 29 on OS X. You can run the tests yourself here:

http://jsben.ch/BQhED

How is lodash's .each almost twice as fast as native .forEach? And moreover, how is it faster than the plain for? Sorcery? Black magic?

kmonsoor
  • 7,600
  • 7
  • 41
  • 55
Matt Zukowski
  • 4,469
  • 4
  • 37
  • 38
  • I ran the test, and for me the native for loop was fastest (for: 1,771,709 vs forEach: 1,287,785 vs lo-dash: 236,743). In most cases, you won't even notice the difference, a few milliseconds here and there won't save you. Also, it would be quite hard to beat the native for loop, seeing as that is the only way to loop for an array (other than for..in, but that's not exactly proper). – Mark Sep 18 '13 at 20:17
  • 2
    See also [Why is Lo-Dash \_.each faster than Boiler.js \_.each and Underscore.js \_.each?](http://stackoverflow.com/questions/16737711/why-is-lo-dash-each-faster-than-boiler-js-each-and-underscore-js-each) – Bergi Sep 18 '13 at 20:21
  • 1
    What is that `lambda` thing for? Why don't you simply put `cb` directly? – Bergi Sep 18 '13 at 20:25
  • In my actual code I'm creating some variables inside the for loop, so I need the contents to be scoped. My original code actually had `(function() { cb[ix] })();` inside the `for` loop, but I thought that wasn't a fair comparison since creating a new closure on each iteration is probably fairly expensive. – Matt Zukowski Sep 18 '13 at 20:31
  • 1
    Lo-Dash's `.each()` is much slower than any other method in your test, for me. `FF 23.0.1` – Joe Simmons Sep 18 '13 at 20:36
  • 1
    The difference between `.each()` and `for` comes from the additional function lookup (`lambda`). See http://jsperf.com/lo-dash-each-vs-native-foreach/15 for a more meaningful benchmark. – user123444555621 Sep 19 '13 at 00:04
  • native .forEach -> 398,167 ops/sec is plenty fast – Pyrolistical Mar 08 '16 at 22:36
  • 1
    I added 10000 elements to your test and the lodash each is now slower than the native foreach: http://jsben.ch/RBkjH – kevinl Sep 05 '17 at 17:48

4 Answers4

100

_.each() is not fully compatible to [].forEach(). See the following example:

var a = ['a0'];
a[3] = 'a3';
_.each(a, console.log); // runs 4 times
a.forEach(console.log); // runs twice -- that's just how [].forEach() is specified

http://jsfiddle.net/BhrT3/

So lodash's implementation is missing an if (... in ...) check, which might explain the performance difference.


As noted in the comments above, the difference to native for is mainly caused by the additional function lookup in your test. Use this version to get more accurate results:

for (var ix = 0, len = a.length; ix < len; ix++) {
  cb(a[ix]);
}

http://jsperf.com/lo-dash-each-vs-native-foreach/15

user123444555621
  • 148,182
  • 27
  • 114
  • 126
  • 1
    Thanks for that. Worth pointing out though that the `for` without the closure is not entirely equivalent. The inside of the `for` block doesn't get its own scope. – Matt Zukowski Sep 19 '13 at 01:55
  • 1
    @MattZukowski Your original [testcase](http://jsperf.com/lo-dash-each-vs-native-foreach/17) `for (...) {(function(item) {cb(item);})(a[ix]);}` is fine for that matter. Contrary to your comment above, this does not create considerable overhead for each iteration. Related: http://stackoverflow.com/questions/17308446/how-big-are-javascript-function-objects – user123444555621 Sep 19 '13 at 02:09
  • 4
    Lo-Dash gets its speed by treating all arrays as dense & hoisting out .call from the loop. Some JS engines may also have problems inlining across the native method boundary which lodash avoids by being plain JS. Treating all arrays as dense is more consistent cross-browser as IE < 9 will treat the literal `undefined` value in an array like `[null, undefined, false]` as a hole & skip it while others won't. – John-David Dalton Sep 20 '13 at 02:57
  • 25
    I get a 404 on that `jsperf` link. – hpaulj Oct 06 '13 at 21:46
  • 1
    I'm getting 404 as well. Did anyone see the results and which was faster? – PrimeLens Dec 29 '16 at 14:59
27

http://kitcambridge.be/blog/say-hello-to-lo-dash/

The lo-dash developers explain (here and on a video) that the relative speed of the native forEach varies among browsers. Just because forEach is native does not mean that it is faster than a simple loop built with for or while. For one thing, the forEach has to deal with more special cases. Secondly, forEach uses callbacks, with the (potential) overhead of function invocation etc.

chrome in particular is known (at least to the lo-dash developers) to have a relatively slow forEach. So for that browser, lo-dash uses it's own simple while loop to gain speed. Hence the speed advantage that you see (but others don't).

By smartly opting into native methods — only using a native implementation if it’s known to be fast in a given environment — Lo-Dash avoids the performance cost and consistency issues associated with natives.

Sathyajith Bhat
  • 21,321
  • 22
  • 95
  • 134
hpaulj
  • 221,503
  • 14
  • 230
  • 353
18

Yes, lodash/underscore each don't even have same semantics as .forEach. There is a subtle detail that will make the function really slow unless the engine can check for sparse arrays without getters quickly.

This will be 99% spec compliant and runs at the same speed as lodash each in V8 for the common case:

function FastAlmostSpecForEach( fn, ctx ) {
    "use strict";
    if( arguments.length > 1 ) return slowCaseForEach();
    if( typeof this !== "object" ) return slowCaseForEach();
    if( this === null ) throw new Error("this is null or not defined");
    if( typeof fn !== "function" ) throw new Error("is not a function");
    var len = this.length;
    if( ( len >>> 0 ) !== len ) return slowCaseForEach();


    for( var i = 0; i < len; ++i ) {
        var item = this[i];
        //Semantics are not exactly the same,
        //Fully spec compliant will not invoke getters
       //but this will.. however that is an insane edge case
        if( item === void 0 && !(i in this) ) {
            continue;
        }
        fn( item, i, this );
    }
}

Array.prototype.fastSpecForEach = FastAlmostSpecForEach;

By checking for undefined first, we don't punish normal arrays in the loop at all. An engine could use its internals to detect strange arrays but V8 doesn't.

Esailija
  • 138,174
  • 23
  • 272
  • 326
6

Here's an updated link (circa 2015) showing the performance difference which compares all three, for(...), Array.forEach and _.each: https://jsperf.com/native-vs-underscore-vs-lodash

Note: Put here since I didn't have enough points yet to comment on the accepted answer.

patricknelson
  • 977
  • 10
  • 16
  • 2
    So it seems that `lodash.each` is no longer faster than `forEach` (Chrome 78, 2020) – adelriosantiago Jan 03 '20 at 18:58
  • Lodash is still faster on my machine (i9-9900k cpu @ 5Ghz 8core 16 thread) than native forEach, but a native for loop is double as fast as both of them (all of this according to running the test at that above URL). – agm1984 May 23 '20 at 18:48