web-dev-qa-db-fra.com

Pourquoi lodash.each est-il plus rapide que le format natif pour chaque?

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:

http://jsben.ch/BQhED

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?

64
Matt Zukowski

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

http://jsfiddle.net/BhrT3/

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://jsperf.com/lo-dash-each-vs-native-foreach/15

87
user123444555621

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.

23
hpaulj

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.

16
Esailija

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.

3
chunk_split