Analyse der CPU-Auslastung einzelner JavaScript-Komponenten

Lassen Sie uns ein wenig darüber sprechen, wie viel CPU-Ressourcen der JavaScript-Code der Anwendung verbraucht. Gleichzeitig schlage ich vor, unser Gespräch auf Komponenten aufzubauen - die Grundbausteine ​​der Anwendung. Mit diesem Ansatz können alle Bemühungen zur Verbesserung der Produktivität (oder zur Ermittlung der Ursachen für Programmverlangsamungen) auf (hoffentlich) kleine, autarke Teile des Projekts konzentriert werden. Gleichzeitig gehe ich davon aus, dass Ihre Front-End-Anwendung, wie viele andere moderne Projekte, durch Zusammenfügen kleiner Fragmente der Schnittstelle erstellt wurde, die für die wiederholte Verwendung geeignet sind. Wenn dies nicht der Fall ist, können wir unsere Überlegungen auf eine andere Anwendung übertragen. Sie müssen jedoch Ihren eigenen Weg finden, um Ihren umfangreichen Code in Fragmente zu unterteilen, und Sie müssen darüber nachdenken, wie Sie diese Fragmente analysieren können.



Warum ist das nötig?


Warum den CPU-Verbrauch mit JavaScript messen? Tatsache ist, dass die Anwendungsleistung heutzutage meistens an die Fähigkeiten des Prozessors gebunden ist. Lassen Sie mich die Worte von Steve Soders und Pat Minan aus einem Interview für Planet Performance Podcast zitieren. Beide gaben an, dass die Anwendungsleistung nicht mehr auf Netzwerkfähigkeiten oder Netzwerklatenzen beschränkt ist. Netzwerke werden immer schneller. Darüber hinaus lernten die Entwickler, Server-Textantworten mit GZIP (oder besser mit brotli) zu komprimieren, und fanden heraus, wie Bilder optimiert werden können. Es ist alles sehr einfach.

Der Leistungsengpass moderner Anwendungen sind Prozessoren. Dies gilt insbesondere für die mobile Umgebung. Gleichzeitig sind unsere Erwartungen an die interaktiven Funktionen moderner Webanwendungen gestiegen. Wir erwarten, dass die Schnittstellen solcher Anwendungen sehr schnell und reibungslos funktionieren. Und das alles erfordert immer mehr JavaScript-Code. Darüber hinaus müssen wir uns daran erinnern, dass 1 MB Bild nicht mit 1 MB JavaScript identisch ist. Die Bilder werden nach und nach heruntergeladen und die Anwendung löst zu diesem Zeitpunkt andere Probleme. JavaScript-Code ist jedoch häufig eine solche Ressource, ohne die sich die Anwendung als nicht funktionsfähig herausstellt. Um das Funktionieren einer modernen Anwendung zu gewährleisten, sind große Mengen an JS-Code erforderlich, die analysiert und ausgeführt werden müssen, bevor sie wirklich funktionieren. Und dies sind Aufgaben, die stark von den Fähigkeiten des Prozessors abhängen.

Leistungsindikator


Wir werden einen solchen Indikator für die Geschwindigkeit von Codefragmenten als die Anzahl der Prozessoranweisungen verwenden, die erforderlich sind, um sie zu verarbeiten. Dies ermöglicht es uns, die Messungen von den Eigenschaften eines bestimmten Computers und von dem Zustand, in dem er sich zum Zeitpunkt der Messung befindet, zu trennen. Zeitbasierte Metriken (wie TTI) haben zu viel "Rauschen". Sie hängen vom Status der Netzwerkverbindung sowie von allen anderen Ereignissen ab, die zum Zeitpunkt der Messung auf dem Computer auftreten. Beispielsweise können einige Skripte, die während des Ladens der untersuchten Seite ausgeführt werden, oder Viren, die mit etwas in den Hintergrundprozessen beschäftigt sind, die zeitlichen Leistungsindikatoren beeinflussen. Das Gleiche gilt für Browsererweiterungen, die viel Systemressourcen verbrauchen und die Seite verlangsamen können. Bei der Berechnung der Anzahl der Prozessorbefehle spielt die Zeit dagegen keine Rolle. Solche Indikatoren können, wie Sie gleich sehen werden, wirklich stabil sein.

