JavaScript Preis 2019

In den letzten Jahren hat sich der sogenannte „ JavaScript-Preis “ aufgrund der erhöhten Analysegeschwindigkeit und der Browser-Kompilierung von Skripten erheblich positiv verändert. Jetzt, im Jahr 2019, sind die Hauptkomponenten der Auslastung der von JavaScript erstellten Systeme die Ladezeit der Skripte und ihre Ausführungszeit.



Die Interaktion des Benutzers mit der Site kann vorübergehend unterbrochen werden, wenn der Browser gerade JavaScript-Code ausführt. Infolgedessen können wir sagen, dass die Optimierung von Engpässen beim Laden und Ausführen von Skripten einen starken positiven Einfluss auf die Leistung der Website haben kann.

Allgemeine praktische Richtlinien für die Website-Optimierung


Was bedeutet das oben Genannte für Webentwickler? Der Punkt hier ist, dass die Kosten für Ressourcen zum Parsen (Parsen, Parsen) und Kompilieren von Skripten nicht so hoch sind wie zuvor. Daher sollten Entwickler bei der Analyse und Optimierung von JavaScript-Bundles die folgenden drei Empfehlungen berücksichtigen:

  1. Versuchen Sie, die zum Herunterladen von Skripten erforderliche Zeit zu verkürzen.

    • Versuchen Sie, Ihre JS-Bundles klein zu halten. Dies ist besonders wichtig für Websites, die für mobile Geräte entwickelt wurden. Die Verwendung kleiner Bundles verbessert die Ladezeit des Codes, verringert die Speichernutzung und verringert die Belastung des Prozessors.
    • Versuchen Sie zu verhindern, dass der gesamte Projektcode als ein einziges großes Paket dargestellt wird. Wenn die Bündelgröße ungefähr 50-100 Kb überschreitet, teilen Sie sie in separate Fragmente kleiner Größe. Dank HTTP / 2-Multiplexing können mehrere Serveranforderungen und mehrere Antworten gleichzeitig verarbeitet werden. Dies reduziert die Belastung des Systems, die mit der Notwendigkeit verbunden ist, zusätzliche Anforderungen zum Laden von Daten zu erfüllen.
    • Wenn Sie an einem mobilen Projekt arbeiten, versuchen Sie, den Code so klein wie möglich zu halten. Diese Empfehlung ist mit niedrigen Datenraten über Mobilfunknetze verbunden. Streben Sie außerdem nach einer sparsamen Nutzung des Speichers.
  2. Versuchen Sie, die zum Ausführen von Skripten erforderliche Zeit zu verkürzen.

    • Vermeiden Sie langwierige Aufgaben , die den Hauptthread über einen längeren Zeitraum laden können, und erhöhen Sie die Zeit, die erforderlich ist, damit sich die Seiten in einem Zustand befinden, in dem Benutzer mit ihnen interagieren können. In der aktuellen Umgebung tragen Skripte, die nach dem Laden ausgeführt werden, wesentlich zum "Preis von JavaScript" bei.
  3. Betten Sie keine großen Codefragmente in Seiten ein.

    • Die folgende Regel sollte hier eingehalten werden: Wenn die Skriptgröße 1 KB überschreitet, versuchen Sie, sie nicht in den Seitencode einzubetten. Einer der Gründe für diese Empfehlung ist die Tatsache, dass 1 KB die Grenze ist, nach der das Zwischenspeichern von externem Skriptcode in Chrome funktioniert. Beachten Sie außerdem, dass das Parsen und Kompilieren eingebetteter Skripte weiterhin im Hauptthread ausgeführt wird.

Warum ist es so wichtig, Skripte zu laden und auszuführen?


Warum ist es wichtig, die Lade- und Ausführungszeit von Skripten unter modernen Bedingungen zu optimieren? Die Ladezeiten von Skripten sind in Situationen äußerst wichtig, in denen auf Websites über langsame Netzwerke zugegriffen wird. Trotz der Tatsache, dass sich 4G-Netze (und sogar 5G) immer mehr verbreiten, zeigt die Eigenschaft NetworkInformation.effectiveType in vielen Fällen bei Verwendung mobiler Internetverbindungen Indikatoren an, die sich auf der Ebene von 3G-Netzen oder sogar auf niedrigeren Ebenen befinden.

