31 votes

Pourquoi les nouvelles sont-elles lentes ?

Le point de référence :

JsPerf

Les invariants :

var f = function() { };

var g = function() { return this; }

Les tests :

Ci-dessous, dans l'ordre de la vitesse prévue

  • new f;
  • g.call(Object.create(Object.prototype));
  • new (function() { })
  • (function() { return this; }).call(Object.create(Object.prototype));

Vitesse réelle :

  1. new f;
  2. g.call(Object.create(Object.prototype));
  3. (function() { return this; }).call(Object.create(Object.prototype));
  4. new (function() { })

La question :

  1. Lorsque vous échangez f y g pour les fonctions anonymes en ligne. Pourquoi le new (test 4.) test plus lent ?

Mise à jour :

Qu'est-ce qui cause spécifiquement le new pour être plus lent lorsque f y g sont soulignés.

Je suis intéressé par les références à la spécification ES5 ou les références au code source de JagerMonkey ou V8. (N'hésitez pas à faire des liens vers le code source de JSC et Carakan également. Oh et l'équipe IE peut faire fuir le code source de Chakra si elle le souhaite).

Si vous faites un lien vers une source de moteur JS, veuillez l'expliquer.

3 votes

Par curiosité, quel est l'intérêt de l'utilisation de Object.create(Object.prototype) plutôt que d'utiliser un objet littéral ({}) ? Ne sont-elles pas exactement les mêmes ? Cela pourrait-il être la source d'une certaine différence de performance ?

0 votes

@maerics Je me suis senti Object.create(object.prototype) était plus dans "l'esprit" de new . Il semble que {} est plus rapide dans Chrome, plus lente dans FF et à peu près la même chose dans IE.

0 votes

Bien que je puisse vaguement voir pourquoi Object.create(Object.prototype) est plus dans l'esprit de new Je serais intéressé de voir un exemple qui essaie de construire plus qu'un simple objet vide afin de confirmer cela.

18voto

Vyacheslav Egorov Points 4198

La principale différence entre le cas n° 4 et tous les autres est que la première fois que vous utilisez une fermeture comme constructeur est toujours assez coûteuse.

  1. Elle est toujours traitée dans le runtime V8 (et non dans le code généré) et la transition entre le code JS compilé et le runtime C++ est assez coûteuse. Les allocations ultérieures sont généralement traitées dans le code généré. Vous pouvez jeter un coup d'oeil à Generate_JSConstructStubHelper en builtins-ia32.cc et remarquez qu'il tombe dans le Runtime_NewObject lorsque la fermeture n'a pas de carte initiale. (voir http://code.google.com/p/v8/source/browse/trunk/src/ia32/builtins-ia32.cc#138 )

  2. Lorsque closure est utilisé comme constructeur pour la première fois, V8 doit créer une nouvelle carte (aka classe cachée) et l'assigner en tant que carte initiale pour cette fermeture. Voir http://code.google.com/p/v8/source/browse/trunk/src/heap.cc#3266 . Ce qui est important ici, c'est que les cartes sont allouées dans un espace mémoire séparé. Cet espace ne peut pas être nettoyé par une opération partielle rapide. fouiller collecteur. Lorsque l'espace de la carte déborde, V8 doit effectuer des opérations complètes relativement coûteuses. balayage des marques GC.

Il y a quelques autres choses qui se produisent lorsque vous utilisez la fermeture comme constructeur pour la première fois mais 1 et 2 sont les principaux contributeurs à la lenteur du scénario de test #4.

Si nous comparons les expressions #1 et #4 alors les différences sont :

  • 1 n'alloue pas une nouvelle fermeture à chaque fois ;

  • Le numéro 1 n'entre pas dans le temps d'exécution à chaque fois : après la fermeture, la construction de la carte initiale est traitée dans le chemin rapide du code généré. Gérer l'ensemble de la construction dans le code généré est beaucoup plus rapide que de faire des allers-retours entre le runtime et le code généré ;

  • 1 n'alloue pas à chaque fois une nouvelle carte initiale pour chaque nouvelle fermeture ;

  • Le numéro 1 ne provoque pas de balayage en débordant de l'espace de la carte (seulement des récupérations bon marché).