Idee


Hier ist die Idee, die unserer Arbeit zugrunde liegt: Wir müssen ein „Labor“ schaffen, in dem der Code gestartet und überprüft wird, wenn Änderungen daran vorgenommen werden. Mit "Labor" meine ich einen normalen Computer, vielleicht den, den Sie ständig benutzen. Versionskontrollsysteme stellen uns Haken zur Verfügung, mit denen Sie bestimmte Ereignisse abfangen und bestimmte Prüfungen durchführen können. Selbstverständlich können Messungen im „Labor“ nach dem Festschreiben durchgeführt werden. Aber Sie wissen wahrscheinlich, dass Änderungen an Code, der die Festschreibungsphase erreicht hat, langsamer durchgeführt werden als an Code, der geschrieben wird (wenn überhaupt). Gleiches gilt für die Korrektur des Beta-Codes des Produkts und des Codes, der in die Produktion gelangt ist.

Wir müssen jedes Mal, wenn der Code geändert wird, seine Leistung vergleichen, bevor und nachdem Änderungen vorgenommen werden. Dabei sind wir bestrebt, Komponenten isoliert zu untersuchen. Auf diese Weise können wir die Probleme klar erkennen und genau wissen, wo sie auftreten.

Das Gute ist, dass solche Studien in einem echten Browser durchgeführt werden können, zum Beispiel mit Puppeteer. Mit diesem Tool können Sie den Browser ohne Benutzeroberfläche über Node.js steuern.

Suchcode für Recherchen


Um den Code für die Studie zu finden, können wir auf jeden Styleguide oder auf jedes Design-System verweisen. Im Allgemeinen sind wir mit allem zufrieden, was kurze, isolierte Beispiele für die Verwendung von Komponenten liefert.

Was ist ein „Styleguide“? Dies ist normalerweise eine Webanwendung, die alle Komponenten oder „Bausteine“ von Benutzeroberflächenelementen demonstriert, die dem Entwickler zur Verfügung stehen. Es kann sich entweder um eine bestimmte Bibliothek mit Komponenten von Drittanbietern oder um eine von Ihnen erstellte Bibliothek handeln.

Bei der Suche nach solchen Projekten im Internet bin ich kürzlich auf einen Diskussionsthread auf Twitter gestoßen, in dem es um relativ neue Bibliotheken von React-Komponenten ging. Ich habe mir einige der dort erwähnten Bibliotheken angesehen.

Es überrascht nicht, dass moderne, qualitativ hochwertige Bibliotheken über eine Dokumentation verfügen, die Arbeitscodebeispiele enthält. Hier sind einige Bibliotheken und Button Komponenten aufgeführt, die mit ihren Mitteln implementiert wurden. Die Dokumentation zu diesen Bibliotheken enthält Beispiele für die Verwendung dieser Komponenten. Wir sprechen über die Chakra-Bibliothek und die Semantic UI React-Bibliothek.


Button Component Chakra Dokumentation


Button Semantic UI React Documentation

Genau das brauchen wir. Dies sind Beispiele, deren Code wir auf den Verbrauch von Prozessorressourcen untersuchen können. Ähnliche Beispiele finden Sie in der Dokumentation oder in Codekommentaren im JSDoc-Stil. Wenn Sie Glück haben, finden Sie solche Beispiele möglicherweise als separate Dateien, beispielsweise in Form von Unit-Test-Dateien. Sicher wird es so sein. Schließlich schreiben wir alle Unit-Tests. Richtig?

Dateien