Die zur Ausführung des JS-Codes erforderliche Zeit ist für mobile Geräte mit langsamen Prozessoren wichtig. Aufgrund der Tatsache, dass mobile Geräte unterschiedliche CPUs und GPUs verwenden, und aufgrund der Tatsache, dass bei Überhitzung von Geräten zum Schutz der Leistung die Leistung ihrer Komponenten abnimmt, können Sie eine gravierende Lücke zwischen der Leistung teurer und billiger Telefone und Tablets feststellen. Dies wirkt sich stark auf die Leistung von JavaScript-Code aus, da die Fähigkeit, solchen Code von einem Gerät auszuführen, durch die Prozessorfähigkeiten dieses Geräts eingeschränkt ist.

Wenn wir die Gesamtzeit analysieren, die zum Laden und Vorbereiten der Seite für die Arbeit in einem Browser wie Chrome aufgewendet wurde, können etwa 30% dieser Zeit für die Ausführung von JS-Code aufgewendet werden. Im Folgenden finden Sie eine Analyse des Ladens einer sehr typischen Webseite (reddit.com) auf einem Hochleistungs-Desktop-Computer.


Beim Laden der Seite werden etwa 10 bis 30% der Zeit für die Codeausführung mit V8 aufgewendet

Wenn wir über mobile Geräte sprechen, dauert es auf einem durchschnittlichen Telefon (Moto G4) 3-4 Mal länger, reddit.com auf einem JS-Code auszuführen als auf einem High-Level-Gerät (Pixel 3). Auf einem schwachen Gerät (Alcatel 1X kostet weniger als 100 US-Dollar) erfordert die Lösung des gleichen Problems mindestens sechsmal mehr Zeit als bei Pixel 3.


Die Zeit, die zum Verarbeiten von JS-Code auf Mobilgeräten verschiedener Klassen benötigt wird

Bitte beachten Sie, dass die mobilen und Desktop-Versionen von reddit.com unterschiedlich sind. Daher können Sie die Ergebnisse von Mobilgeräten und beispielsweise MacBook Pro nicht vergleichen.

Wenn Sie versuchen, die Ausführungszeit von JavaScript-Code zu optimieren, achten Sie auf langwierige Aufgaben , die den UI-Stream für lange Zeit erfassen können. Diese Aufgaben können die Ausführung anderer, äußerst wichtiger Aufgaben behindern, selbst wenn das Erscheinungsbild der Seite vollständig arbeitsbereit erscheint. Langzeitaufgaben sollten in kleinere Aufgaben unterteilt werden. Indem Sie den Code in Teile aufteilen und die Ladereihenfolge dieser Teile steuern, können Sie erreichen, dass Seiten schneller in einen interaktiven Zustand versetzt werden. Dies führt hoffentlich dazu, dass Benutzer weniger Schwierigkeiten bei der Interaktion mit Seiten haben.


Lang laufende Aufgaben erfassen den Hauptthread. Sie sollten in Stücke zerbrochen werden

Wie beschleunigen V8-Verbesserungen das Parsen und Kompilieren von Skripten?


Die Geschwindigkeit beim Parsen des JS-Quellcodes in V8 hat sich seit Chrome 60 um das Zweifache erhöht. Gleichzeitig tragen Parsen und Kompilieren jetzt weniger zum "JavaScript-Preis" bei. Dies ist anderen Optimierungsbemühungen von Chrome zu verdanken, die zur Parallelisierung dieser Aufgaben geführt haben.

In V8 wird der Aufwand für das Parsen und Kompilieren von Code im Hauptthread um durchschnittlich 40% reduziert. Für Facebook betrug die Verbesserung dieses Indikators beispielsweise 46%, für Pinterest 62%. Das höchste Ergebnis, 81%, wurde für YouTube erzielt. Solche Ergebnisse sind möglich, weil Parsing und Kompilierung in einen separaten Stream verschoben werden. Dies gilt zusätzlich zu den bestehenden Verbesserungen hinsichtlich der Streaming-Lösung derselben Aufgaben außerhalb des Hauptstroms.


JS-Analysezeit in verschiedenen Versionen von Chrome