Si nous comparons les numéros 3 et 4, les différences sont les suivantes :

  • 3 n'alloue pas à chaque fois une nouvelle carte initiale pour chaque nouvelle fermeture ;

  • Le n°3 ne provoque pas de balayage en débordant de l'espace de la carte (seulement des récupérations bon marché) ;

  • Le n°4 fait moins sur le plan JS (pas de Function.prototype.call, pas d'Object.create, pas de Object.prototype lookup, etc.) et plus sur le plan C++ (le n°3 entre également dans le runtime à chaque fois que vous faites Object.create, mais ne fait pas grand-chose).

L'essentiel ici est que la première fois que vous utilisez la fermeture comme constructeur est coûteuse par rapport aux appels de construction ultérieurs de l'option même fermeture parce que V8 doit installer de la plomberie. Si nous éliminons immédiatement la fermeture, nous jetons tout le travail que V8 a fait pour accélérer les appels de constructeurs ultérieurs.

5voto

galambalazs Points 24393

Le problème est que vous pouvez inspecter le actuel le code source de divers moteurs, mais cela ne vous aidera pas beaucoup. N'essayez pas d'être plus malin que le compilateur. Il essaiera de toute façon d'optimiser pour l'utilisation la plus courante. Je ne pense pas que (function() { return this; }).call(Object.create(Object.prototype)) appelé 1 000 fois n'a pas du tout de réel cas d'utilisation.

"Les programmes doivent être écrits pour que les gens les lisent, et seulement accessoirement pour que les machines les exécutent."

Abelson & Sussman, SICP, préface de la première édition.

0 votes

Je sais. new est plus rapide que Object.create dans presque tous les cas. Je suis curieux de savoir pourquoi ce n'est pas le cas pour cette affaire particulière. Je ne suggère pas que je vais utiliser new plus ou moins en fonction de la réponse. J'écris du code lisible et maintenable.

0 votes

Oui, mais si vous n'avez pas de réel cas d'utilisation ou un problème de performance, qu'est-ce que ça donne ? Je veux dire que vous ne devriez pas créer 1 000 fonctions à la volée avec new (function(){}) soit. Comparer des modèles utilisables avec des modèles inutilisables ne présente aucun avantage réel.

2 votes

La curiosité prend toujours le dessus sur moi.

3voto

salman.mirghasemi Points 459

Je suppose que les extensions suivantes expliquent ce qui se passe dans la V8 :

  1. t(exp1) : t(Création d'objet)
  2. t(exp2) : t(Création d'objet par Object.create())
  3. t(exp3) : t(Création d'objet par Object.create()) + t(Fonction Création d'objet)
  4. t(exp4) : t(Création d'objet) + t(Création d'objet de fonction) + t(Création d'objet de classe) [En chrome]

    • Pour les classes cachées dans Chrome, voir : http://code.google.com/apis/v8/design.html .
    • Quand un nouvel objet est créé par Object.create, aucun nouvel objet de classe ne doit être créé. Il en existe déjà un qui est utilisé pour les littéraux de l'objet et il n'est pas nécessaire de créer une nouvelle classe.

0 votes

Il n'y a pas Class en JavaScript. Je comprends toute cette logique. La seule chose qui me perturbe est que, étant donné que t(exp1) < t(exp2), pourquoi t(exp4) N'EST PAS < t(exp3) ?

0 votes

Chrome fait des Classes (cachées à l'utilisateur, dans leur moteur JS), je vous renvoie à leurs documents. J'ai mis à jour ma réponse.

0 votes

@salman.mirghasemi, vous allez devoir faire marche arrière. Class Object creation avec des extraits de la source V8. Vous allez aussi devoir sauvegarder pourquoi new a la création d'objets de classe et Object.create() ne le fait pas. Vous allez aussi devoir expliquer pourquoi new est plus lent en este cas spécifique (3 & 4) par rapport à chaque autre cas.

0voto

benekastah Points 2602

Eh bien, ces deux appels ne font pas exactement la même chose. Considérez ce cas :

var Thing = function () {
  this.hasMass = true;
};
Thing.prototype = { holy: 'object', batman: '!' };
Thing.prototype.constructor = Thing;

var Rock = function () {
  this.hard = 'very';
};
Rock.prototype = new Thing();
Rock.constructor = Rock;

var newRock = new Rock();
var otherRock = Object.create(Object.prototype);
Rock.call(otherRock);

newRock.hard // => 'very'
otherRock.hard // => 'very'
newRock.hasMass // => true
otherRock.hasMass // => undefined
newRock.holy // => 'object'
otherRock.holy // => undefined
newRock instanceof Thing // => true
otherRock instanceof Thing // => false

On peut donc voir qu'appeler Rock.call(otherRock) ne provoque pas otherRock pour hériter du prototype. Cela doit expliquer au moins une partie de la lenteur supplémentaire. Bien que dans mes tests, le new La construction est presque 30 fois plus lente, même dans cet exemple simple.

0 votes

Bien sûr que non. Vous devez appeler Object.create(Rock.prototype) Parce que je créais des objets que j'appelais Object.prototype avant.

0 votes

Oh, c'est plus logique. Désolé, je n'ai pas lu assez attentivement !

0 votes

Hmm... Maintenant je me demande ce que Object.create() fait différemment ?

0voto

c-smile Points 8609
new f;
  1. prendre la fonction locale 'f' (accès par index dans le cadre local) - bon marché.
  2. exécuter le bytecode BC_NEW_OBJECT (ou quelque chose comme ça) - pas cher.
  3. Exécuter la fonction - bon marché ici.

Maintenant, ceci :

g.call(Object.create(Object.prototype));
  1. Trouver la var globale Object - bon marché ?
  2. Trouver un bien prototype en objet - moyen
  3. Trouver un bien create en objet - moyen
  4. Trouver une var g locale ; - bon marché
  5. Trouver un bien call - moyennement
  6. Appeler create Fonction - moyennement
  7. Appeler call Fonction - moyennement

Et ceci :

new (function() { })
  1. crée un nouvel objet fonction (cette fonction anonyme) - relativement coûteux.
  2. exécuter le bytecode BC_NEW_OBJECT - bon marché
  3. Exécuter la fonction - bon marché ici.

Comme vous le voyez, le cas n°1 est le moins consommateur.

0 votes

Ce qui est intéressant, c'est que le numéro 1 est plus rapide que le numéro 2. Mais en remplaçant f et g par des fonctions anonymes, le numéro 3 est plus rapide que le numéro 4, ce que je ne comprends pas.

0 votes

Pas de surprise, g.call(Object.create(Object.prototype)); c'est beaucoup d'opérations. Deux invocations de fonctions au lieu de deux. Deux recherches supplémentaires de propriétés + recherche globale d'objets. Vous pouvez essayer de mettre en cache le résultat de Object.prototype dans une variable locale.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X