Почему именно лодаш.каждый быстрее, чем родной 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. Вы можете запустить тесты самостоятельно здесь:
как лодашь по .each
почти в два раза быстрее, чем родной .forEach
? И более того, как это быстрее, чем обычный for
? Колдовство? Черная магия?
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
поэтому реализация лодашь отсутствует
if (... in ...)
проверяем, что может объяснить разницу в производительности.
как отмечено в комментариях выше, разница в родной
for
в основном вызвано дополнительным поиском функции в вашем тесте. Используйте эту версию, чтобы получить более точный результаты:for (var ix = 0, len = a.length; ix < len; ix++) { cb(a[ix]); }
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Примечание: поставьте здесь, так как у меня еще не было достаточно очков, чтобы прокомментировать принятый ответ.