Profilierung von TypeScript-Code: Engpässe in der Praxis finden
Wenn Ihr TypeScript-Projekt langsamer wird und Benutzer sich über Verzögerungen beschweren, ist es Zeit für die Profilierung. Profilierung ist nicht einfach nur "schauen, was langsam ist", sondern ein systematischer Ansatz zur Identifizierung von Engpässen in der Code-Leistung. In diesem Artikel werden wir praktische Methoden zur Profilierung von TypeScript-Code mit realen Beispielen und Werkzeugen behandeln.
Warum TypeScript-Code profilieren?
TypeScript wird zu JavaScript kompiliert, und alle Leistungsprobleme, die JS betreffen, übertragen sich auf TS. Allerdings fügt TypeScript eigene Nuancen hinzu: Typisierung, Dekorateure, Generics können unerwartete Overhead-Kosten verursachen. Die Profilierung hilft, Fragen zu beantworten:
- Welche Funktion wird am längsten ausgeführt?
- Wo tritt ein Speicherleck auf?
- Warum ruckelt die Animation bei 60fps?
- Welcher Algorithmus benötigt Optimierung?
Werkzeuge zur Profilierung von TypeScript
Chrome DevTools (für Browser-Code)
Das leistungsstärkste Werkzeug für das Frontend. Öffnen Sie den Reiter Performance, klicken Sie auf Record, führen Sie eine Aktion aus und stoppen Sie die Aufnahme. Sie sehen ein Flamegraph – eine Visualisierung der Funktionsaufrufe und ihrer Ausführungszeiten.
// Beispiel für langsamen Code zur Profilierungfunction heavyCalculation(iterations: number): number { let result = 0; for (let i = 0; i < iterations; i++) { result += Math.sqrt(i) * Math.sin(i); } return result;}
// Führen Sie in der Browser-Konsole aus, dann profilieren SieheavyCalculation(1000000);Node.js – integrierter Profiler
Für serverseitigen Code verwenden Sie das --inspect-Flag und Chrome DevTools oder den integrierten --prof zur Erstellung von Logs.
// server.tsimport express from 'express';
const app = express();
app.get('/heavy', (req, res) => { const data = processData(10000); res.json({ result: data });});
function processData(size: number): number[] { const arr: number[] = []; for (let i = 0; i < size; i++) { arr.push(i * Math.random()); } return arr.sort((a, b) => a - b);}
app.listen(3000);// Ausführung: node --prof dist/server.jsBenchmark.js für präzise Messungen
Wenn Sie zwei Implementierungen vergleichen müssen, verwenden Sie die Bibliothek benchmark.
import Benchmark from 'benchmark';
const suite = new Benchmark.Suite();
// Langsame Variantefunction slowSearch(arr: number[], target: number): boolean { for (let i = 0; i < arr.length; i++) { if (arr[i] === target) return true; } return false;}
// Schnelle Variante mit Setfunction fastSearch(arr: number[], target: number): boolean { const set = new Set(arr); return set.has(target);}
const testArray = Array.from({ length: 10000 }, (_, i) => i);
suite .add('slowSearch', () => slowSearch(testArray, 9999)) .add('fastSearch', () => fastSearch(testArray, 9999)) .on('cycle', (event: any) => console.log(String(event.target))) .on('complete', function(this: any) { console.log('Fastest is ' + this.filter('fastest').map('name')); }) .run({ async: true });Praktische Beispiele zur Suche nach Engpässen
Beispiel 1: Speicherleck durch Closures
Ein typisches Problem in React-Anwendungen sind Closures, die Referenzen auf große Objekte halten.
// Problematischer Codefunction createHeavyComponent() { const hugeData = new Array(1000000).fill('data'); return { getData: () => hugeData, // Closure hält hugeData fest updateData: (newData: string) => { // Mutation, aber die Referenz bleibt bestehen hugeData.push(newData); } };}
const component = createHeavyComponent();// hugeData wird nicht vom GC eingesammelt, solange component existiert<