Sie können auch visualisieren, wie sich die in verschiedenen Chrome-Versionen vorgenommenen V8-Optimierungen auf die für die Verarbeitung des Codes erforderliche Prozessorzeit auswirken. In der gleichen Zeit, in der Chrome 61 den Facebook JS-Code analysieren musste, kann Chrome 75 jetzt den Facebook JS-Code und zusätzlich den Twitter-Code sechsmal analysieren.


In der Zeit, die Chrome 61 für die Verarbeitung von Facebook JS-Code benötigte, kann Chrome 75 sowohl Facebook-Code als auch die sechsfache Menge an Twitter-Code verarbeiten.

Lassen Sie uns darüber sprechen, wie solche Verbesserungen erzielt wurden. Kurz gesagt, die Skriptressourcen können im Streaming-Modus des Workflows analysiert und kompiliert werden. Dies bedeutet Folgendes:

  • V8 kann JS-Code analysieren und kompilieren, ohne den Hauptthread zu blockieren.
  • Die Stream-Verarbeitung des Skripts beginnt, wenn der universelle HTML-Parser auf das <script> stößt. Ein HTML-Parser verarbeitet Skripte, die das Parsen von Seiten blockieren. Er trifft auf asynchrone Skripte und arbeitet weiter.
  • In den meisten realen Szenarien, die durch bestimmte Netzwerkverbindungsgeschwindigkeiten gekennzeichnet sind, analysiert der V8 den Code schneller, als er laden kann. Infolgedessen führt V8 die Aufgaben des Parsens und Kompilierens des Codes einige Millisekunden nach dem Laden der letzten Bytes des Skripts aus.

Wenn Sie darüber etwas ausführlicher sprechen, lautet der Punkt hier wie folgt. In viel älteren Versionen von Chrome musste das Skript vollständig heruntergeladen werden, bevor es analysiert werden konnte. Dieser Ansatz ist einfach und verständlich, aber wenn er verwendet wird, werden Prozessorressourcen irrational verwendet. Chrome zwischen den Versionen 41 und 68 beginnt unmittelbar nach dem Laden des Skripts im asynchronen Modus mit dem Parsen und führt diese Aufgabe in einem separaten Thread aus.


Skripte werden fragmentarisch an den Browser gesendet. V8 beginnt mit der Streaming-Datenverarbeitung, nachdem mindestens 30 KB Code vorhanden sind.

In Chrome 71 sind wir zu einem aufgabenbasierten System übergegangen. Hier kann der Scheduler gleichzeitig mehrere asynchrone / verzögerte Skriptverarbeitungssitzungen starten. Aufgrund dieser Änderung hat sich die durch das Parsen des Hauptthreads erzeugte Last um etwa 20% verringert. Dies führte zu einer Verbesserung der TTI / FID-Werte an realen Standorten um etwa 2%.


Chrome 71 verwendet ein aufgabenbasiertes Codeverarbeitungssystem. Mit diesem Ansatz kann der Scheduler mehrere asynchrone / ausstehende Skripts gleichzeitig verarbeiten.

In Chrome 72 haben wir die Streaming-Verarbeitung zur primären Methode zum Parsen von Skripten gemacht. Jetzt werden sogar reguläre synchrone Skripte auf diese Weise behandelt (obwohl dies nicht für integrierte Skripte gilt). Außerdem haben wir das Abbrechen von aufgabenbasierten Analysevorgängen eingestellt, wenn der Hauptthread analysierten Code benötigte. Dies geschieht aufgrund der Tatsache, dass dies dazu führt, dass ein Teil der bereits geleisteten Arbeit erneut ausgeführt werden muss.

Die vorherige Version von Chrome unterstützte das Streaming-Parsing und die Stream-Kompilierung von Code. Dann sollte das aus dem Netzwerk heruntergeladene Skript zuerst in den Hauptstrom gelangen und dann zum Verarbeitungssystem für Streaming-Skripte umgeleitet werden.

Dies führte häufig dazu, dass der Stream-Parser auf Daten wartete, die bereits aus dem Netzwerk heruntergeladen, aber noch nicht vom Haupt-Stream zur Stream-Verarbeitung umgeleitet wurden. Dies geschah aufgrund der Tatsache, dass der Hauptthread möglicherweise mit einigen anderen Aufgaben beschäftigt war (z. B. Parsen von HTML, Erstellen eines Seitenlayouts oder Ausführen von JS-Code).