Stellen Sie sich zur Veranschaulichung der beschriebenen Methode der Leistungsanalyse vor, dass sich in der Bibliothek, die wir untersuchen, eine Button Komponente befindet, deren Code in der Datei Button.js enthalten ist. Button-example.js . Wir müssen eine Art Testseite erstellen, in deren Umgebung Testcode ausgeführt werden kann. Sowas wie test.html .

Komponente


Hier ist eine einfache Button Komponente. Ich verwende React hier, aber Ihre Komponenten können mit jeder für Sie geeigneten Technologie geschrieben werden.

 import React from 'react'; const Button = (props) =>  props.href    ? <a {...props} className="Button"/>    : <button {...props} className="Button"/> export default Button; 

Beispiel


Und hier ist ein Beispiel für die Verwendung der Button Komponente. Wie Sie sehen, gibt es in diesem Fall zwei Komponentenoptionen, die unterschiedliche Eigenschaften verwenden.

 import React from 'react'; import Button from 'Button'; export default [  <Button onClick={() => alert('ouch')}>    Click me  </Button>,  <Button href="https://reactjs.com">    Follow me  </Button>, ] 

Test


Hier ist die Seite test.html , auf der alle Komponenten geladen werden können. Beachten Sie, dass die Methode das performance aufruft. Mit ihrer Hilfe schreiben wir auf Anfrage in die Chrome-Leistungsprotokolldatei. Sehr bald werden wir diese Aufzeichnungen verwenden.

 const examples =  await import(location.hash + '-example.js'); examples.forEach(example =>  performance.mark('my mark start');  ReactDOM.render(<div>{example}</div>, where);  performance.mark('my mark end');  performance.measure(    'my mark', 'my mark start', 'my mark end'); ); 

Testläufer


Um eine Testseite in Chrome zu laden, können wir die Puppeteer Node.js-Bibliothek verwenden, die uns den Zugriff auf die API zur Verwaltung des Browsers ermöglicht. Sie können diese Bibliothek auf jedem Betriebssystem verwenden. Es hat eine eigene Kopie von Chrome, kann aber auch verwendet werden, um mit einer Instanz von Chrome oder Chromium verschiedener Versionen zu arbeiten, die bereits auf dem Computer des Entwicklers vorhanden sind. Chrome kann so gestartet werden, dass sein Fenster nicht sichtbar ist. Tests werden automatisch durchgeführt, während der Entwickler das Browserfenster nicht sehen muss. Chrome kann im normalen Modus gestartet werden. Dies ist nützlich für Debugging-Zwecke.

Hier ist ein Beispiel für ein Node.js-Skript, das über die Befehlszeile ausgeführt wird, auf der eine Testseite geladen und Daten in eine Leistungsprotokolldatei geschrieben werden. Alles, was im Browser zwischen den tracing.start() und end() passiert, wird in die Datei trace.json .

 import pup from 'puppeteer'; const browser = await pup.launch(); const page = await browser.newPage(); await page.tracing.start({path: 'trace.json'}); await page.goto('test.html#Button'); await page.tracing.stop(); await browser.close(); 

Der Entwickler kann die "Details" der Leistungsdaten verwalten, indem er die "Kategorien" der Ablaufverfolgung angibt. Die Liste der verfügbaren Kategorien wird angezeigt, wenn Sie unter chrome://tracing zu Chrome chrome://tracing , auf Record klicken und im angezeigten Fenster den Abschnitt Edit categories öffnen.


Konfigurieren der Zusammensetzung von Daten, die in das Leistungsprotokoll geschrieben werden

Ergebnisse


Nachdem die Testseite mit Puppeteer untersucht wurde, können Sie die Ergebnisse der Leistungsmessungen analysieren, indem Sie zum Browser unter trace.json chrome://tracing trace.json und die gerade aufgezeichnete trace.json Datei herunterladen.


Trace.json Visualisierung

