J'essayais de trouver le moyen le plus rapide d'exécuter une boucle for avec sa propre portée. Les trois méthodes que j'ai comparées étaient:
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]);
}
C'est sur Chrome 29 sur OS X. Vous pouvez exécuter les tests vous-même ici:
Comment est le lodash .each
presque deux fois plus vite que le natif .forEach
? Et de plus, comment est-il plus rapide que la simple for
? Sorcellerie? Magie noire?
_.each()
n'est pas totalement compatible avec [].forEach()
. Voir l'exemple suivant:
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
Il manque donc à la mise en oeuvre de lodash un contrôle if (... in ...)
, qui pourrait expliquer la différence de performances.
Comme indiqué dans les commentaires ci-dessus, la différence par rapport à for
native est principalement due à la recherche de fonction supplémentaire dans votre test. Utilisez cette version pour obtenir des résultats plus précis:
for (var ix = 0, len = a.length; ix < len; ix++) {
cb(a[ix]);
}
http://kitcambridge.be/blog/say-hello-to-lo-dash/
Les développeurs de bas niveau expliquent (ici et sur une vidéo) que la vitesse relative du forEach
natif varie selon les navigateurs. Le fait que forEach
soit natif ne signifie pas qu’elle est plus rapide qu’une simple boucle construite avec for
ou while
. D'une part, le forEach
doit traiter davantage de cas particuliers. Deuxièmement, forEach
utilise des rappels, avec la surcharge (potentielle) d'invocation de fonction, etc.
chrome
en particulier est connu (du moins pour les développeurs de bas de tableau) pour avoir un forEach
relativement lent. Donc, pour ce navigateur, lo-dash utilise sa propre simple boucle while
pour gagner de la vitesse. D'où l'avantage de vitesse que vous voyez (mais que d'autres ne voient pas).
En optant intelligemment pour les méthodes natives (en n’utilisant une implémentation native que si elle est connue pour sa rapidité dans un environnement donné), Lo-Dash évite les problèmes de coût de performance et de cohérence associés aux natifs.
Oui, lodash/underscore n'ont même pas la même sémantique que .forEach
. Il existe un détail subtil qui rendra la fonction très lente à moins que le moteur ne puisse vérifier rapidement les tableaux épars sans getters.
Ce sera conforme aux spécifications à 99% et s'exécute à la même vitesse que lodash dans V8 pour le cas commun:
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;
En vérifiant d'abord non défini, nous ne punissons pas du tout les tableaux normaux de la boucle. Un moteur peut utiliser ses composants internes pour détecter des tableaux étranges, mais pas le V8.
Voici un lien mis à jour (vers 2015) montrant la différence de performance qui compare les trois, for(...)
, Array.forEach
et _.each
: https://jsperf.com/native-vs-underscore-vs-lodash
Note: Mettez ici car je n'avais pas encore assez de points pour commenter la réponse acceptée.