- In der V8-Engine wurde die Performance der Funktion JSON.stringify um mehr als das Doppelte gesteigert, was die Geschwindigkeit der Datenserialisierung verbessert
- Durch die Einführung eines Optimierungspfads für Objekte ohne Seiteneffekte konnten viele defensive Prüfungen übersprungen werden, was bei typischen Datenobjekten zu großen Geschwindigkeitsgewinnen führt
- Bei der String-Verarbeitung kamen fortgeschrittene Verfahren für Hardware und Speicher zum Einsatz, darunter die Unterscheidung zwischen 1-Byte-/2-Byte-Zeichen, die Nutzung von SIMD und Änderungen an der Struktur temporärer Puffer
- Beim Zahlenumwandlungsprozess wurde der bisherige Grisu3-Algorithmus durch Dragonbox ersetzt, was auch bei Aufrufen von
Number.toString() insgesamt schnellere Konvertierungen ermöglicht
- Bei einigen Argumenten und Formen wird zwar zum allgemeinen Serialisierungspfad zurückgekehrt, aber in den meisten Webentwicklungs-Szenarien profitiert man automatisch von der Optimierung
Überblick
JSON.stringify ist eine Schlüsselfunktion zum Umwandeln von Daten in Strings in JavaScript
- Eine Leistungssteigerung dieser Funktion wirkt sich auch positiv auf sehr wichtige Web-Aufgaben wie Netzwerkanfragen oder das Speichern in localStorage aus
- Durch aktuelles V8-Engineering wurde die Geschwindigkeit dieser Funktion um mehr als das Doppelte verbessert, und die wichtigsten Optimierungsansätze werden im Detail vorgestellt
Fast-Path für Seiteneffektfreiheit
- Der Kern der Optimierung ist die Anwendung eines schnellen Serialisierungspfads, der nur in Situationen ohne Seiteneffekte (side effects) verwendet werden kann
- In solchen Fällen werden Objekte nicht rekursiv, sondern mit einer iterativen Struktur durchlaufen, wodurch keine Stack-Overflow-Prüfungen nötig sind und auch tiefere Objekte serialisiert werden können
- Wenn ein Datenobjekt einfach ist, nutzt V8 statt der langsamen allgemeinen Logik diesen Fast Path, lässt viele Prüfungen aus und erhöht so die Geschwindigkeit
Umgang mit verschiedenen String-Darstellungen
- V8 speichert Strings je nach 1-Byte-/2-Byte-Zeichen (ASCII/Nicht-ASCII) unterschiedlich; sobald auch nur ein Nicht-ASCII-Zeichen enthalten ist, wird der gesamte String als 2-Byte-String behandelt
- Für die Performance der String-Serialisierung werden separate Algorithmus-Versionen je nach String-Typ kompiliert
- Da während der Verarbeitung der Typ der String-Instanz geprüft werden muss, übernimmt bei Erkennung eines 2-Byte-Strings ein passender 2-Byte-Serializer den Zustand
- Dadurch gibt es praktisch keinen Overhead durch Pfadwechsel je nach String-Encoding
- Das Ergebnis wird erzeugt, indem zunächst getrennte 1-Byte- und 2-Byte-Puffer erstellt und am Ende einfach zusammengeführt werden
Optimierung der String-Serialisierung mit SIMD
- JavaScript-Strings können Zeichen enthalten, die bei der JSON-Serialisierung maskiert werden müssen
- Lange Strings werden mit SIMD-Hardwarebefehlen (wie ARM64 Neon) geprüft, sodass mehrere Bytes gleichzeitig verarbeitet werden
- Kurze Strings werden mit der SWAR-Methode verarbeitet, bei der per Bit-Operationen in allgemeinen Registern mehrere Zeichen gleichzeitig behandelt werden
- Unabhängig von der Methode kann in den meisten Fällen der gesamte String ohne besondere Umwandlung schnell kopiert werden
Express Lane hinzugefügt
- Selbst innerhalb des Fast Path wurde eine Express Lane eingerichtet, damit die Serialisierung allein durch das Kopieren von Schlüsseln ohne wiederholte Arbeiten wie Property-Prüfungen möglich ist
- Mithilfe des Hidden-Class-Flags eines Objekts werden Fälle, in denen Schlüssel keine Symbols enthalten, alle enumerable sind und ohne Escaping serialisiert werden können, als
fast-json-iterable markiert
- Wird später ein anderes Objekt mit derselben Hidden Class serialisiert, können die Schlüssel sofort ohne zusätzliche Prüfung kopiert werden
- Diese Technik wird auch in
JSON.parse für schnelle Schlüsselvergleiche eingesetzt
Schnellerer double-to-string-Algorithmus
- Auch die Umwandlung von Zahlen in Strings ist häufig und komplex
- Durch den Austausch des bisherigen Grisu3-Algorithmus gegen Dragonbox ergeben sich Leistungsverbesserungen auch bei sämtlichen Aufrufen von
Number.prototype.toString()
Optimierung der Struktur temporärer Puffer
- Beim Erzeugen von Strings wurde bisher ein einzelner zusammenhängender Puffer verwendet, was bei Platzmangel zu dem Overhead führte, den gesamten Inhalt kopieren zu müssen
- Die neue Methode verwendet eine segmentierte Pufferstruktur, bei der je nach Bedarf mehrere kleinere Puffer aneinandergereiht werden
- Dadurch muss bei Platzmangel nicht mehr alles kopiert werden, sondern nur ein neuer Puffer allokiert werden
Grenzen
- Der Fast Path funktioniert nur bei einfacher Datenserialisierung
- Werden die folgenden Bedingungen nicht erfüllt, wird der allgemeine Pfad verwendet
- replacer- oder space-Argumente dürfen nicht verwendet werden (kein Pretty-Print, keine Transformation)
- Es muss sich um ein einfaches Objekt ohne benutzerdefinierte toJSON-Methode handeln
- Bei indexbasierten Properties wird auf den langsamen Pfad gewechselt
- ConsString und andere spezielle Strings werden nicht verarbeitet
- Für die meisten üblichen Einsatzfälle wie Datenserialisierung, API-Antworten oder Konfigurations-Caching greift die Optimierung automatisch
Fazit
- Durch eine Neugestaltung des Ansatzes in allen Bereichen – von der Grundarchitektur von
JSON.stringify über Speicherverarbeitung bis zur Zeichenverarbeitung – wurde im JetStream2-Benchmark eine mehr als doppelte Geschwindigkeitssteigerung erzielt
- Diese Verbesserungen sind ab V8 Version 13.8 (Chrome 138) direkt nutzbar
Noch keine Kommentare.