Hier sehen Sie die Ergebnisse des Aufrufs der Methode performance.measure('my mark') . Der Aufruf von measure() dient nur zu Debugging-Zwecken, falls der Entwickler die Datei trace.json öffnen und trace.json möchte. Alles, was mit der Seite passiert ist, ist im Block my mark .

Hier ist ein trace.json :


Fragment der trace.json-Datei

Um herauszufinden, was wir brauchen, ist es ausreichend, den Indikator der Anzahl der Prozessoranweisungen ( ticount ) des Start Markers vom selben Indikator des End Markers zu subtrahieren. Auf diese Weise können Sie herausfinden, wie viele Prozessoranweisungen erforderlich sind, um die Komponente im Browser anzuzeigen. Dies ist dieselbe Zahl, mit der Sie feststellen können, ob eine Komponente schneller oder langsamer geworden ist.

Der Teufel steckt im Detail


Jetzt haben wir nur Indikatoren gemessen, die die erste Ausgabe auf der Seite einer einzelnen Komponente kennzeichnen. Und nichts mehr. Es ist unbedingt erforderlich, Indikatoren zu messen, die sich auf die kleinste auszuführende Codemenge beziehen. Auf diese Weise können Sie den Geräuschpegel verringern. Der Teufel steckt im Detail. Je kleiner die gemessene Leistung ist, desto besser. Nach den Messungen muss aus den erhaltenen Ergebnissen entfernt werden, was außerhalb des Einflusses des Entwicklers liegt. Zum Beispiel Daten, die sich auf Speicherbereinigungsvorgänge beziehen. Die Komponente steuert solche Vorgänge nicht. Wenn sie ausgeführt werden, bedeutet dies, dass der Browser beim Rendern der Komponente beschlossen hat, sie selbst zu starten. Infolgedessen sollten die Prozessorressourcen, die für die Speicherbereinigung verwendet wurden, aus den endgültigen Ergebnissen entfernt werden.

Der Datenblock für die Garbage Collection (dieser „Datenblock“ wird genauer gesagt als „Ereignis“ bezeichnet) heißt V8.GCScavenger . Sein tidelta sollte von der Anzahl der Prozessoranweisungen abgezogen werden, die zum Rendern der Komponente verwendet werden. Hier ist die Dokumentation für Trace-Ereignisse. Es stimmt, es ist veraltet und enthält keine Informationen zu den erforderlichen Indikatoren:

  • tidelta - Die Anzahl der Prozessoranweisungen, die zum Verarbeiten eines Ereignisses erforderlich sind.
  • ticount - Die Anzahl der Anweisungen zum Starten des Ereignisses.

Sie müssen sehr vorsichtig sein, was wir messen. Browser sind hochintelligente Einheiten. Sie optimieren Code, der mehrmals ausgeführt wird. In der nächsten Grafik sehen Sie die Anzahl der Prozessoranweisungen, die für die Ausgabe einer bestimmten Komponente erforderlich sind. Der erste Rendervorgang erfordert die meisten Ressourcen. Nachfolgende Operationen verursachen eine viel geringere Belastung des Prozessors. Dies sollte bei der Analyse der Codeleistung berücksichtigt werden.


10 Renderoperationen derselben Komponente

Hier ist ein weiteres Detail: Wenn die Komponente einige asynchrone Vorgänge ausführt (z. B. setTimeout() oder fetch() ), wird die durch den asynchronen Code verursachte Systemlast nicht berücksichtigt. Vielleicht ist es gut. Vielleicht ist es schlecht. Wenn Sie die Leistung solcher Komponenten untersuchen, ziehen Sie eine separate Untersuchung des asynchronen Codes in Betracht.

Starkes Signal


Wenn Sie verantwortungsbewusst vorgehen, um das Problem zu lösen, was genau gemessen wird, erhalten Sie ein wirklich stabiles Signal, das die Auswirkung von Änderungen auf die Leistung widerspiegelt. Ich mag die Glätte der Linien in der nächsten Grafik.