Jetzt experimentieren wir damit, wie Sie beim Vorladen von Seiten mit dem Parsen von Code beginnen. Bisher wurde die Implementierung eines solchen Mechanismus durch die Notwendigkeit behindert, die Ressourcen des Hauptthreads zum Übertragen von Aufgaben an den Streaming-Parser zu verwenden. Details zum Parsen von JS-Code, der "sofort" ausgeführt wird, finden Sie hier .

Wie haben sich die Verbesserungen auf das ausgewirkt, was in den Tools des Entwicklers zu sehen ist?


Zusätzlich zu dem oben Gesagten kann festgestellt werden, dass es zuvor ein Problem in den Entwicklertools gab. Es bestand darin, dass Informationen über die Aufgabe des Parsens so angezeigt wurden, als würden sie den Hauptthread vollständig blockieren. Der Parser führte jedoch nur Operationen aus, die den Hauptthread blockierten, wenn neue Daten benötigt wurden. Da wir von dem Schema der Verwendung eines einzelnen Streams für die Streaming-Datenverarbeitung zu dem Schema übergegangen sind, in dem Streaming-Verarbeitungsaufgaben angewendet werden, ist dies ziemlich offensichtlich geworden. Folgendes können Sie in Chrome 69 sehen.


Das Problem liegt in den Entwicklertools, aufgrund derer Informationen zu Parsing-Skripten angezeigt wurden, als würden sie den Hauptthread vollständig blockieren

Hier sehen Sie, dass die Aufgabe "Parse Script" 1,08 Sekunden dauert. Das Parsen von JavaScript ist jedoch nicht so langsam! Meistens wird nichts Nützliches ausgeführt, außer auf Daten vom Hauptthread zu warten.
In Chrome 76 sehen Sie bereits ein völlig anderes Bild.


In Chrome 76 ist das Parsen in viele kleine Aufgaben unterteilt

Im Allgemeinen kann festgestellt werden, dass die Registerkarte Leistung der Entwicklertools großartig ist, um das Gesamtbild der Vorgänge auf der Seite anzuzeigen. Um detailliertere Informationen zu den Funktionen von V8 zu erhalten, z. B. Analysezeit und Kompilierungszeit, können Sie Chrome Tracing mit RCS-Unterstützung (Runtime Call Stats) verwenden. In den empfangenen RCS-Daten finden Sie die Indikatoren Parse-Background und Compile-Background. Sie können angeben, wie lange es gedauert hat, den JS-Code außerhalb des Hauptthreads zu analysieren und zu kompilieren. Die Metriken "Analysieren" und "Kompilieren" geben an, wie viel Zeit für verwandte Aktivitäten im Hauptthread aufgewendet wurde.


Analyse von RCS-Daten mit Google Tracing

Wie haben sich die Änderungen auf die Arbeit mit realen Websites ausgewirkt?


Schauen wir uns einige Beispiele an, wie die Verarbeitung von Streaming-Skripten das Surfen auf realen Websites beeinflusst hat.

»Reddit



Zeigen Sie reddit.com auf einem MacBook Pro an. Zeit zum Parsen und Kompilieren von JS-Code, der im Haupt- und im Arbeitsthread verbracht wird

Auf der reddit.com-Website gibt es mehrere JS-Bundles, von denen jedes eine Größe von mehr als 100 KB hat. Sie sind in externe Funktionen eingebunden, was zur Ausführung großer Mengen "fauler" Kompilierungen im Hauptthread führt. Entscheidend im obigen Diagramm ist die Zeit, die erforderlich ist, um die Skripte im Hauptthread zu verarbeiten. Dies liegt an der Tatsache, dass eine große Belastung des Hauptthreads die Zeit verlängern kann, die die Seite benötigt, um in den interaktiven Modus zu wechseln. Bei der Verarbeitung des reddit.com-Sitecodes wird die meiste Zeit im Hauptthread verbracht, und die Ressourcen des Arbeits- / Hintergrundthreads werden auf ein Minimum reduziert.

Sie können diese Site optimieren, indem Sie einige große Bundles in Teile (jeweils ca. 50 KB) aufteilen und den Code nicht in eine Funktion einschließen. Dies würde die parallele Verarbeitung von Skripten maximieren. Infolgedessen konnten Bundles im Streaming-Modus gleichzeitig analysiert und kompiliert werden. Dies würde die Belastung des Hauptthreads beim Vorbereiten der Seite für die Arbeit verringern.

