简体   繁体   English

为什么lodash.each比原生forEach更快?

[英]Why is lodash.each faster than native forEach?

I was trying to find the fastest way of running a for loop with its own scope. 我试图找到运行具有自己范围的for循环的最快方法。 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: 这是在OS X上的Chrome 29上。您可以在此处自行运行测试:

http://jsben.ch/BQhED http://jsben.ch/BQhED

How is lodash's .each almost twice as fast as native .forEach ? Lodash的.each几乎是原生.forEach And moreover, how is it faster than the plain for ? 而且,它是如何比平原快for Sorcery? 巫术? Black magic? 黑魔法?

_.each() is not fully compatible to [].forEach() . _.each()[].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/ http://jsfiddle.net/BhrT3/

So lodash's implementation is missing an if (... in ...) check, which might explain the performance difference. 所以lodash的实现缺少if (... in ...)检查,这可能解释了性能差异。


As noted in the comments above, the difference to native for is mainly caused by the additional function lookup in your test. 如上面的评论中指出,到本机的差异for原因,主要是在测试中的附加功能查找。 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 http://jsperf.com/lo-dash-each-vs-native-foreach/15

http://kitcambridge.be/blog/say-hello-to-lo-dash/ 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. lo-dash开发人员解释(在这里和视频中)本机forEach的相对速度因浏览器而异。 Just because forEach is native does not mean that it is faster than a simple loop built with for or while . 仅仅因为forEach本机并不意味着它比用forwhile构建的简单循环更快。 For one thing, the forEach has to deal with more special cases. 首先, forEach必须处理更多特殊情况。 Secondly, forEach uses callbacks, with the (potential) overhead of function invocation etc. 其次, forEach使用回调,具有函数调用的(潜在)开销等。

chrome in particular is known (at least to the lo-dash developers) to have a relatively slow forEach . chrome特别是已知的(至少到LO划线开发者)为具有相对慢forEach So for that browser, lo-dash uses it's own simple while loop to gain speed. 因此,对于该浏览器,lo-dash使用它自己的简单while循环来获得速度。 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. 通过巧妙地选择本机方法 - 如果已知在给定环境中快速使用本机实现 - Lo-Dash避免了与本机相关的性能成本和一致性问题。

Yes, lodash/underscore each don't even have same semantics as .forEach . 是的,lodash / underscore每个甚至都没有.forEach相同的语义。 There is a subtle detail that will make the function really slow unless the engine can check for sparse arrays without getters quickly. 除非引擎能够快速检查没有getter的稀疏数组,否则有一个细微的细节会使函数真正变慢。

This will be 99% spec compliant and runs at the same speed as lodash each in V8 for the common case: 这将符合99%规范,并且与Vod相同的速度运行,对于常见情况:

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. 引擎可以使用其内部来检测奇怪的数组,但V8不能。

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 这是一个更新的链接(大约2015年),显示了比较所有三个的性能差异, for(...)Array.forEach_.eachhttps_.each

Note: Put here since I didn't have enough points yet to comment on the accepted answer. 注意:放在这里,因为我没有足够的分数来评论接受的答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM