Blog 4 min de lecture

Booster les performances avec le moteur V8 : étude de ses choix de conception

Share this article
Booster les performances avec le moteur V8 : étude de ses choix de conception

Le moteur V8 est le moteur JavaScript open source et haute performance de Google, écrit en C++. Outre Google Chrome, on le retrouve aussi dans MongoDB, Node.js et de nombreuses autres applications populaires.

Il est intéressant d'explorer ce qui rend V8 si rapide et quelles techniques sont utilisées pour atteindre ce niveau de performance.

POO, design patterns et performance

Certains programmeurs C/C++ croient à tort que l'utilisation de la POO et des design patterns réduit les performances d'une application. V8 est un bon exemple montrant que ce n'est pas nécessairement vrai. V8 implémente de nombreux design patterns, et il est bien optimisé.

Voici quelques-uns des patterns utilisés :

Factory

Lorsque le moteur JavaScript exécute un script, il crée une instance pour chaque variable, fonction ou tableau qu'il rencontre. JSObject est la classe parente de toutes ces sortes d'objets.

Voici la liste de toutes les classes qui héritent de JSObject :

v1

V8 implémente une classe factory pour créer les objets requis, et la méthode Factory::NewJsObject est utilisée à cette fin.

Voici toutes les méthodes qui l'utilisent :

v4

La factory n'est pas utilisée directement par les classes de V8 ; elle est invoquée depuis la classe Heap, ce qui ajoute un niveau d'indirection supplémentaire à l'implémentation.

Visiteur

Comme l'explique la page wiki du pattern Visiteur :

The visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle.

Comme le pattern factory, ce pattern ajoute aussi une certaine indirection à l'implémentation, mais il rend le code plus lisible et plus maintenable.

Le code source de V8 contient de nombreuses classes qui implémentent le pattern Visiteur.

v2

Même si les développeurs de V8 doivent optimiser les performances d'exécution, ils acceptent d'introduire une certaine indirection dans le code. Il est vrai que l'utilisation des design patterns et de certains mécanismes C++ peut impacter les performances en raison de l'indirection ajoutée par leur implémentation, mais cela relève plutôt de la micro-optimisation. Une optimisation significative au niveau macro dépend davantage de choix de conception adaptés aux besoins de votre application.

Les choix de conception de V8 pour optimiser son exécution

1- Les classes cachées et l'accès rapide aux propriétés

JavaScript est un langage de programmation dynamique : des propriétés peuvent être ajoutées aux objets et supprimées à la volée. Cela signifie que les propriétés d'un objet peuvent changer au fil du temps.

Comme mentionné précédemment, JSObject est la classe parente de JSFunction, qui représente une fonction JavaScript, ou de JSValue, qui représente une valeur JavaScript. Cependant, il n'existe pas de classe héritant de JSObject qui représente une classe comme Function ou Value. La plupart des moteurs JavaScript utilisent une structure de données de type dictionnaire pour stocker les propriétés des objets ; chaque accès à une propriété nécessite une recherche dynamique pour résoudre l'emplacement de la propriété en mémoire.

Cette approche rend l'accès aux propriétés en JavaScript typiquement beaucoup plus lent que l'accès aux variables d'instance dans des langages comme Java et Smalltalk. Dans ces langages, les variables d'instance sont situées à des offsets fixes déterminés par le compilateur grâce à la disposition fixe des objets définie par leur classe. L'accès est simplement une question de chargement ou d'écriture en mémoire, ne nécessitant souvent qu'une seule instruction.

V8 utilise le concept de classes cachées pour réduire le temps nécessaire à l'accès aux propriétés JavaScript. V8 n'utilise pas de recherche dynamique pour accéder aux propriétés. À la place, V8 crée dynamiquement des classes cachées en coulisses.

2- La génération dynamique de code machine

V8 compile le code source JavaScript directement en code machine lors de sa première exécution. Il n'y a ni bytecodes intermédiaires ni interpréteur. L'accès aux propriétés est géré par du code de cache inline qui peut être patché avec d'autres instructions machine au fil de l'exécution de V8.

3- Un ramasse-miettes efficace

V8 récupère la mémoire utilisée par les objets qui ne sont plus nécessaires, dans un processus appelé ramasse-miettes (garbage collection). Pour garantir une allocation d'objets rapide, des pauses de garbage collection courtes et l'absence de fragmentation mémoire, V8 utilise un ramasse-miettes stop-the-world, générationnel et précis. Cela signifie que V8 :

  • arrête l'exécution du programme lors d'un cycle de garbage collection.
  • ne traite qu'une partie du tas d'objets dans la plupart des cycles de garbage collection, ce qui minimise l'impact de l'arrêt de l'application.
  • sait toujours exactement où se trouvent tous les objets et pointeurs en mémoire, ce qui évite d'identifier à tort des objets comme des pointeurs — une erreur qui peut entraîner des fuites mémoire.

Conclusion

Choisir de ne pas utiliser la POO et les design patterns simplement parce que la performance est votre priorité absolue peut être une mauvaise idée : vous pourriez gagner quelques microsecondes mais perdre la lisibilité et la maintenabilité de votre code.

Une optimisation significative au niveau macro dépend davantage de choix de conception adaptés aux besoins de votre application.

Share this article