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


74

我试图找到在自己的范围内运行for循环的最快方法。我比较的三种方法是:

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]);
}

这是在OS X上的Chrome 29上。您可以在此处自己运行测试:

http://jsben.ch/BQhED

lodash的.each速度几乎是本地的两倍.forEach?而且,它比平原快for吗?巫术?黑魔法?



1
lambda是为了什么?为什么不简单地cb直接放?
Bergi 2013年

1
.each()对我来说,Lo-Dash的速度比您测试中的任何其他方法都要慢得多。FF 23.0.1
乔·西蒙斯

1
.each()和之间的区别for来自附加功能查找(lambda)。有关更有意义的基准,请参见jsperf.com/lo-dash-each-vs-native-foreach/15
user123444555621 2013年

1
我向您的测试中添加了10000个元素,并且lodash现在都比本地的foreach慢:jsben.ch/RBkjH
kevinl

Answers:


96

_.each()与并不完全兼容[].forEach()。请参见以下示例:

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/

因此lodash的实现缺少if (... in ...)检查,这可能解释了性能差异。


如上面的注释中所述,与本机的差异for主要是由您的测试中的附加功能查找引起的。使用此版本可获得更准确的结果:

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

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


1
感谢那。值得指出的是,for没有闭包并不完全等效。for块的内部没有自己的作用域。
Matt Zukowski

1
@MattZukowski您原来的测试用例 for (...) {(function(item) {cb(item);})(a[ix]);}就可以了。与您上面的评论相反,这不会为每次迭代带来可观的开销。相关阅读:stackoverflow.com/questions/17308446/...
user123444555621

4
Lo-Dash通过将所有阵列视为密集阵列并从循环中提升.call来提高速度。一些JS引擎可能还存在跨本地方法边界内联的问题,而lodash通过使用纯JS避免了这种问题。将所有数组都视为密集数组是跨浏览器更一致的浏览器,因为IE <9会将undefined数组中的文字值[null, undefined, false]当作一个空洞对待,并跳过它,而其他则不然。
John-David Dalton

24
我在该jsperf链接上收到404 。
hpaulj 2013年

1
我也收到404。有人看到结果了吗?
PrimeLens

24

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

破折号的开发人员解释(在此处和在视频上),本机的相对速度forEach在浏览器之间有所不同。仅仅因为forEach是native并不意味着它比用foror构建的简单循环要快while。一方面,forEach必须处理更多特殊情况。其次,forEach使用回调,以及函数调用的(潜在)开销等。

chrome特别是(至少对于破折号开发人员而言)具有相对较慢的速度 forEach。因此,对于该浏览器,lo-dash使用它自己的简单while循环来提高速度。因此,您所看到的速度优势(其他人却没有)。

通过明智地采用本机方法-仅在已知的给定环境中可以使用本机实现的情况下,才使用本机实现-Lo-Dash避免了与本机相关的性能成本和一致性问题。


18

是的,lodash /下划线每个甚至都不具有相同的语义 .forEach。有一个细微的细节将使函数真正变慢,除非引擎可以快速地检查稀疏数组而没有吸气剂。

在常见情况下,这将符合99%规范,并以与V8中lodash相同的速度运行

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;

通过先检查未定义的数组,我们根本不会惩罚循环中的普通数组。引擎可以使用其内部来检测奇怪的数组,但是V8不能。


6

这是一个更新的链接(大约在2015年),其中显示了性能差异,将所有三个for(...)Array.forEach和进行比较_.eachhttps : //jsperf.com/native-vs-underscore-vs-lodash

注意:放到这里,因为我还没有足够的意见来评论接受的答案。


2
因此,似乎lodash.each不再比forEach(Chrome
78,2020

Lodash在我的机器(i9-9900k cpu @ 5Ghz 8core 16线程)上仍然比本机forEach更快,但是本机for循环的速度是它们两者的两倍(所有这些都取决于在上述URL上运行测试)。
agm1984年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.