Maison  >  Article  >  interface Web  >  Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

coldplay.xixi
coldplay.xixiavant
2020-12-08 17:10:562851parcourir

javascriptLa colonne présente le moteur V8 en profondeur et l'écriture de code optimisé

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Recommandations d'apprentissage gratuites associées : javascript( Vidéo)

Présentation

Un moteur JavaScript est un programme ou un interpréteur qui exécute du code JavaScript. Un moteur JavaScript peut être implémenté comme un interpréteur standard ou comme une forme de compilateur juste à temps qui compile JavaScript en bytecode.

Liste des projets populaires qui implémentent des moteurs JavaScript :

  • V8 — Open source, développé par Google, écrit en C++
  • Rhino — Géré par la Fondation Mozilla, open source et développé entièrement en Java
  • SpiderMonkey — Il s'agit du premier moteur JavaScript à prendre en charge Netscape Navigator et est actuellement utilisé par Firefox
  • JavaScriptCore — Open source, vendu sous le nom de Nitro, développé par Apple pour Safari
  • KJS — Un moteur pour KDE, développé à l'origine par Harri Porten pour le développement du navigateur Web KDE Konqueror dans le projet
  • Chakra (JScript9) — Internet Explorer
  • Chakra (JavaScript) Microsoft Edge
  • Nashorn, écrit par Oracle Java Language and Tools Group dans le cadre d'OpenJDK
  • JerryScript — Un moteur léger pour l'Internet des objets

Pourquoi le moteur V8 a-t-il été créé ?

Le moteur V8 construit par Google est open source et écrit en C++. Ce moteur est utilisé dans Google Chrome, cependant, contrairement à d'autres moteurs, le V8 est également utilisé dans le populaire Node.js.
Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

La V8 a été initialement conçue pour améliorer les performances d'exécution de JavaScript dans les navigateurs Web. Pour gagner en vitesse, V8 convertit le code JavaScript en code machine plus efficace au lieu d'utiliser un interpréteur. Il compile le code JavaScript en code machine au moment de l'exécution en implémentant un compilateur JIT (Just-In-Time), tout comme le font de nombreux moteurs JavaScript modernes tels que SpiderMonkey ou Rhino (Mozilla). La principale différence ici est que V8 ne génère pas de bytecode ni de code intermédiaire.

Le V8 avait deux compilateurs

Avant la sortie de la version 5.9 du V8, le moteur V8 utilisait deux compilateurs :

  • full-codegen — un simple et compilateur très rapide qui produit un code machine simple et relativement lent.
  • Vilebrequin — Un compilateur d'optimisation plus sophistiqué (juste à temps) qui génère du code hautement optimisé.

Le moteur V8 utilise également plusieurs threads en interne :

  • Le thread principal fait ce à quoi vous vous attendez : récupère le code, le compile et l'exécute
  • Il existe également un thread séparé pour la compilation, afin que le thread principal puisse continuer à s'exécuter pendant que le premier optimise le code
  • Un thread Profiler qui indique au runtime que nous avons passé beaucoup de temps afin que Crankshaft puisse les optimiser
  • Certains threads gèrent le garbage collector

Lorsque le code JavaScript est exécuté pour la première fois, V8 utilise le compilateur full-codegen pour traduire directement le JavaScript analysé en code machine sans aucune conversion. Cela lui permet de commencer à exécuter du code machine très rapidement. Notez que la V8 n'utilise pas de bytecode intermédiaire, éliminant ainsi le besoin d'un interprète.

Lorsque le code est exécuté depuis un certain temps, le fil d'analyse a collecté suffisamment de données pour déterminer quelle méthode doit être optimisée.

Ensuite, Vilebrequin démarre l'optimisation à partir d'un autre fil de discussion. Il convertit les arbres de syntaxe abstraite JavaScript en une représentation statique à allocation unique (SSA) de haut niveau appelée Hydrogen et tente d'optimiser le graphe Hydrogen, la plupart des optimisations sont effectuées à ce niveau.

