Nous allons d'abord discuter de ce qu'il fait et pourquoi c'est plus rapide et puis pourquoi il fonctionne.
Ce qu'il fait
Le moteur V8 utilise l'objet de deux représentations:
-
Dictionnaire de la mode - dans laquelle les objets sont stockés en tant que clé - valeur des cartes comme un hachage de la carte.
-
Rapide mode - dans laquelle les objets sont stockés comme des structs, dans lequel il n'y a pas de calcul impliqués dans l'accès à la propriété.
Ici est une simple démo qui montre la différence de vitesse. Ici, nous utilisons l' delete
déclaration de force les objets lents dictionnaire de la mode.
Le moteur essaie d'utiliser le mode rapide chaque fois que possible et généralement toutes les fois que beaucoup de l'accès à la propriété est exécutée, cependant parfois il est jeté dans le dictionnaire de la mode. Étant dans le dictionnaire de la mode a une grande performance de sanction en général, il est souhaitable de placer les objets en mode rapide.
Ce hack est destiné à la force de l'objet en mode rapide à partir du dictionnaire de mode.
Pourquoi c'est plus rapide
En JavaScript prototypes stockent généralement des fonctions partagées entre de multiples instances et rarement beaucoup changer dynamiquement. Pour cette raison, il est très souhaitable de les avoir dans le mode rapide pour éviter la sanction à chaque fois qu'une fonction est appelée.
Pour ce v8 fera un plaisir de mettre des objets qui sont l' .prototype
de la propriété de fonctions dans le mode rapide, car ils seront partagés par tous les objets créés par l'invocation de la fonction en tant que constructeur. C'est généralement intelligent et souhaitable d'optimisation.
Comment ça marche
Nous allons tout d'abord passer par le code et la figure de ce que chaque ligne ne:
function toFastProperties(obj) {
/*jshint -W027*/ // suppress the "unreachable code" error
function f() {} // declare a new function
f.prototype = obj; // assign obj as its prototype to trigger the optimization
// assert the optimization passes to prevent the code from breaking in the future
// in case this optimization breaks. This requires the "native syntax" flag
ASSERT("%HasFastProperties", true, obj);
return f; // return it
eval(obj); // prevent the function from being optimized through dead code
// elimination or further optimizations. This code is never
// reached but even unreachable code causes v8 to not optimize functions.
}
Nous n' avons à trouver le code de nous-mêmes pour affirmer que les v8 ne cette optimisation, on peut, au lieu de lire le v8 tests unitaires:
// Adding this many properties makes it slow.
assertFalse(%HasFastProperties(proto));
DoProtoMagic(proto, set__proto__);
// Making it a prototype makes it fast again.
assertTrue(%HasFastProperties(proto));
De la lecture et de l'exécution de ce test nous montre que cette optimisation est en effet efficace en v8. Cependant, il serait agréable de voir comment.
Si nous vérifions objects.cc
, nous pouvons trouver la fonction suivante (L9925):
void JSObject::OptimizeAsPrototype(Handle<JSObject> object) {
if (object->IsGlobalObject()) return;
// Make sure prototypes are fast objects and their maps have the bit set
// so they remain fast.
if (!object->HasFastProperties()) {
MigrateSlowToFast(object, 0);
}
}
Maintenant, JSObject::MigrateSlowToFast
seulement prend explicitement le Dictionnaire et le convertit en un V8 ultra-rapide de l'objet. Il est intéressant de lire un aperçu intéressant de v8 objet internes - mais c'est pas le sujet ici. - Je encore vous le recommande chaudement de le lire ici que c'est un bon moyen d'en apprendre sur les v8 des objets.
Si nous découvrez SetPrototype
en objects.cc
, nous pouvons voir qu'il est appelé en ligne de 12231:
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
Qui à son tour est appelé par FuntionSetPrototype
qui est ce que nous obtenons avec .prototype =
.
Faire __proto__ =
ou .setPrototypeOf
aurait également travaillé mais ce sont ES6 fonctions et Bluebird fonctionne sur tous les navigateurs depuis Netscape 7, donc c'est hors de question pour simplifier le code ici. Par exemple, si nous vérifions .setPrototypeOf
, nous pouvons voir:
// ES6 section 19.1.2.19.
function ObjectSetPrototypeOf(obj, proto) {
CHECK_OBJECT_COERCIBLE(obj, "Object.setPrototypeOf");
if (proto !== null && !IS_SPEC_OBJECT(proto)) {
throw MakeTypeError("proto_object_or_null", [proto]);
}
if (IS_SPEC_OBJECT(obj)) {
%SetPrototype(obj, proto); // MAKE IT FAST
}
return obj;
}
Qui est directement sur Object
:
InstallFunctions($Object, DONT_ENUM, $Array(
...
"setPrototypeOf", ObjectSetPrototypeOf,
...
));
Ainsi - nous avons parcouru le chemin à partir du code de pet'ka a écrit pour le métal nu. C'était agréable.
Avertissement:
Rappelez-vous ceci est la mise en œuvre des détails. Des gens comme Petka sont l'optimisation des freaks. Rappelez-vous toujours que l'optimisation prématurée est la racine de tous les maux 97% du temps. Bluebird fait quelque chose de très basique, très souvent, donc il gagne beaucoup l'un de ces hacks - être aussi rapide que les rappels n'est pas facile. Vous rarement faire quelque chose comme ceci dans le code qui n'a pas la puissance d'une bibliothèque.