Pourquoi le nouveau est lent?

référence:

JsPerf

les invariants:

var f = function() { };

var g = function() { return this; }

les tests:

ci-dessous, par ordre de vitesse attendue

  • 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. quand vous changez f et g pour les fonctions anonymes en ligne. Pourquoi le new(essai 4. test plus lent?

mise à Jour:

Qu'est-ce qui cause spécifiquement le new être plus lent quand f et g sont inline.

Je m'intéresse aux références à la spécification ES5 ou aux références au code source JagerMonkey ou V8. (N'hésitez pas à relier JSC et le code source de Carakan. Oh, et l'équipe IE fuites de Chakra source si ils le souhaitent).

si vous liez une source de moteur JS, veuillez l'expliquer.

30
demandé sur Raynos 2011-06-22 17:28:36

5 réponses

le problème est que vous pouvez inspecter le code source de divers moteurs, mais il ne vous aidera pas beaucoup. N'essayez pas d'être plus malin que le compilateur. Ils vont essayer d'optimiser pour l'usage le plus commun de toute façon. Je ne pense pas que (function() { return this; }).call(Object.create(Object.prototype)) appelé 1 000 fois a un vrai cas d'utilisation du tout.

" les programmes doivent être écrits pour que les gens puissent les lire, et seulement accessoirement pour que les machines puissent les exécuter."

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

5
répondu galambalazs 2011-06-24 21:02:19

la principale différence entre le #4 et tous les autres cas est que la première fois que vous utilisez une fermeture comme constructeur est toujours très chère.

  1. il est toujours manipulé dans V8 runtime (pas dans le code généré) et la transition entre le code JS compilé et C++ runtime est très chère. Les attributions subséquentes sont habituellement traitées en code généré. Vous pouvez prendre un coup d'oeil à Generate_JSConstructStubHelperbuiltins-ia32.cc et un avis qui passe à travers les Runtime_NewObject lors de la fermeture n'a pas de initiale de la carte. (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 (alias hidden class) et l'assigner comme 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 attribuées dans un espace mémoire séparé. Cet espace ne peut pas être nettoyé par un jeûne partiel récupérer collecteur. Quand l'espace de carte déborde V8 doit effectuer relativement cher plein mark-sweep GC.

il y a quelques autres choses qui se produisent quand vous utilisez la fermeture comme constructeur pour la première fois mais 1 et 2 sont les principaux contributeurs à la lenteur du test case #4.

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

  • #1 n'affecte pas une nouvelle fermeture de tous les temps;
  • #1 n'entre pas l'exécution à chaque fois: après la fermeture obtient la construction de la carte initiale est traitée dans le chemin rapide du code généré. Gérer toute la construction en code généré est beaucoup plus rapide que de faire des allers - retours entre l'exécution et le code généré;
  • #1 n'attribue pas de nouvelle carte initiale pour chaque nouvelle fermeture à chaque fois;
  • #1 ne cause pas une marque de balayages par débordement de la carte de l'espace (seulement bon marché piège).

si nous comparons #3 et #4, alors les différences sont:

  • #3 n'attribue pas de nouvelle carte initiale pour chaque nouvelle fermeture à chaque fois;
  • n ° 3 n'entraîne pas une marque de balayages par débordement de la carte de l'espace (seulement bon marché, piège);
  • #4 fait moins du côté de JS (aucune fonction.prototype.d'appel, pas d'Objet.créer, pas d'Objet.prototype lookup etc) plus sur le côté C++ (#3 entre aussi runtime chaque fois que vous faites objet.crée mais fait très peu y.)

en résumé, la première fois que vous utilisez closure en tant que constructeur est coûteuse par rapport aux appels de construction ultérieurs du fermeture parce que V8 doit installer de la plomberie. Si nous rejetons immédiatement la fermeture, nous rejetons en gros tout le travail que V8 a fait pour accélérer les appels subséquents du constructeur.

15
répondu Vyacheslav Egorov 2018-10-01 09:40:21

je suppose que les points suivants expliquent ce qui se passe en V8:

  1. t(exp1) : t(Création de l'Objet)
  2. t (exp2): t(création D'objet par objet.créer())
  3. t (exp3): t(création D'objet par objet.créer()) + t(Fonction de Création de l'Objet)
  4. t(exp4) : t(Création de l'Objet) + t(Fonction de Création de l'Objet) + t(Classe de Création de l'Objet)[Chrome]

    • pour les Classes cachées dans Chrome regardez : http://code.google.com/apis/v8/design.html.
    • quand un nouvel objet est créé par objet.créer aucun nouvel objet de classe ne doit être créé. Il y a déjà un qui est utilisé pour les Objets littéraux et pas besoin d'une nouvelle Classe.
3
répondu salman.mirghasemi 2011-06-24 14:23:34

Eh bien, ces deux appels ne font pas exactement la même chose. Considérons cet exemple:

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

donc nous pouvons voir que l'appel Rock.call(otherRock) ne provoque pas de otherRock hériter du prototype. Cela doit expliquer au moins une partie de la lenteur ajoutée. Bien que dans mes tests, le new construction est presque 30 fois plus lente, même dans cet exemple simple.

0
répondu benekastah 2011-06-23 15:41:21
new f;
  1. prendre la fonction locale ' f '(accès par index dans le cadre local) - pas cher.
  2. exécuter bytecode BC_NEW_OBJECT (ou quelque chose comme ça) - pas cher.
  3. exécutez la fonction - cheap ici.

Maintenant ceci:

g.call(Object.create(Object.prototype));
  1. trouver global var Object - pas cher?
  2. Trouver la propriété prototype dans l'Objet - so-so
  3. Trouver la propriété create dans l'Objet - so-so
  4. trouver la var g locale; - pas cher
  5. Trouver la propriété call - so-so
  6. Appeler create fonction - so-so
  7. Appeler call fonction - so-so

Et ça:

new (function() { })
  1. créer un nouvel objet de fonction (fonction anonyme) - relativement cher.
  2. exécuter bytecode BC_NEW_OBJECT - pas cher
  3. exécutez la fonction - cheap ici.

comme vous le voyez, le cas n ° 1 est celui qui consomme le moins.

0
répondu c-smile 2011-06-24 21:26:34