Code en ligne

La première optimisation consiste à intégrer autant de code que possible à l'avance. L'inlining est le processus de remplacement du site d'appel (la ligne de code qui appelle la fonction) par le corps de la fonction appelée. Cette étape simple permet aux optimisations suivantes de prendre plus de sens.

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Classes cachées

JavaScript est un langage basé sur des prototypes : les classes et les objets ne sont pas créés à l'aide d'un processus de clonage. JavaScript est également un langage de programmation dynamique, ce qui signifie que des propriétés peuvent être facilement ajoutées ou supprimées d'un objet après instanciation.

La plupart des interpréteurs JavaScript utilisent une structure de type dictionnaire (basée sur une fonction de hachage) pour stocker l'emplacement des valeurs de propriété d'objet en mémoire. Cette structure rend la récupération des valeurs de propriété en JavaScript plus rapide qu'en Java ou. C# Les coûts de calcul sont plus élevés dans les langages de programmation non dynamiques.

En Java, toutes les propriétés des objets sont déterminées par une disposition d'objet fixe avant la compilation et ne peuvent pas être ajoutées ou supprimées dynamiquement au moment de l'exécution (bien sûr, C# a un typage dynamique, ce qui est un autre sujet).

Ainsi, les valeurs d'attribut (ou les pointeurs vers ces attributs) peuvent être stockées en mémoire sous forme de tampons contigus, avec des décalages fixes entre chaque tampon. La longueur du décalage peut être facilement déterminée en fonction du type d'attribut, tandis qu'en vous. peut modifier le type de propriété au moment de l'exécution, ce qui n'est pas possible en JavaScript.

Comme utiliser un dictionnaire pour trouver l'emplacement des propriétés d'un objet en mémoire est très inefficace, V8 utilise une approche différente : les classes cachées. Les classes cachées fonctionnent de la même manière que les objets fixes (classes) utilisés dans des langages comme Java, sauf qu'ils sont créés au moment de l'exécution. Maintenant, regardons leur exemple réel :

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Une fois l'appel "new Point(1, 2)" effectué, V8 créera un type caché nommé "C0".

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Aucune propriété n'a été définie pour Point, donc 'C0' est vide.

Une fois la première instruction "this.x = x" exécutée (au sein de la fonction "Point"), V8 créera une deuxième classe cachée appelée "C1", qui est basée sur "C0" . "C1" décrit l'emplacement en mémoire (par rapport au pointeur d'objet) où la propriété x peut être trouvée.

Dans ce cas, "x" est stocké au décalage 0, ce qui signifie que lorsque l'on considère l'objet point en mémoire comme un tampon contigu, le premier décalage correspondra à l'attribut "x" . La V8 mettra également à jour "C0" avec une "transformation de classe" qui stipule que si l'attribut "x" est ajouté à l'objet point, la classe cachée doit passer de "C0" à "C1". La classe cachée de l'objet point ci-dessous est désormais "C1".

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Chaque fois qu'une nouvelle propriété est ajoutée à un objet, l'ancienne classe cachée est mise à jour avec un chemin de transformation pointant vers la nouvelle classe cachée. Les conversions de classes cachées sont importantes car elles permettent de partager des classes cachées entre des objets créés de la même manière. Si deux objets partagent une classe cachée et que la même propriété leur est ajoutée, la transformation garantira que les deux objets reçoivent la même nouvelle classe cachée et tout le code d'optimisation qui l'accompagne.

Le même processus est répété lorsque l'instruction "this.y = y" est exécutée (à l'intérieur de la fonction "Point", après l'instruction "this.x = x").

Une nouvelle classe cachée nommée "C2" sera créée. Si une propriété "y" est ajoutée à un objet Point (qui contient déjà la propriété "x"), une classe cast sera ajoutée à "C1". " , alors la classe cachée doit être remplacée par "C2" et la classe cachée de l'objet ponctuel doit être mise à jour par "C2".

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