▍Facebook



Zeigen Sie facebook.com auf Ihrem MacBook Pro an. Zeit zum Parsen und Kompilieren von JS-Code, der im Haupt- und im Arbeitsthread verbracht wird

Wir können auch eine Site wie facebook.com in Betracht ziehen, die etwa 6 MB komprimierten JS-Code verwendet. Dieser Code wird mit ungefähr 292 Anforderungen geladen. Einige von ihnen sind asynchron, andere zielen auf das Vorladen von Daten ab, andere haben eine niedrige Priorität. Die meisten Facebook-Skripte sind klein und eng fokussiert. Dies kann sich durch Hintergrund- / Workflows positiv auf die parallele Datenverarbeitung auswirken. Tatsache ist, dass viele kleine Skripte gleichzeitig durch Streaming-Skriptverarbeitung analysiert und kompiliert werden können.

Bitte beachten Sie, dass sich Ihre Website wahrscheinlich von der Facebook-Website unterscheidet. Sie haben wahrscheinlich keine Anwendungen, die lange Zeit geöffnet bleiben (z. B. eine Facebook-Website oder die Google Mail-Oberfläche), und wenn Sie mit ihnen arbeiten, kann das Herunterladen derart schwerwiegender Skriptmengen mit einem Desktop-Browser gerechtfertigt sein. Trotzdem können wir eine allgemeine Empfehlung abgeben, die für alle Projekte fair ist. Es liegt in der Tatsache, dass es sich lohnt, den Anwendungscode in bescheidene Bundles zu zerlegen, und dass Sie diese Bundles nur herunterladen müssen, wenn sie benötigt werden.

Obwohl die meisten Arbeiten zum Parsen und Kompilieren von JS-Code mithilfe von Streaming-Tools in einem Hintergrundthread ausgeführt werden können, erfordern einige Vorgänge immer noch einen Hauptthread. Wenn der Haupt-Thread mit etwas beschäftigt ist, kann die Seite nicht auf Benutzerinteraktionen reagieren. Daher wird empfohlen, die Auswirkungen des Ladens und Ausführens von JS-Code auf UX-Sites zu berücksichtigen.

Beachten Sie, dass nicht alle JavaScript-Engines und -Browser jetzt Skripte streamen und deren Laden optimieren. Trotzdem hoffen wir, dass die oben beschriebenen allgemeinen Optimierungsprinzipien die Benutzererfahrung bei der Arbeit mit Websites verbessern können, die in einem der vorhandenen Browser angezeigt werden.

JSON-Analysepreis


Das Parsen von JSON-Code kann viel effizienter sein als das Parsen von JavaScript-Code. Die Sache ist, JSON-Grammatik ist viel einfacher als JavaScript-Grammatik. Dieses Wissen kann angewendet werden, um die Vorbereitungsgeschwindigkeit für die Arbeit von Webanwendungen zu verbessern, die große Konfigurationsobjekte (wie Redux-Repositorys) verwenden, deren Struktur JSON-Code ähnelt. Als Ergebnis stellt sich heraus, dass Sie die Daten nicht als im Code eingebettete Objektliterale darstellen, sondern als Zeichenfolgen von JSON-Objekten darstellen und diese Objekte zur Laufzeit analysieren können.

Der erste Ansatz mit JS-Objekten sieht folgendermaßen aus:

 const data = { foo: 42, bar: 1337 }; //  

Der zweite Ansatz, bei dem JSON-Zeichenfolgen verwendet werden, umfasst die Verwendung solcher Konstrukte:

 const data = JSON.parse('{"foo":42,"bar":1337}'); //  

Da Sie die JSON-Zeichenfolgenverarbeitung nur einmal ausführen müssen, ist der Ansatz, der JSON.parse verwendet, viel schneller als die Verwendung von JavaScript-Objektliteralen. Besonders - beim "kalten" Laden von Seiten. Es wird empfohlen, JSON-Zeichenfolgen zu verwenden, um Objekte darzustellen, die bei 10 KB beginnen. Wie bei jedem Leistungstipp sollte dieser Tipp jedoch nicht gedankenlos befolgt werden. Bevor diese Technik zur Darstellung von Daten in der Produktion angewendet wird, müssen Messungen durchgeführt und die tatsächlichen Auswirkungen auf das Projekt bewertet werden.

