【问题标题】:Why is lodash.each faster than native forEach?为什么 lodash.each 比原生 forEach 快?
【发布时间】:2013-09-23 18:13:12
【问题描述】:

我试图找到在自己的范围内运行 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 快多少?巫术?黑魔法?

【问题讨论】:

标签: javascript performance lodash


【解决方案1】:

_.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 ...) 检查,这可能解释了性能差异。


如上面的 cmets 中所述,与原生 for 的差异主要是由测试中的附加函数查找引起的。使用此版本可获得更准确的结果:

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

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

【讨论】:

  • 谢谢。值得指出的是,没有闭包的for 并不完全等价。 for 块的内部没有自己的范围。
  • @MattZukowski 你原来的testcase for (...) {(function(item) {cb(item);})(a[ix]);} 就可以了。与您上面的评论相反,这不会为每次迭代产生相当大的开销。相关:stackoverflow.com/questions/17308446/…
  • Lo-Dash 通过将所有阵列视为密集阵列并从循环中吊出 .call 来提高速度。一些 JS 引擎也可能存在跨原生方法边界内联的问题,lodash 通过成为普通 JS 来避免这些问题。将所有数组视为密集的跨浏览器更一致,因为 IE undefined 之类的数组中的文字 [null, undefined, false] 视为一个洞并跳过它,而其他数组则不会。
  • 我在那个 jsperf 链接上得到了 404。
  • 我也得到了 404。有没有人看到结果,哪个更快?
【解决方案2】:

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

lo-dash 开发人员解释(此处和视频)原生 forEach 的相对速度因浏览器而异。仅仅因为forEach 是原生的并不意味着它比使用forwhile 构建的简单循环更快。一方面,forEach 必须处理更多特殊情况。其次,forEach 使用回调,具有(潜在的)函数调用开销等。

chrome 尤其是已知的(至少对于 lo-dash 开发人员而言)具有相对较慢的forEach。所以对于那个浏览器,lo-dash 使用它自己的简单while 循环来提高速度。因此,您看到了速度优势(但其他人没有)。

通过巧妙地选择原生方法——只使用原生的 如果已知它在给定环境中速度快,则实现 — Lo-Dash 避免了相关的性能成本和一致性问题 与当地人。

【讨论】:

    【解决方案3】:

    是的,lodash/underscore 甚至都没有与.forEach 相同的语义。除非引擎可以快速检查没有 getter 的稀疏数组,否则有一个细微的细节会使函数变得非常缓慢。

    这将符合 99% 的规范,并在常见情况下为 runs at the same speed as lodash each in V8

    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 不能。

    【讨论】:

      【解决方案4】:

      这是一个更新的链接(大约 2015 年),显示了比较所有三个 for(...)Array.forEach_.each 的性能差异: https://jsperf.com/native-vs-underscore-vs-lodash

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

      【讨论】:

      • 看来lodash.each 不再比forEach 快(Chrome 78, 2020)
      • Lodash 在我的机器上(i9-9900k cpu @ 5Ghz 8core 16 线程)仍然比原生 forEach 快,但是原生 for 循环是它们的两倍(所有这些都根据运行上面那个 URL 上的测试)。
      猜你喜欢
      • 2010-10-19
      • 2015-02-10
      • 2014-02-19
      • 1970-01-01
      • 1970-01-01
      • 2012-06-14
      • 1970-01-01
      • 1970-01-01
      • 2019-11-02
      相关资源
      最近更新 更多