Heute veröffentlichen wir den zweiten Teil der Übersetzung des Materials, das den internen Mechanismen von V8 und der Untersuchung des Leistungsproblems von React gewidmet ist.

→
Der erste TeilVeralterung und Migration von Objektformen
Was ist, wenn das Feld ursprünglich einen
Smi
enthielt und sich dann die Situation änderte und ein Wert gespeichert werden musste, für den die
Smi
Darstellung nicht geeignet ist? Beispiel: Wie im folgenden Beispiel, wenn zwei Objekte mit derselben Form des Objekts dargestellt werden, in dem
x
anfänglich als
Smi
gespeichert ist:
const a = { x: 1 }; const b = { x: 2 };
Zu Beginn des Beispiels haben wir zwei Objekte, für deren Darstellung wir dieselbe Form des Objekts verwenden, in dem das
Smi
Format zum Speichern von
x
.
Das gleiche Formular wird zur Darstellung von Objekten verwendetWenn sich die
bx
Eigenschaft ändert und Sie das
Double
Format verwenden müssen, um sie darzustellen, weist V8 Speicherplatz für die neue Form des Objekts zu, in der
x
die
Double
Darstellung zugewiesen ist und die eine leere Form angibt. V8 erstellt auch eine Entität
MutableHeapNumber
, in der der Wert 0,2 der
x
Eigenschaft gespeichert wird. Anschließend aktualisieren wir das Objekt
b
so, dass es auf dieses neue Formular verweist, und ändern den Steckplatz im Objekt so, dass es
MutableHeapNumber
die zuvor erstellte
MutableHeapNumber
Entität mit Offset 0
MutableHeapNumber
. Schließlich markieren wir die alte Form des Objekts als veraltet und trennen sie vom Baum Übergänge. Dazu erstellen Sie einen neuen Übergang für
'x'
vom leeren zum gerade erstellten Formular.
Folgen der Zuweisung eines neuen Werts zu einer ObjekteigenschaftDerzeit können wir das alte Formular nicht vollständig löschen, da es noch von Objekt
a
. Darüber hinaus ist es sehr teuer, den gesamten Speicher bei der Suche nach allen Objekten zu umgehen, die auf das alte Formular verweisen, und den Status dieser Objekte sofort zu aktualisieren. Stattdessen verwendet der V8 hier einen "faulen" Ansatz. Alle Operationen zum Lesen oder Schreiben der Eigenschaften von Objekt
a
zunächst auf die Verwendung eines neuen Formulars übertragen. Die Idee hinter dieser Aktion ist es, die veraltete Form des Objekts letztendlich unerreichbar zu machen. Dies führt dazu, dass der Garbage Collector damit umgeht.
Out-of-Form-Speicher gibt den Garbage Collector freiIn Situationen, in denen das Feld, das die Ansicht ändert, nicht das letzte in der Kette ist, sind die Dinge komplizierter:
const o = { x: 1, y: 2, z: 3, }; oy = 0.1;
In diesem Fall muss der V8 die sogenannte Split-Form finden. Dies ist das letzte Formular in der Kette, das sich vor dem Formular befindet, in dem die entsprechende Eigenschaft angezeigt wird. Hier ändern wir
y
, das heißt - wir müssen die letzte Form finden, in der es kein
y
. In unserem Beispiel ist dies die Form, in der
x
erscheint.
Suchen Sie nach dem letzten Formular, in dem kein Wert geändert wurdeAusgehend von dieser Form erstellen wir hier eine neue Übergangskette für
y
, die alle vorherigen Übergänge reproduziert. Erst jetzt wird die Eigenschaft
'y'
als
Double
. Jetzt verwenden wir diese neue Übergangskette für
y
und markieren sie als veralteten alten Teilbaum. Im letzten Schritt migrieren wir die Instanz des
o
Objekts in ein neues Formular und verwenden jetzt die Entität
MutableHeapNumber
, um den
y
MutableHeapNumber
zu speichern. Bei diesem Ansatz verwendet das neue Objekt keine Fragmente des alten Übergangsbaums, und nachdem alle Verweise auf die alte Form verschwunden sind, verschwindet auch der veraltete Teil des Baums.
Erweiterbarkeit und Übergangsintegrität
Mit dem Befehl
Object.preventExtensions()
können Sie das Hinzufügen neuer Eigenschaften zu einem Objekt vollständig verhindern. Wenn Sie das Objekt mit diesem Befehl verarbeiten und versuchen, ihm eine neue Eigenschaft hinzuzufügen, wird eine Ausnahme ausgelöst. (Richtig, wenn der Code nicht im strengen Modus ausgeführt wird, wird keine Ausnahme ausgelöst. Ein Versuch, eine Eigenschaft hinzuzufügen, führt jedoch einfach zu keinen Konsequenzen.) Hier ist ein Beispiel:
const object = { x: 1 }; Object.preventExtensions(object); object.y = 2;
Die
Object.seal()
-Methode wirkt auf Objekte wie
Object.preventExtensions()
, markiert jedoch auch alle Eigenschaften als nicht konfigurierbar. Dies bedeutet, dass sie weder gelöscht noch ihre Eigenschaften hinsichtlich der Möglichkeiten zum Auflisten, Festlegen oder Umschreiben geändert werden können.
const object = { x: 1 }; Object.seal(object); object.y = 2;
Die
Object.freeze()
-Methode führt dieselben Aktionen wie
Object.seal()
, ihre Verwendung führt jedoch zusätzlich dazu, dass die Werte vorhandener Eigenschaften nicht geändert werden können. Sie sind als Eigenschaften gekennzeichnet, in die keine neuen Werte geschrieben werden können.
const object = { x: 1 }; Object.freeze(object); object.y = 2;
Betrachten Sie ein bestimmtes Beispiel. Wir haben zwei Objekte, von denen jedes einen eindeutigen Wert
x
. Dann verbieten wir die Erweiterung des zweiten Objekts:
const a = { x: 1 }; const b = { x: 2 }; Object.preventExtensions(b);
Die Verarbeitung dieses Codes beginnt mit Aktionen, die wir bereits kennen. Es wird nämlich ein Übergang von der leeren Form des Objekts zur neuen Form vorgenommen, die die Eigenschaft
'x'
(dargestellt als Entität
Smi
). Wenn wir die Erweiterung von Objekt
b
verbieten, führt dies zu einem besonderen Übergang zu einer neuen Form, die als nicht erweiterbar markiert ist. Dieser besondere Übergang führt nicht zum Erscheinen einer neuen Immobilie. Dies ist in der Tat nur ein Marker.
Das Ergebnis der Verarbeitung eines Objekts mit der Object.preventExtensions () -MethodeBitte beachten Sie, dass wir das vorhandene Formular nicht einfach mit dem Wert
x
darin ändern können, da es von einem anderen Objekt benötigt wird, nämlich Objekt
a
, das noch erweiterbar ist.
Leistungsproblem reagieren
Lassen Sie uns nun alles sammeln, worüber wir gesprochen haben, und das gewonnene Wissen nutzen, um die Essenz des jüngsten React-Leistungsproblems zu verstehen. Als das React-Team reale Anwendungen profilierte, stellten sie eine merkwürdige Verschlechterung der V8-Leistung fest, die sich auf den React-Kern auswirkte. Hier ist eine vereinfachte Reproduktion des Problemteils des Codes:
const o = { x: 1, y: 2 }; Object.preventExtensions(o); oy = 0.2;
Wir haben ein Objekt mit zwei Feldern, die als
Smi
Entitäten dargestellt werden. Wir verhindern eine weitere Erweiterung des Objekts und führen dann eine Aktion aus, die dazu führt, dass das zweite Feld im
Double
werden muss.
Wir haben bereits herausgefunden, dass das Verbot der Objekterweiterung zu ungefähr der folgenden Situation führt.
Folgen des Verbots der ObjekterweiterungUm beide Eigenschaften des Objekts darzustellen, werden
Smi
Entitäten
Smi
, und der letzte Übergang ist erforderlich, um die Form des Objekts als nicht erweiterbar zu markieren.
Jetzt müssen wir die Darstellung der
y
Eigenschaft durch
Double
ändern. Dies bedeutet, dass wir nach einer Form der Trennung suchen müssen. In diesem Fall ist dies die Form, in der die
x
Eigenschaft angezeigt wird. Aber jetzt ist der V8 verwirrt. Tatsache ist, dass das Trennformular erweiterbar war und das aktuelle Formular als nicht erweiterbar markiert wurde. V8 weiß nicht, wie der Übergangsprozess in einer ähnlichen Situation reproduziert werden soll. Infolgedessen weigert sich der Motor einfach, alles herauszufinden. Stattdessen wird einfach ein separates Formular erstellt, das nicht mit dem aktuellen Formularbaum verbunden ist und nicht mit anderen Objekten geteilt wird. Dies ist so etwas wie eine verwaiste Form eines Objekts.
Verwaiste FormEs ist leicht zu erraten, dass dies, wenn dies bei vielen Objekten geschieht, sehr schlecht ist. Tatsache ist, dass dies das gesamte System der V8-Objektformen unbrauchbar macht.
Wenn kürzlich ein React-Problem aufgetreten ist, ist Folgendes aufgetreten. Jedes Objekt der
FiberNode
Klasse verfügte über Felder, in denen Zeitstempel gespeichert werden sollten, wenn die Profilerstellung aktiviert ist.
class FiberNode { constructor() { this.actualStartTime = 0; Object.preventExtensions(this); } } const node1 = new FiberNode(); const node2 = new FiberNode();
Diese Felder (z. B.
actualStartTime
) wurden mit 0 oder -1 initialisiert. Dies führte dazu, dass
Smi
Entitäten verwendet wurden, um ihre Bedeutungen
Smi
darzustellen. Später speicherten sie Echtzeitstempel im Format von Gleitkommazahlen, die von der Methode
performance.now () zurückgegeben wurden. Dies führte dazu, dass diese Werte nicht mehr in Form von
Smi
. Um diese Felder darzustellen, waren jetzt
Double
Entitäten erforderlich. Darüber hinaus hat React auch die Erweiterung von Instanzen der
FiberNode
Klasse verhindert.
Zunächst könnte unser vereinfachtes Beispiel in der folgenden Form dargestellt werden.
Ausgangszustand des SystemsEs gibt zwei Instanzen der Klasse, die denselben Baum von Übergängen der Form von Objekten verwenden. Genau genommen ist das System der Formen von Objekten in V8 dafür ausgelegt. Wenn dann die Echtzeitstempel im Objekt gespeichert sind, kann V8 nicht verstehen, wie es die Form der Trennung finden kann.
V8 ist verwirrtV8 weist
node1
eine neue verwaiste Form zu. Das gleiche passiert etwas später mit dem
node2
Objekt. Als Ergebnis haben wir jetzt zwei "verwaiste" Formen, von denen jede nur von einem Objekt verwendet wird. In vielen realen React-Anwendungen beträgt die Anzahl solcher Objekte viel mehr als zwei. Dies können Zehntausende oder sogar Tausende von Objekten der
FiberNode
Klasse sein. Es ist leicht zu verstehen, dass diese Situation die V8-Leistung nicht sehr gut beeinflusst.
Glücklicherweise haben wir dieses Problem in
V8 v7.4 behoben und
untersuchen die Möglichkeit, die Darstellung von Feldern von Objekten weniger ressourcenintensiv zu gestalten. Auf diese Weise können wir die verbleibenden Leistungsprobleme lösen, die in solchen Situationen auftreten. Dank des Fixes verhält sich V8 jetzt in der oben beschriebenen Problemsituation korrekt.
Ausgangszustand des SystemsSo sieht es aus. Zwei Instanzen der
FiberNode
Klasse verweisen auf ein nicht erweiterbares Formular. In diesem Fall wird
'actualStartTime'
als
Smi
Feld dargestellt. Wenn die erste Operation zum Zuweisen eines Werts zur Eigenschaft
node1.actualStartTime
, wird eine neue Übergangskette erstellt und die vorherige Kette als veraltet markiert.
Ergebnisse der Zuweisung eines neuen Werts zur Node1.actualStartTime-EigenschaftBitte beachten Sie, dass der Übergang zur nicht erweiterbaren Form jetzt in der neuen Kette korrekt wiedergegeben wird. Dies ist, worauf das System
node2.actualStartTime
nachdem der Wert von
node2.actualStartTime
.
Die Ergebnisse der Zuweisung eines neuen Werts zur Eigenschaft node2.actualStartTimeNachdem der neue Wert der Eigenschaft
node2.actualStartTime
zugewiesen wurde,
node2.actualStartTime
beide Objekte auf das neue Formular, und der veraltete Teil des Übergangsbaums kann vom Garbage Collector zerstört werden.
Bitte beachten Sie, dass Vorgänge zum Markieren der Formen von Objekten als veraltet und deren Migration kompliziert aussehen können. In der Tat - so wie es ist. Wir vermuten, dass dies auf echten Websites mehr schadet (in Bezug auf Leistung, Speichernutzung, Komplexität) als nützt. Insbesondere - nachdem wir im Fall der
Zeigerkomprimierung diesen Ansatz nicht mehr verwenden können, um
Double
Felder in Form von in Objekte eingebetteten Werten zu speichern. Infolgedessen hoffen wir
, den Mechanismus der Veralterung von V8-Objektformen
vollständig aufzugeben und diesen Mechanismus selbst überflüssig zu machen.
Es ist zu beachten, dass das React-Team dieses Problem selbst
gelöst hat und sichergestellt hat, dass die Felder in den Objekten der
FiberNodes
Klasse ursprünglich durch doppelte Werte dargestellt wurden:
class FiberNode { constructor() {
Hier kann anstelle von
Number.NaN
jeder Gleitkommawert verwendet werden, der nicht in den
Smi
Bereich passt. Zu diesen Werten gehören 0,000001,
Number.MIN_VALUE
, -0 und
Infinity
.
Es ist erwähnenswert, dass das in React beschriebene Problem spezifisch für V8 war und dass Entwickler beim Erstellen von Code nicht danach streben müssen, ihn basierend auf einer bestimmten Version einer bestimmten JavaScript-Engine zu optimieren. Es ist jedoch nützlich, etwas durch Optimieren des Codes beheben zu können, falls die Ursachen einiger Fehler in den Funktionen der Engine liegen.
Es sei daran erinnert, dass es in den Eingeweiden von JS-Motoren viele allerlei erstaunliche Dinge gibt. Der JS-Entwickler kann all diesen Mechanismen nach Möglichkeit helfen, ohne dieselben Variablenwerte unterschiedlichen Typs zuzuweisen. Beispielsweise sollten Sie numerische Felder nicht mit
null
initialisieren, da dies alle Vorteile der Beobachtung der Felddarstellung zunichte macht und die Lesbarkeit des Codes verbessert:
Mit anderen Worten - schreiben Sie lesbaren Code, und die Leistung wird von selbst kommen!
Zusammenfassung
In diesem Artikel haben wir die folgenden wichtigen Punkte untersucht:
- JavaScript unterscheidet zwischen "primitiven" und "Objekt" -Werten, und die Art der Ergebnisse kann nicht als vertrauenswürdig eingestuft werden.
- Sogar Werte mit demselben JavaScript-Typ können im Darm der Engine auf unterschiedliche Weise dargestellt werden.
- V8 versucht, den besten Weg zu finden, um jede Eigenschaft des in JS-Programmen verwendeten Objekts darzustellen.
- In bestimmten Situationen führt V8 Operationen zum Markieren der Formulare von Objekten als veraltet durch und führt die Migration von Formularen durch. Einschließlich - implementiert Übergänge, die mit dem Verbot der Erweiterung von Objekten verbunden sind.
Basierend auf dem Vorstehenden können wir einige praktische JavaScript-Programmiertipps bereitstellen, die zur Verbesserung der Codeleistung beitragen können:
- Initialisieren Sie Ihre Objekte immer auf die gleiche Weise. Dies trägt zur effektiven Arbeit mit Objektformen bei.
- Wählen Sie verantwortungsbewusst die Anfangswerte für die Felder von Objekten aus. Dies hilft den JavaScript-Engines bei der Auswahl, wie diese Werte intern dargestellt werden sollen.
Liebe Leser! Haben Sie Ihren Code jemals basierend auf den internen Funktionen bestimmter JavaScript-Engines optimiert?