Die Verwendung von Objektliteralen als Speicher für große Datenmengen stellt eine weitere Bedrohung dar. Der Punkt ist, dass das Risiko besteht, dass solche Literale zweimal verarbeitet werden können:

  1. Der erste Verarbeitungsdurchlauf wird mit vorläufiger Analyse des Literal durchgeführt.
  2. Der zweite Ansatz wird während des "faulen" Parsens des Literals durchgeführt.

Sie können den ersten Durchgang der Verarbeitung von Objektliteralen nicht loswerden. Glücklicherweise kann der zweite Durchgang vermieden werden, indem Objektliterale auf der obersten Ebene oder in PIFE platziert werden .

Was ist mit dem Parsen und Kompilieren von Code bei wiederholten Besuchen von Websites?


Dank der V8-Funktionen zum Zwischenspeichern von Code und Bytecode ist es möglich, die Site-Leistung für diese Fälle zu optimieren, wenn Benutzer sie mehrmals besuchen. Wenn ein Skript zum ersten Mal vom Server angefordert wird, lädt Chrome es herunter und übergibt V8 zur Kompilierung. Der Browser speichert außerdem die Datei dieses Skripts im Festplatten-Cache. Wenn die zweite Anforderung zum Herunterladen derselben JS-Datei ausgeführt wird, nimmt Chrome sie aus dem Browser-Cache und übergibt V8 erneut zum Kompilieren. Diesmal wird der kompilierte Code jedoch serialisiert und als Metadaten an die zwischengespeicherte Skriptdatei angehängt.


Code-Caching-System in V8

Wenn das Skript zum dritten Mal angefordert wird, nimmt Chrome sowohl die Datei als auch ihre Metadaten aus dem Cache und überträgt dann beide V8. V8 deserialisiert die Metadaten und überspringt daher möglicherweise den Kompilierungsschritt. Das Zwischenspeichern von Code wird ausgelöst, wenn Besuche auf der Site innerhalb von 72 Stunden durchgeführt werden. Chrome verwendet auch die Strategie des gierigen Code-Caching, wenn ein Servicemitarbeiter zum Zwischenspeichern von Skripten verwendet wird. Details zum Code-Caching finden Sie hier .

Zusammenfassung


Im Jahr 2019 sind das Laden und Ausführen von Skripten die wichtigsten Leistungsengpässe für Webseiten. Um die Situation zu verbessern, bemühen Sie sich, synchrone (integrierte) Skripte kleiner Größe zu verwenden, die für die Organisation der Benutzerinteraktion mit dem Teil der Seite erforderlich sind, der für ihn unmittelbar nach dem Laden sichtbar ist. Es wird empfohlen, Skripte, die zur Wartung anderer Teile der Seiten verwendet werden, im verzögerten Modus zu laden. Brechen Sie große Bündel in kleine Stücke. Dies erleichtert die Implementierung einer Strategie für die Arbeit mit Code, in deren Anwendung der Code nur geladen wird, wenn er benötigt wird und nur dort, wo er benötigt wird. Dadurch werden die Funktionen von V8 maximiert, die auf die parallele Verarbeitung des Codes abzielen.

Wenn Sie mobile Projekte entwickeln, sollten Sie sicherstellen, dass diese so wenig JS-Code wie möglich verwenden. Diese Empfehlung beruht auf der Tatsache, dass mobile Geräte normalerweise in relativ langsamen Netzwerken arbeiten. Solche Geräte können außerdem hinsichtlich des verfügbaren RAM und der verfügbaren Prozessorressourcen begrenzt sein. Versuchen Sie, ein Gleichgewicht zwischen der Zeit zu finden, die für die Vorbereitung der vom Netzwerk heruntergeladenen Skripte und die Verwendung des Caches erforderlich ist. Dadurch wird der Umfang des Parsens und Kompilierens von Code außerhalb des Hauptthreads maximiert.

Liebe Leser! Optimieren Sie Ihre Webprojekte unter Berücksichtigung der Besonderheiten der JS-Code-Verarbeitung durch moderne Browser?

Source: https://habr.com/ru/post/de459296/


All Articles