- Dans le moteur V8, les performances de la fonction JSON.stringify ont été plus que doublées, améliorant ainsi la vitesse de sérialisation des données
- Une voie d’optimisation a été introduite pour les objets sans effets de bord, ce qui permet d’omettre de nombreuses vérifications défensives et d’obtenir de forts gains de vitesse sur les objets de données courants
- Pour le traitement des chaînes, des méthodes avancées ont été appliquées à l’échelle du matériel et de la mémoire, avec distinction entre 1 octet et 2 octets, usage de SIMD et modification de la structure des buffers temporaires
- Dans le processus de conversion des nombres, l’ancien algorithme Grisu3 a été remplacé par Dragonbox, ce qui ouvre la voie à des conversions plus rapides dans l’ensemble des appels à
Number.toString()
- Pour certains arguments et certaines formes de données, le chemin de sérialisation standard reste utilisé, mais dans la plupart des situations de développement web, l’effet de l’optimisation s’applique automatiquement
Vue d’ensemble
JSON.stringify est une fonction clé qui convertit des données JavaScript en chaîne de caractères
- L’amélioration de ses performances a aussi un effet positif sur des opérations très importantes sur le web, comme les requêtes réseau ou l’enregistrement dans localStorage
- Grâce aux dernières avancées d’ingénierie de V8, la vitesse de cette fonctionnalité a été plus que doublée, et les principales optimisations sont présentées en détail
Fast Path sans effets de bord
- Le cœur de l’optimisation consiste à appliquer un chemin de sérialisation rapide utilisable uniquement dans les cas sans effets de bord
- Dans ces situations, les objets sont parcourus avec une structure itérative plutôt que récursive, ce qui évite d’avoir à vérifier les dépassements de pile et permet aussi de tenter la sérialisation d’objets plus profonds
- Quand un objet de données est simple, V8 exploite ce Fast Path au lieu de la logique générale plus lente, en supprimant de nombreux contrôles pour accélérer le traitement
Gestion des différentes représentations de chaînes
- V8 stocke différemment les chaînes selon qu’elles utilisent des caractères 1 octet / 2 octets (ASCII / non-ASCII), et s’il existe ne serait-ce qu’un caractère non ASCII, l’ensemble est géré en 2 octets
- Pour les performances de sérialisation, des versions distinctes de l’algorithme sont compilées selon le type de chaîne
- Comme il faut vérifier le type concret de l’instance de chaîne pendant le traitement, lorsqu’une chaîne 2 octets est détectée, un sérialiseur 2 octets adapté prend le relais
- Grâce à cela, le coût du basculement de chemin selon l’encodage des chaînes est pratiquement nul
- Le résultat est produit en créant séparément des buffers 1 octet et 2 octets, puis en les fusionnant simplement à la fin
Optimisation de la sérialisation des chaînes avec SIMD
- Les chaînes JavaScript peuvent contenir des caractères qui doivent être échappés lors de la sérialisation JSON
- Les chaînes longues sont analysées plusieurs octets à la fois grâce à des instructions matérielles SIMD (comme ARM64 Neon)
- Les chaînes courtes sont traitées selon une approche SWAR, qui utilise des opérations sur les bits dans des registres généraux pour traiter plusieurs caractères simultanément
- Quelle que soit la méthode, dans la plupart des cas il est possible de copier rapidement toute la chaîne sans transformation particulière
Ajout d’Express Lane
- Même au sein du Fast Path, une voie ultra-rapide a été ajoutée pour permettre une sérialisation par simple copie des clés, sans travail répétitif comme les vérifications de propriétés
- En exploitant le flag de hidden class des objets, lorsqu’une clé ne contient pas de Symbol, que toutes sont enumerable et déjà sérialisables sans échappement, l’objet est marqué comme
fast-json-iterable
- Lors de la sérialisation d’un autre objet ayant la même hidden class, la copie des clés est effectuée directement sans vérification supplémentaire
- Cette technique est aussi réutilisée dans
JSON.parse pour accélérer la comparaison des clés
Un algorithme double-to-string plus rapide
- Le processus de conversion des nombres en chaînes est lui aussi fréquent et complexe
- L’ancien algorithme Grisu3 a été remplacé par Dragonbox, ce qui améliore également les performances sur l’ensemble des appels à
Number.prototype.toString()
Optimisation de la structure des buffers temporaires
- Lors de la construction des chaînes, on utilisait auparavant un buffer continu unique, ce qui entraînait une surcharge liée à la copie complète à chaque manque d’espace
- La nouvelle méthode repose sur une structure de buffers segmentés, qui assemble plusieurs petits buffers selon les besoins
- Ainsi, en cas de manque d’espace, il suffit d’allouer un nouveau buffer sans recopier l’ensemble
Limites
- Le Fast Path ne fonctionne que pour les sérialisations de données simples
- Si les conditions ci-dessous ne sont pas remplies, le chemin standard est utilisé
- impossible d’utiliser les arguments replacer ou space (pas de Pretty-Print ni de transformation)
- il faut un objet simple, sans méthode personnalisée toJSON
- en présence de propriétés indexées, le traitement bascule vers le chemin lent
- les chaînes spéciales comme ConsString ne sont pas prises en charge
- Pour la plupart des usages courants, comme la sérialisation de données, la génération de réponses d’API ou la mise en cache de configuration, l’optimisation s’applique automatiquement
Conclusion
- En repensant l’approche sur toute la chaîne, de la conception de base de
JSON.stringify jusqu’au traitement mémoire et caractère, V8 atteint un gain de plus de 2× sur le benchmark JetStream2
- Ces améliorations sont disponibles à partir de V8 13.8 (Chrome 138)
Aucun commentaire pour le moment.