La conversion de classe cachée dépend de l'ordre dans lequel les propriétés sont ajoutées à l'objet. Jetez un œil à l'extrait de code suivant :

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Supposons maintenant que pour p1 et p2, la même classe cachée et la même transformation seront utilisées. Ainsi, pour "p1", ajoutez d'abord l'attribut "a", puis ajoutez l'attribut "b". Cependant, "p2" se voit d'abord attribuer "b", puis "a". Par conséquent, « p1 » et « p2 » se retrouvent avec des catégories cachées différentes en raison de chemins de transformation différents. Dans ce cas, il est préférable d'initialiser les propriétés dynamiques dans le même ordre afin que la classe cachée puisse être réutilisée.

Mise en cache en ligne

V8 utilise une autre technique pour optimiser les langages typés dynamiquement, appelée mise en cache en ligne. La mise en cache en ligne repose sur l'observation selon laquelle les appels répétés à la même méthode ont tendance à se produire sur des objets du même type. Une explication détaillée de la mise en cache en ligne peut être trouvée ici.

Le concept général de la mise en cache en ligne sera abordé ensuite (si vous n'avez pas le temps d'examiner en profondeur ce qui précède).

Alors, comment ça marche ? La V8 maintient un cache des types d'objets passés en arguments dans les appels de méthode récents et utilise ces informations pour prédire les types d'objets passés en arguments à l'avenir. Si V8 peut prédire suffisamment bien le type d'objet transmis à une méthode, il peut contourner le processus d'accès aux propriétés de l'objet et utiliser à la place les informations stockées lors des recherches précédentes dans la classe cachée de l'objet.

Alors, quel est le lien entre les concepts de classes cachées et de mise en cache en ligne ? Chaque fois qu'une méthode est appelée sur un objet spécifique, le moteur V8 doit effectuer une recherche dans la classe cachée de cet objet pour déterminer le décalage auquel accéder à la propriété spécifique. Après deux appels réussis à la même classe cachée, V8 omet la recherche de la classe cachée et ajoute simplement le décalage de la propriété au pointeur d'objet lui-même. Pour tous les prochains appels à cette méthode, le moteur V8 suppose que la classe cachée n'a pas changé et passe directement à l'adresse mémoire de la propriété spécifique en utilisant le décalage stocké lors de la recherche précédente. Cela améliore considérablement la vitesse d’exécution.

