Почему именно лодаш.каждый быстрее, чем родной forEach?


Я пытался найти самый быстрый способ запуска цикла 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]);
}

это на Chrome 29 на OS X. Вы можете запустить тесты самостоятельно здесь:

http://jsben.ch/BQhED

как лодашь по .each почти в два раза быстрее, чем родной .forEach? И более того, как это быстрее, чем обычный for? Колдовство? Черная магия?

4 58

4 ответа:

_.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/

поэтому реализация лодашь отсутствует 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

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

разработчики lo-dash объясняют (здесь и на видео), что относительная скорость родной forEach варьируется в разных браузерах. Просто потому что forEach родной не значит, что это быстрее, чем простой цикл, построенный с for или while. Во-первых,forEach имеет дело с более особых случаях. Во-вторых, forEach использует обратные вызовы, с (потенциальными) накладные расходы на вызов функции так далее.

chrome в частности, известно (по крайней мере, для разработчиков lo-dash), чтобы иметь относительно медленный forEach. Так что для этого браузера, lo-dash использует его собственный простой while петля для увеличения скорости. Следовательно, преимущество в скорости, которое вы видите (но другие этого не делают).

умно выбирая собственные методы-ТОЛЬКО используя родной реализация, если известно, что она быстрая в данной среде - Lo-Dash позволяет избежать проблем с производительностью и согласованностью связанный с аборигенами.

да, lodash / underscore каждый из них даже не имеет такой же семантики, как .forEach. Существует тонкая деталь, которая сделает функцию действительно медленной, если двигатель не сможет быстро проверить разреженные массивы без геттеров.

это будет 99% спецификации совместимый и работает с той же скоростью, что и lodash каждый в 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 не делает.

вот обновленная ссылка (около 2015 года), показывающая разницу в производительности, которая сравнивает все три,for(...),Array.forEach и _.each: https://jsperf.com/native-vs-underscore-vs-lodash

Примечание: поставьте здесь, так как у меня еще не было достаточно очков, чтобы прокомментировать принятый ответ.