Почему именно лодаш.каждый быстрее, чем родной 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Примечание: поставьте здесь, так как у меня еще не было достаточно очков, чтобы прокомментировать принятый ответ.