La mise en cache en ligne est également la raison pour laquelle il est important que les objets du même type partagent des classes cachées. Si vous créez deux objets du même type et des classes cachées différentes (comme nous l'avons fait dans notre exemple précédent), V8 ne pourra pas utiliser la mise en cache en ligne car même si les deux objets sont du même type, leurs classes cachées correspondantes sont ses les propriétés se voient attribuer différents décalages.

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Les deux objets sont fondamentalement les mêmes, mais les propriétés "a" et "b" sont créées dans un ordre différent.

Compiler en code machine

Une fois le graphique de l'hydrogène optimisé, Vilebrequin le réduit à une représentation de niveau inférieur appelée Lithium. La plupart des implémentations de Lithium sont spécifiques à l'architecture. L'attribution des registres se produit souvent à ce niveau.

Enfin, Lithium est compilé en code machine. Ensuite, il y a OSR : remplacement sur la pile. Avant de commencer à compiler et à optimiser une méthode explicite de longue durée, nous pouvons exécuter le remplacement de pile. Le V8 ne se contente pas d'effectuer lentement le remplacement de la pile et de recommencer l'optimisation. Au lieu de cela, il convertit tout le contexte dont nous disposons (pile, registres) pour passer à la version optimisée lors de l'exécution. Il s'agit d'une tâche très complexe, étant donné que, entre autres optimisations, la V8 intègre initialement le code. Le V8 n’est pas le seul moteur capable de le faire.

Il existe une mesure de sécurité appelée désoptimisation qui effectue la conversion inverse et renvoie du code non optimisé en supposant que le moteur n'est pas valide.

Collecte des ordures

Pour la collecte des ordures, V8 utilise l'algorithme traditionnel de marquage et de balayage pour nettoyer l'ancienne génération. La phase de marquage doit arrêter l'exécution de JavaScript. Pour contrôler les coûts du GC et rendre l'exécution plus stable, V8 utilise un marquage incrémentiel : au lieu de parcourir tout le tas et d'essayer de marquer tous les objets possibles, il parcourt simplement une partie du tas puis reprend l'exécution normale. Le prochain arrêt du GC continuera là où le tas précédent s'est arrêté, ce qui permet une très brève pause pendant l'exécution normale, comme mentionné précédemment, la phase d'analyse est gérée par un thread séparé.

Comment écrire du JavaScript optimisé

  1. Ordre des propriétés des objets : instanciez toujours les propriétés des objets dans le même ordre afin que les classes cachées et les optimisations ultérieures puissent être partagées en code .
  2. Propriétés dynamiques Parce que l'ajout d'une propriété à un objet après l'instanciation forcera un changement de classe cachée et ralentira l'exécution de toutes les méthodes précédemment optimisées par la classe cachée, il est important de All object les propriétés sont attribuées dans le constructeur.
  3. Méthodes : le code qui exécute la même méthode à plusieurs reprises s'exécutera plus rapidement que le code qui n'exécute qu'une seule fois plusieurs méthodes différentes (en raison de la mise en cache en ligne).
  4. Array : évitez les tableaux clairsemés où les valeurs clés ne sont pas des nombres auto-croissants et les tableaux clairsemés qui ne stockent pas tous les éléments sont des tables de hachage. L’accès aux éléments de tels tableaux coûte cher. Essayez également d’éviter de préallouer de grands tableaux. Mieux vaut croître à la demande. Enfin, ne supprimez pas d’éléments du tableau, car cela rendrait les clés clairsemées.
  5. Valeur de la balise : V8 utilise 32 bits pour représenter les objets et les valeurs. Puisque la valeur est de 31 bits, il utilise un bit pour distinguer s'il s'agit d'un objet (drapeau = 1) ou d'un entier appelé SMI (SMall Integer) (drapeau = 0). Ensuite, si un nombre est supérieur à 31 chiffres, V8 encadrera le numéro, le transformera en double et créera un nouvel objet pour contenir le numéro. Utilisez autant que possible des nombres signés 31 bits pour éviter des opérations de boxing coûteuses sur les objets JS.

Ignition et TurboFan

Avec la sortie de la V8 5.9 début 2017, un nouveau pipeline d'exécution a été introduit. Ce nouveau pipeline permet des gains de performances plus importants et des économies de mémoire significatives dans les applications JavaScript réelles.

Le nouveau flux d'exécution est construit sur Ignition (l'interpréteur de V8) et TurboFan (le dernier compilateur d'optimisation de V8).

Depuis la sortie de la V8 5.9, l'équipe V8 s'est éloignée du full-codegen et de Crankshaft (depuis 2010) car l'équipe V8 a eu du mal à suivre les nouvelles fonctionnalités du langage JavaScript et les optimisations que ces fonctionnalités nécessitent. Desservi par la technologie V8).

Cela signifie que la V8 aura globalement une architecture plus simple et plus maintenable.

Comprendre le fonctionnement de JavaScript, plonger dans le moteur V8 et écrire du code optimisé

Ces améliorations ne sont que le début. Les nouveaux pipelines Ignition et TurboFan ouvrent la voie à de nouvelles optimisations qui amélioreront les performances de JavaScript et réduiront l'empreinte de la V8 dans Chrome et Node.js pour les années à venir.

Recommandations d'apprentissage gratuites associées : programmation php (vidéo)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer