Profilierung von JavaScript-Code: Engpässe finden
\n\nWenn Ihr Node.js-Projekt anfängt zu stocken, ist der erste Gedanke: „Ich muss alles in Rust neu schreiben.“ Aber beeilen Sie sich nicht! Meistens liegt das Problem in ein paar Zeilen Code, die ineffizient arbeiten. In diesem Artikel schauen wir uns an, wie Sie diese „Flaschenhälse“ mit einfachen Profilierungswerkzeugen finden. Sie erfahren, wie Sie die Leistung messen, ein Flamegraph erstellen und verstehen, wohin die CPU-Zeit fließt.
\n\nWarum Code profilieren?
\n\nStellen Sie sich vor, Ihr Server verarbeitet 100 Anfragen pro Sekunde, benötigt werden aber 500. Sie könnten alles Mögliche optimieren, aber das würde Wochen dauern. Oder Sie finden eine Funktion, die 80% der Zeit beansprucht, und beheben sie in einer Stunde. Profilierung zeigt genau, wo die Zeit verbraucht wird.
\n\nBetrachten wir die wichtigsten Szenarien, in denen eine Profilierung notwendig ist:
\n- \n
- Hohe Antwortverzögerung bei APIs \n
- Speicherlecks (memory leaks) \n
- Hohe CPU-Auslastung ohne ersichtlichen Grund \n
- Langsame Verarbeitung großer Datenarrays \n
Werkzeug 1: Integrierter Profiler von Node.js (--prof)
\n\nDer einfachste Weg ist, Node.js mit dem Flag --prof zu starten. Es sammelt Ausführungsstatistiken und speichert sie in einer Datei isolate-*.log.
Erstellen wir ein Testskript, das einen Engpass enthält:
\n\n// slow.js\nfunction heavyComputation() {\n let sum = 0;\n for (let i = 0; i < 1e7; i++) {\n sum += Math.sqrt(i) * Math.sin(i);\n }\n return sum;\n}\n\nfunction fastFunction() {\n return 42;\n}\n\nconsole.time('total');\nheavyComputation();\nfastFunction();\nconsole.timeEnd('total');\n\n\nStarten wir die Profilierung:
\nnode --prof slow.js\n\nNach der Ausführung erscheint eine Datei isolate-0x...-v8.log. Um sie zu lesen, verwenden wir das Dienstprogramm node --prof-process:
node --prof-process isolate-0x...-v8.log > profile.txt\n\nÖffnen wir profile.txt und sehen eine Tabelle mit Zeitprozenten:
[Summary]:\n ticks total nonlib name\n 785 78.5% 85.3% JavaScript\n 215 21.5% 14.7% C++\n ...\n[Bottom up (heavy) profile]:\n ticks parent name\n 500 50.0% LazyCompile: *heavyComputation slow.js:2:17\n 100 10.0% Builtin: MathSqrt\n 50 5.0% Builtin: MathSin\n\n\nWir sehen, dass 50% der Zeit für die Funktion heavyComputation aufgewendet werden. Jetzt wissen wir, was zuerst optimiert werden muss.
Werkzeug 2: Chrome DevTools (Inspektor)
\n\nFür eine anschaulichere Analyse verbinden wir Chrome DevTools. Starten Sie Node.js mit dem Flag --inspect:
node --inspect slow.js\n\nÖffnen Sie Chrome und gehen Sie zu chrome://inspect. Klicken Sie auf „Open dedicated DevTools for Node“. Reiter „Profiler“ → „Start“ → führen Sie das Skript aus → „Stop“.
Sie sehen ein Flamegraph – ein Diagramm, bei dem die Breite eines Blocks die Ausführungszeit anzeigt. Je breiter der Block, desto „heißer“ die Funktion. Unser heavyComputation wird ein riesiges Rechteck sein.
Tipp: Verwenden Sie den Filter „Focus selected“ auf einer Funktion, um ihre internen Aufrufe zu sehen. Sie werden zum Beispiel feststellen, dass Math.sqrt 10 Millionen Mal aufgerufen wird – vielleicht lohnt es sich, die Wurzeln vorab zu berechnen.
Werkzeug 3: perf_hooks – präzise Messungen im Code
\n\nManchmal muss man einen bestimmten Codeabschnitt messen, ohne externe Werkzeuge zu starten. Dafür verwenden wir das integrierte Modul perf_hooks.
Beispiel für die Messung der Ausführungszeit einer Funktion:
\n\nconst { performance } = require('perf_hooks');\n\nfunction processData(items) {\n let result = [];\n for (let item of items) {\n // Simulation einer komplexen Verarbeitung\n for (let j = 0; j < 1000; j++) {\n result.push(item * j);\n }\n