Stabile Messergebnisse

Das untere Diagramm zeigt die Messergebnisse von 10 Renderoperationen eines einfachen <span> -Elements in React. In diesen Ergebnissen ist nichts anderes enthalten. Es stellt sich heraus, dass für diesen Vorgang 2,15 bis 2,2 Millionen Prozessorbefehle erforderlich sind. Wenn Sie das <span> in das <p> , benötigen Sie für die Ausgabe eines solchen Designs etwa 2,3 Millionen Anweisungen. Diese Genauigkeit fällt mir auf. Wenn ein Entwickler den Leistungsunterschied sieht, der auftritt, wenn ein einzelnes <p> -Element zu einer Seite hinzugefügt wird, bedeutet dies, dass der Entwickler über ein wirklich leistungsfähiges Tool verfügt.

Wie genau solche Messungen dargestellt werden, ist Sache des Entwicklers. Wenn er keine solche Genauigkeit benötigt, kann er immer die Renderleistung größerer Fragmente messen.

Zusätzliche Leistungsinformationen


Jetzt, da dem Entwickler ein System zum Auffinden numerischer Indikatoren zur Verfügung steht, die die Leistung kleinster Codefragmente sehr genau charakterisieren, kann der Entwickler mit diesem System verschiedene Probleme lösen. Mit performance.mark() Sie also zusätzliche nützliche Informationen in trace.json schreiben. Sie können den Mitgliedern des Entwicklungsteams mitteilen, was gerade passiert und was die Anzahl der Prozessoranweisungen erhöht, die zum Ausführen von Code erforderlich sind. Sie können in die Leistungsberichte Informationen zur Anzahl der DOM-Knoten oder zur Anzahl der von React ausgeführten Schreibvorgänge im DOM aufnehmen. In der Tat können Sie hier Informationen über eine Menge anzeigen. Sie können die Anzahl der Seitenlayout-Neuberechnungen zählen. Mit Puppeteer können Sie Screenshots von Seiten machen und vergleichen, wie die Benutzeroberfläche vor und nach Änderungen aussieht. Manchmal ist die Erhöhung der Anzahl der zum Anzeigen einer Seite erforderlichen Prozessoranweisungen völlig überraschend. Zum Beispiel, wenn der neuen Version der Seite 10 Schaltflächen und 12 Felder zum Bearbeiten und Formatieren von Text hinzugefügt werden.

Zusammenfassung


Ist es möglich, dass jeder, der hier diskutiert wurde, es heute benutzt? Ja du kannst Dazu benötigen Sie Chrome Version 78 oder höher. Wenn trace.json über ticount und tidelta , steht Ihnen das oben tidelta zur Verfügung. Frühere Versionen von Chrome tun dies nicht.

Leider können auf der Mac-Plattform keine Informationen über die Anzahl der Prozessoranweisungen abgerufen werden. Ich habe Windows noch nicht ausprobiert, daher kann ich zu diesem Betriebssystem nichts Genaues sagen. Im Allgemeinen - unsere Freunde sind Unix und Linux.

Es ist zu beachten, dass der Browser nur dann Informationen zu Prozessoranweisungen bereitstellen kann, wenn Sie ein paar Flags verwenden. --enable-thread-instruction-count handelt es sich um --no-sandbox --enable-thread-instruction-count und --enable-thread-instruction-count . So übergeben Sie sie an einen von Puppeteer gestarteten Browser:

 await puppeteer.launch({  args: [    '--no-sandbox',    '--enable-thread-instruction-count',  ]}); 

Hoffentlich können Sie jetzt die Leistungsanalyse Ihrer Webanwendung auf die nächste Stufe heben.

Sehr geehrte Leser! Planen Sie, die hier vorgestellte Methodik zur Analyse der Leistung von Webprojekten zu verwenden?


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


All Articles