Profilierung von TypeScript-Code: Engpässe in der Praxis finden
\nJeder Entwickler kennt die Situation, wenn die Anwendung zu trödeln beginnt und die Ursache nicht offensichtlich ist. In der Welt von TypeScript, wo Code nach JavaScript transpiliert wird, können Engpässe sowohl zur Laufzeit als auch aufgrund der Besonderheiten der Transpilierung selbst auftreten. In diesem Artikel werden wir praktische Methoden zur Profilierung von TypeScript-Code besprechen, die helfen, problematische Stellen zu identifizieren und die Leistung Ihrer Anwendung zu verbessern.
\n\nWarum ist die Profilierung von TypeScript wichtig?
\nTypeScript fügt statische Typisierung hinzu, aber zur Laufzeit wird normales JavaScript ausgeführt. Das bedeutet, dass Engpässe nicht nur mit Algorithmen zusammenhängen können, sondern auch damit, wie TypeScript kompiliert wird. Zum Beispiel kann die übermäßige Verwendung von Dekorateuren oder komplexen Generics zur Generierung von suboptimalem Code führen. Die Profilierung hilft, Fragen zu beantworten:
\n- \n
- Welche Funktionen brauchen am längsten? \n
- Wie viel Speicher verbraucht die Anwendung? \n
- Wo kommt es zu häufigen Garbage Collections? \n
- Welche Codeabschnitte blockieren den Hauptthread? \n
Ohne Profilierung riskieren Sie, die falschen Stellen zu optimieren und Zeit mit Mikrooptimierungen zu verschwenden, die keine spürbare Verbesserung bringen.
\n\nWerkzeuge zur Profilierung von TypeScript
\nFür die Profilierung von TypeScript-Code werden wir drei Hauptwerkzeuge verwenden, die die meisten Szenarien abdecken: von Browseranwendungen bis hin zu serverseitigen Lösungen auf Node.js.
\n\n1. Chrome DevTools für Browseranwendungen
\nDas zugänglichste und leistungsstärkste Werkzeug. Öffnen Sie den Tab Performance in den Chrome DevTools, starten Sie eine Aufnahme und führen Sie die gewünschte Aktion in der Anwendung aus. Nach dem Stoppen erhalten Sie ein Flame Chart – eine visuelle Darstellung der Funktionsaufrufe. So sieht das in der Praxis aus:
\n// Beispiel für langsamen TypeScript-Code\nfunction processData(data: number[]): number[] {\n const result: number[] = [];\n for (let i = 0; i < data.length; i++) {\n // Potenzieller Engpass: häufige Aufrufe\n result.push(data[i] * Math.sqrt(data[i]));\n }\n return result;\n}\n\n// Verwendung\nconst largeArray = Array.from({ length: 100000 }, (_, i) => i);\nconsole.time('processData');\nprocessData(largeArray);\nconsole.timeEnd('processData'); // ~15ms\n\nIn den Chrome DevTools werden Sie sehen, dass die Funktion processData viel Zeit in Anspruch nimmt und darin viele Aufrufe von Math.sqrt stattfinden. Dies ist ein Hinweis, dass man die Iteration durch map ersetzen oder die Berechnungen vektorisieren könnte.
2. Node.js profiler für Serveranwendungen
\nNode.js hat einen eingebauten Profiler. Starten Sie die Anwendung mit dem Flag --prof:
node --prof dist/app.js\n\nNach Beendigung der Arbeit wird eine Datei isolate-*.log generiert. Konvertieren Sie diese in ein lesbares Format:
node --prof-process isolate-*.log > processed.txt\n\nEin Beispiel für ein typisches Problem sind häufige Allokationen in Schleifen:
\n// Schlechtes Beispiel: Erstellen von Objekten in einer Schleife\ninterface User {\n id: number;\n name: string;\n}\n\nfunction createUsers(ids: number[]): User[] {\n const users: User[] = [];\n for (const id of ids) {\n users.push({ id, name: `User_${id}` }); // Neue Allokation\n }\n return users;\n}\n\nDer Profiler wird zeigen, dass die Funktion createUsers viele temporäre Objekte erstellt, was die Belastung des Garbage Collectors erhöht. Optimierung: Speicher vorab zuweisen oder einen Objektpool verwenden.
3. Benchmark.js für Mikrobenchmarks
\nWenn Sie die Leistung zweier Implementierungen vergleichen müssen, verwenden Sie die Bibliothek benchmark.js. Installieren Sie sie über npm:
npm install benchmark @types/benchmark\n\nBeispiel für den Vergleich zweier Methoden zum Filtern eines Arrays:
\nimport Benc