Unter Leistung wird normalerweise die Anzahl der Vorgänge für ein bestimmtes Zeitintervall verstanden. Je mehr davon, desto besser. Eine solche Definition und der gesamte Ansatz sind jedoch für das Front-End kaum anwendbar, da jeder Benutzer sein eigenes „Front-End“ hat. Darüber möchte ich sprechen, was „dort“ passiert, mit dem Benutzer auf der anderen Seite, in der Realität und nicht auf Ihrem Top-MacBook.
Darüber hinaus werde ich versuchen, kurz die allgemeinen Regeln für die Optimierung des Codes und einige Fehler zu betrachten, die es wert sind, beachtet zu werden. Ich erzähle Ihnen auch von einem
Tool , das nicht nur bei der Profilerstellung hilft, sondern auch sofort eine Reihe grundlegender Messdaten zur Leistung Ihrer Anwendung sammelt (und ich hoffe, Sie lesen diesen Beitrag bis zum Ende).
Zuerst werden wir bestimmen, was Front-End-Leistung ist, und dann werden wir fortfahren, wie man sie misst. Wie gesagt, wir werden einige Operationen / Sek. Nicht messen, wir brauchen echte Daten, die die Frage beantworten können, was genau mit unserem Projekt in jeder Phase seiner Arbeit passiert. Dazu benötigen wir die folgenden Metriken:
- Download-Geschwindigkeit;
- Zeitpunkt des ersten Renderns und der Interaktivität (Time To Interactive);
- Reaktionsgeschwindigkeit auf Benutzeraktionen;
- FPS zum Scrollen und Animieren;
- Anwendungsinitialisierung;
- Wenn Sie über ein SPA verfügen, müssen Sie die Zeit messen, die für den Wechsel zwischen Routen aufgewendet wurde.
- Speicher- und Verkehrsverbrauch;
- und ... genug für jetzt.
All dies sind grundlegende Metriken, ohne die es unmöglich ist zu verstehen, was genau im Front-End passiert. Und das nicht nur am Frontend, sondern in Wirklichkeit beim Endbenutzer. Um diese Metriken zu erfassen, müssen Sie zunächst lernen, wie sie gemessen werden. Erinnern wir uns also daran, welche Methoden für die Leistungsanalyse verfügbar sind.
Das erste, was Sie anfangen müssen, ist natürlich die Leistungs-API.
Performance.timing , mit dem Sie herausfinden können, wie lange ein Benutzer zum Öffnen Ihres Projekts gebraucht hat. Die Leistungs-API deckt jedoch nur einen Teil der Metrik ab, der Rest muss von uns selbst gemessen werden, und dafür verfügen wir über die folgenden Tools:
In diesem Moment wurde mir klar, dass Sie ein Werkzeug sehen müssen, das die oben genannten Vorteile kombiniert und, wenn möglich, keine Minuspunkte aufweist. Da war also
PerfKeeper .
Perfkeeper
- Volle Kontrolle über Anfang und Ende.
- Sie können an den Server senden.
- Es wird in der Konsole angezeigt.
- Unterstützt DevTools -> Leistung -> Benutzer-Timing.
- Es gibt eine Gruppierung.
- Es gibt eine Farbcodierung (sowie Maßeinheiten, d. H. Sie können nicht nur die Zeit messen).
- Unterstützt Erweiterungen.
Jetzt werde ich die API hier nicht malen, ich habe keine
Dokumentation dafür geschrieben, und der Artikel handelt nicht davon, aber ich werde fortfahren, wie Metriken gesammelt werden.
Seiten-Download-Geschwindigkeit
Wie bereits erwähnt, können Sie die Download-Geschwindigkeit unter
performance.timing ermitteln . Auf diese Weise können Sie den gesamten Zyklus vom Beginn des Seitenladens (Zeit zum Auflösen von DNS, Installieren von HTTP Handshake, Verarbeiten der Anforderung) bis zum vollständigen Laden der Seite (DomReady und OnLoad) ermitteln:

Als Ergebnis sollten Sie die folgenden Metriken erhalten:
Ein Beispiel für die Navigationserweiterung für @ perf-tools / keeper .Das reicht aber nicht, wir haben nur die Grundwerte und wissen immer noch nicht, was genau so viel Zeit gekostet hat. Um dies herauszufinden, müssen Sie auch HTML-Metriken eingeben.
Wie ich bereits sagte, werde ich Beispiele mit
PerfKeeper zeigen. Das erste, was zu tun ist, ist Inline in
<hed/>
PerfKeeper selbst (2,5 KB) und weiter:

Infolgedessen sehen Sie solche Schönheit in der Konsole:

Dies ist eine klassische Großvater-Messmethode, die zu 100% funktioniert. Die Welt steht jedoch nicht still, und für genauere Messungen verfügen wir jetzt über die
Resource Timing API (und wenn sich die Ressourcen in einer separaten
Timing-Allow-Origin- Domäne befinden, um Ihnen zu helfen).
Und hier lohnt es sich, über klassische Fehler beim ersten Laden der Seite zu sprechen, nämlich:
- Mangel an GZip und HTTP / 2 (ja, das ist immer noch üblich);
- unangemessene Verwendung von Schriftarten (manchmal wird eine Schrift nur wegen einer Kopfzeile oder sogar einer Telefonnummer in der Fußzeile 0_o verbunden);
- CSS / JS-Bundles zu allgemein.
Möglichkeiten zum Optimieren des Seitenladens:
- Verwenden Sie Brotli (oder sogar SDCH) anstelle von GZip und aktivieren Sie HTTP / 2.
- Sammeln Sie nur das erforderliche CSS (kritisch) und vergessen Sie CSSO nicht .
- Minimieren Sie die Größe des JS-Bündels, indem Sie das minimale CORE-Bündel trennen, und laden Sie den Rest nach Bedarf, d. h. asynchron;
- Laden Sie JS und CSS im nicht blockierenden Modus, indem Sie dynamisch
/> <sript src="..."/>
Laden Sie JS idealerweise nach dem Hauptinhalt. - Verwenden Sie SVG anstelle von PNG. In Kombination mit JS werden redundante XML -Dateien entfernt (z. B. font-awesome ).
- Verwenden Sie das verzögerte Laden sowohl für Bilder als auch für Iframes (außerdem wird in naher Zukunft native Unterstützung angezeigt).
Erste Renderzeit und Interaktivität (TTI)
Die nächste Phase nach dem Laden ist der Moment, in dem der Benutzer das Ergebnis sah und die Benutzeroberfläche in den interaktiven Modus überging. Dafür benötigen wir
Performance Paint Timing und
PerformanceObserver .
Der erste ist einfach, wir rufen
performance.getEntriesByType('paint')
und erhalten zwei Metriken:
- First-Paint - das erste Rendering;
- first-contentful-paint - und das vollständige erste Rendern.
Ein Beispiel für die Farberweiterung für @ perf-tools / keeper .Aber mit der nächsten Metrik, Time To Interactive, ist es etwas interessanter. Es gibt keine genaue Möglichkeit, festzustellen, wann Ihre Anwendung interaktiv wurde, d. H. für den Benutzer zugänglich, dies kann jedoch indirekt durch das Fehlen von
Langzeitaufgaben verstanden werden :
Ein Beispiel für die Leistungserweiterung für @ perf-tools / keeper .Zusätzlich zu diesen grundlegenden Metriken wird auch Ihre Metrik für die Anwendungsbereitschaft benötigt, d. H. Irgendwo in Ihrem Code sollte es so sein:
Import { system } from '@perf-tools/keeper'; export function applicationBoot(el, data) { const app = new Application(el, data);
Antwortrate auf Benutzeraktionen
Es gibt ein riesiges Feld für Metriken, und sie sind sehr individuell. Daher werde ich auf zwei grundlegende Metriken eingehen, die für jedes Projekt geeignet sind, nämlich:
Erstes Ereignis - Der Zeitpunkt des ersten Ereignisses, z. B. der erste Klick (Teilen, wo der Benutzer gestoßen hat). Diese Metrik ist besonders relevant für alle Arten von Suchergebnissen, eine Liste von Produkten, Newsfeeds usw. Damit können Sie steuern, wie sich die Reaktionszeit und der Benutzerfluss aus Ihren Aktionen (Änderungen in: Design / neue Funktionen / Optimierungen usw.) ändern
Ein Beispiel für die Leistungserweiterung für @ perf-tools / keeper .Latenz - Verzögerung bei der Verarbeitung einiger Ereignisse, z. B.
click
,
input
,
submit
,
scroll
usw.
Um die Verzögerung zu messen, hängen Sie einfach den Ereignishandler mit
capture = true
an das
window
und verwenden Sie
requestAnimationFrame
um die Differenz
requestAnimationFrame
berechnen. Dies ist die Verzögerung:
window.addEventListener(eventType, ({target}) => { const start = now(); requestAnimationFrame(() => { const latency = now() - start; if (latency >= minLatency) {
Ein Beispiel für die Leistungserweiterung für @ perf-tools / keeper, die funktioniert, wenn eine Fibonacci-Zahl mit einem Klick berechnet wird.FPS beim Scrollen und Animieren
Dies ist die interessanteste Metrik. Sie wird normalerweise über
requestAnimationFrame
gemessen. Wenn Sie eine konstante FPS-Messung durchführen müssen, reicht das klassische
FPSMeter aus (obwohl dies zu optimistisch ist). Es funktioniert jedoch überhaupt nicht, wenn Sie die Laufruhe beim Scrollen von Seiten messen müssen, weil Er braucht ein Aufwärmen. Und dann bin ich auf einen sehr
interessanten Weg gestoßen .
Genialerweise erstellen wir einfach ein transparentes Div (1x1px), fügen einen
transition: left 300ms linear
und führen es von einer Ecke zur anderen aus. Während der Animation
requestAnimationFrame
über
requestAnimationFrame
die tatsächliche Linke. Wenn sich die neue Länge von der vorherigen unterscheidet, Erhöhen Sie dann die Anzahl der gerenderten Frames (andernfalls haben wir einen FPS-Drawdown).
Und das ist noch nicht alles. Wenn Sie FF verwenden, gibt es einfach
mozPaintCount , das für die Anzahl der gerenderten Frames verantwortlich ist, d. H. Wir erinnern uns an „DO“ und berechnen am
transitionend
die Differenz.
Insgesamt wissen wir ohne Aufwärmen sicher, ob der Browser den Frame neu gezeichnet hat oder nicht.
Sie versprechen bald eine normale API:
http://wicg.imtqy.com/frame-timing/Ein Beispiel für die fps- Erweiterung für @ perf-tools / keeper .Bildlaufoptimierung:
- Am einfachsten ist es, nichts am
requestAnimationFrame
zu tun oder die Ausführung durch requestAnimationFrame
oder sogar requestIdleCallback
. - Verwenden Sie
pointer-events: none
sehr vorsichtig pointer-events: none
, das Ein- und Ausschalten kann den gegenteiligen Effekt haben. Daher ist es besser, ein A / B-Experiment mit und ohne pointer-events
durchzuführen. - Vergessen Sie nicht die virtualisierten Listen, fast alle View-Engines verfügen jetzt über solche Komponenten. Seien Sie jedoch vorsichtig, die Elemente einer solchen Liste sollten so einfach wie möglich sein, oder verwenden Sie "Dummies", die nach Abschluss des Bildlaufs durch echte Elemente ersetzt werden. Wenn Sie selbst eine virtualisierte Liste schreiben, dann kein inneres HTML und vergessen Sie nicht das DOM-Recycling (in diesem Fall erstellen Sie nicht für jedes Niesen DOM-Elemente, sondern verwenden sie wieder).
Anwendungsinitialisierung
Es gibt nur eine Regel: Details, damit Sie genau beantworten können, wie viel Zeit von der Initialisierung der Anwendung bis zum endgültigen Start benötigt wurde. Daher sollten Sie mindestens die folgenden Metriken erhalten:
- wie viel Zeit es dauerte, um jede Sucht zu lösen;
- Zeit, um Daten für den Antrag zu erhalten und vorzubereiten;
- Renderanwendung mit Detaillierung nach Blöcken.
Das heißt, Am Ausgang sollten Sie solche Metriken erhalten, anhand derer Sie genau verfolgen können, in welcher Phase Ihr Drawdown stattfindet.
ArbeitsbeispielKonsole
Benutzer-Timing 
Wenn Sie über ein SPA verfügen, müssen Sie die Routing-Zeit messen
Erstens sollte es eine allgemeine Metrik für die Bewertung der Leistung (Laufzeit auf der Route) als Ganzes geben, aber es ist auch erforderlich, eine Metrik für jede Route zu haben (zum Beispiel haben wir eine "Liste der Threads", "Lesen eines Threads", "Suchen" usw.). d.) sollte die Metrik selbst in Metriken unterteilt werden:
- Daten empfangen (mit einer Aufschlüsselung der Daten)
- Rendern
- Gesamtanwendung
- Blöcke (bei uns zum Beispiel: "Linke Spalte" (auch bekannt als "Liste der Ordner"), "Intelligente Suchleiste", "Liste der Buchstaben" und dergleichen)
Ohne all dies ist es unmöglich zu verstehen, wo die Probleme beginnen, daher haben wir viele
startTime
endTime
Module mit Timings (zum Beispiel hat dasselbe Modul für XHR
startTime
und
endTime
, die automatisch protokolliert werden).
Diese Metriken reichen jedoch nicht aus, um das Geschehen angemessen zu bewerten. Sie sind zu allgemein, weil Wir sprechen über SPA, dann haben Sie definitiv eine Art Laufzeit-Cache (um nicht wieder zum Server zu gehen, wenn Sie bereits dort waren), sodass unsere Metriken weiter in Routing mit und ohne Cache unterteilt sind. Insbesondere in unserem Fall teilen wir die Metrik jedoch durch die Anzahl der darin enthaltenen Entitäten. Mit anderen Worten, Sie können die "Thread" -Ansicht nicht mit 1, 5, 10 oder mehr als 100 Buchstaben in einer Metrik hinzufügen. Wenn also eine Liste angezeigt wird, müssen Sie Haltepunkte auswählen und die Metrik weiter trennen.
Speicher- und Verkehrsverbrauch
Beginnen wir mit der Erinnerung . Und hier warten wir auf eine große Enttäuschung. Im Moment gibt es nur nicht standardisierte (nur Chrome) Performance.memory, die lächerlich niedrige Zahlen liefert. Dennoch müssen sie gemessen werden und beobachten, wie die Anwendung im Laufe der Zeit "fließt":
Ein Beispiel für die Speichererweiterung für @ perf-tools / keeperVerkehr Um den Datenverkehr zu zählen, benötigen Sie
Timing-Allow-Origin (wenn sich die Ressourcen in einer separaten Domäne befinden) und die
Resource Timing API . Dies hilft nicht nur, den Datenverkehr zu berechnen, sondern auch detailliert darzustellen:
- Welches Protokoll wird verwendet (HTTP / 1, HTTP / 2 usw.)?
- Arten geladener Ressourcen;
- wie lange es gedauert hat, sie herunterzuladen;
- Größe können Sie außerdem verstehen, ob die Ressource in das Netzwerk geladen oder aus dem Cache entnommen wurde.
Ein Beispiel für die Ressourcenerweiterung für @ perf-tools / keeper .Was gibt Verkehr zählen?
- Das Wichtigste ist, dass Sie das reale Bild sehen können und nicht wie bei CSS + JS üblich und darüber hinaus, wie sich dieses „Bild“ im Laufe der Zeit ändert.
- Anschließend können Sie analysieren, was genau geladen ist, Ressourcen in Gruppen aufteilen usw.
- Wie gut das Caching für Sie funktioniert.
- Gibt es Anomalien, zum Beispiel nach 15 Minuten Betrieb, zum Beispiel, der Code wurde rekursiv und lädt einige Ressourcen endlos. Die Überwachung des Datenverkehrs hilft dabei.
Nun, ein Nachholbericht meines Kollegen
Igor Druzhinin zu diesem Thema:
Bewertung der Qualität der Anwendung - Überwachung des VerkehrsverbrauchsAnalytik
Wir richten die Metriken ein und was dann? Und dann müssen sie irgendwohin geschickt werden. Und hier holen Sie entweder
Graphit von Ihnen ab oder Sie können zunächst
Google Analytics oder ähnliches für die Datenaggregation verwenden, um persönliche Vorteile zu erzielen.
Und vergessen Sie nicht, es reicht nicht aus, nur ein Diagramm zu erstellen. Für alle wichtigen Metriken sollten Perzentile vorhanden sein, mit denen Sie beispielsweise verstehen können, wie viel Prozent der Zielgruppe das Projekt für <1s, <2s, <3s, <5s, 5s + usw. lädt.
Schreiben eines Hochleistungscodes
Zuerst wollte ich hier etwas Sinnvolles schreiben, sie sagen, benutze WebWorker, vergiss nicht
requestIdleCallback
oder etwas Exotisches, zum Beispiel durch Runtime Cache über Browser-Registerkarten mit SharedWorker oder ServiceWorker (was nicht nur Caching betrifft, wenn das so ist). Aber das ist alles sehr abstrakt und viele Themen sind unmöglich, also schreiben Sie einfach Folgendes:
- Decken Sie Ihren Code zunächst mit Metriken ab, mit denen die Leistung gemessen wird.
- Glauben Sie den Benchmarks mit jsperf nicht. Die überwiegende Mehrheit von ihnen ist schlecht geschrieben und einfach aus dem Zusammenhang gerissen. Der beste Benchmark ist die tatsächliche Metrik für das Projekt, anhand derer Sie die Auswirkungen Ihrer Aktionen sehen können.
- Denken Sie an die Wahrnehmung von Produktivität oder vielmehr an das Weber-Fechner-Gesetz. Wenn Sie mit der Optimierung begonnen haben, rollen Sie die Änderungen erst aus, wenn sie mindestens um 20% besser werden. Andernfalls werden Benutzer dies einfach nicht bemerken. Das Gesetz wirkt auch in die entgegengesetzte Richtung.
- Fürchte Stammgäste, besonders die generierten. Sie können nicht nur den Browser hängen, sondern auch XSS erhalten, weshalb es in unserer Mail verboten ist, HTML mit ihnen nur über einen DOM-Bypass zu analysieren.
- Sie müssen keine Arrays verwenden, um einen Wert in die eine oder andere Gruppe
successSteps.includes(currentStep)
gibt es ein object
oder einen Set
(z. B. wird successSteps.includes(currentStep)
successSteps.hasOwnProperty(currentStep)
successSteps.includes(currentStep)
benötigt). O (1) ist alles. - Bei dem Ausdruck "Vorzeitige Optimierung ist die Wurzel aller Übel" geht es nicht darum, zu schreiben, was immer Sie wollen. Wenn Sie wissen, wie am besten, schreiben Sie optimal.
Ich werde ein paar Absätze über den Code und seine Optimierung schreibenDOM Sehr oft höre ich "Das Problem im DOM" - das ist natürlich wahr, aber da fast jeder jetzt eine Abstraktion darüber hat. Sie ist es, die zum Engpass oder vielmehr zu Ihrem Code wird, der für die Bildung der Ansicht und der Geschäftslogik verantwortlich ist.
Wenn wir zum Beispiel über das DOM sprechen, anstatt ein Fragment aus dem DOM zu löschen, ist es besser, es auszublenden oder zu entfernen. Wenn Sie noch löschen müssen, führen Sie diesen Vorgang in
requestIdleCallback
(falls möglich), oder teilen Sie den Zerstörungsprozess in zwei Phasen auf: synchron und asynchron.
Ich werde sofort eine Reservierung vornehmen. Verwenden Sie diesen Ansatz mit Bedacht, da Sie sonst auf Ihr Knie schießen können.
Wir verwenden auch eine andere interessante Technik für Listen, zum Beispiel die „Liste der Themen“. Das Wesentliche der Technik ist, dass anstelle einer globalen "Liste" und der Aktualisierung ihrer Daten für jeden "Ordner" eine "Liste der Threads" generiert wird. Wenn der Benutzer zwischen den "Ordnern" navigiert, wird eine Liste aus dem DOM entfernt (nicht gelöscht) und die andere entweder teilweise oder gar nicht aktualisiert. Und nicht alle, wie es bei der "Single List" der Fall ist.
All dies gibt eine sofortige Reaktion auf Benutzeraktionen.
Mathe Wir entfernen problemlos alle Berechnungen in Worker oder WebAssembly. Dies funktioniert seit langer Zeit.
Transpiler . Oh, viele glauben nicht einmal, dass der Code, den sie schreiben, durch den Transpiler geht. Ja, sie wissen von ihm, aber das ist alles. Aber was macht er daraus, dass sie sich nicht mehr darum kümmern. In DevTools sehen sie tatsächlich das Ergebnis der Quellkarte.
Studieren Sie daher die Werkzeuge, die Sie verwenden, z. B. hat dasselbe Babel auf dem
Spielplatz die Möglichkeit zu sehen, in welchem Code es abhängig von den ausgewählten Voreinstellungen generiert wird. Schauen Sie sich einfach dasselbe Jahr
yeild
,
await
oder
await
for of
.
Die Feinheiten der Zunge . Noch weniger Leute wissen über den Monomorphismus des Codes Bescheid oder wissen, warum das Binden langsam ist und ... Sie verwenden endlich
handleEvent
!
Daten und Prekreshing . Weniger Anfragen, mehr Caching. Außerdem verwenden wir sehr oft die Technik der "Voraussicht", wenn wir im Hintergrund Daten laden. Nach dem Rendern der „Thread-Liste“ laden wir beispielsweise N-ungelesene Threads in den aktuellen „Ordner“, sodass der Benutzer beim Klicken sofort zu „Lesen“ und nicht zu einem anderen „Loader“ wechselt. Wir verwenden eine ähnliche Technik nicht nur für Daten, sondern auch für JS. Zum Beispiel ist „Schreiben eines Briefes“ ein riesiges Bündel (wegen des Editors), und nicht alle Leute schreiben Briefe gleichzeitig. Deshalb laden wir es nach der Initialisierung der Anwendung im Hintergrund.
Lauter Ich weiß nicht warum, aber ich habe keine Artikel gesehen, in denen gelehrt wurde, wie man keinen Lader herstellt, sondern eine Präsentation der "zukünftigen" Reaktion, in der viel Zeit für dieses Problem in Suspense aufgewendet wurde. Immerhin ist die ideale Anwendung ohne Lader, wir haben lange in der Mail versucht, sie nur in Notsituationen anzuzeigen.
Im Allgemeinen haben wir eine solche Richtlinie, es gibt keine Daten, es gibt keine Ansicht, es gibt nichts, um eine Semi-Schnittstelle zu zeichnen, zuerst laden wir die Daten und erst dann "zeichnen". Aus diesem Grund verwenden wir die „Voraussicht“, wohin der Benutzer gehen und diese Daten laden wird, damit der Benutzer den Loader nicht sieht. Darüber hinaus hilft unsere Datenschicht, die dauerhaft ist, bei dieser Aufgabe sehr. Wenn Sie irgendwo an einem Ort "Thread" angefordert haben, wird beim nächsten Anfordern von einem anderen oder demselben Ort keine Anfrage gestellt. Wir nehmen Daten aus dem Laufzeit-Cache (genauer gesagt, einen Link zu den Daten). Und so sind Sammlungen von Threads in allem auch nur Links zu Daten.
Wenn Sie sich dennoch für einen Lader entscheiden, vergessen Sie nicht die Grundregeln, die Ihren Lader weniger nerven:
- Es ist nicht erforderlich, den Loader sofort zu zeigen. Zum Zeitpunkt des Sendens der Anfrage sollte eine Verzögerung von mindestens 300-500 ms vor der Show auftreten.
- Nach dem Empfang der Daten müssen Sie den Loader nicht scharf entfernen, auch hier sollte es zu einer Verzögerung kommen.
Diese einfachen Regeln werden benötigt, damit der Loader nur bei starken Anforderungen angezeigt wird und nach Abschluss nicht „blinkt“. Am wichtigsten ist jedoch, dass der beste Lader ein Lader ist, der nicht angezeigt wurde.
Vielen Dank für Ihre Aufmerksamkeit, das ist alles, messen, analysieren und verwenden Sie
PerfKeeper (
Live-Beispiel ) sowie
meinen Github und
Twitter bei Fragen!