CPU-profilering och flame graphs för heta kodvägar
Spela in CPU-profiler och läs flame graphs för att hitta funktionerna som tar mest tid.
CPU-profilering och flame graphs för heta kodvägar är en gratis lektion i Bootcamp i backendutveckling med Node.js på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Bootcamp i backendutveckling med Node.js, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Bootcamp i backendutveckling med Node.js innehåller totalt 4 lektioner.
Varför CPU-profilering är viktigt
När en Node.js-backend känns långsam beror det vanligtvis på en av två saker: processen väntar (I/O, databas eller nätverk), eller så beräknar den (förbrukar CPU på den enda huvudtråden). En CPU-profil visar exakt vart den senare typen av tid tar vägen.
- Den samplar anropsstacken med en fast frekvens (V8 använder cirka 1 000 Hz, ett sampel per millisekund).
- Varje sampel registrerar den funktion som körs just då och hela stacken av anropare.
- Funktioner som förekommer i många sampel är Era heta kodvägar — den kod som är värd att optimera.
Eftersom Node.js kör JavaScript i en enda tråd kan en enda het funktion blockera varje inkommande request. Profilering hittar den åt Er i stället för att Ni behöver gissa.
Egen tid kontra total tid
Alla profilerare skiljer mellan två tal per funktion, och att blanda ihop dem är det vanligaste misstaget vid profilering.
- Egen tid (även kallad exklusiv tid): tiden som ägnas åt att köra funktionens egen kod, exklusive de underordnade funktioner som den anropade.
- Total tid (även kallad inklusiv tid): egen tid plus all tid som tillbringas i anropade funktioner.
En funktion som har hög total tid kan bara vara en samordnare som anropar dyra underordnade funktioner. Funktionen som har hög egen tid är där CPU:n faktiskt arbetar hårt. Optimera först utifrån egen tid.
Spela in en profil från CLI
Det snabbaste sättet att profilera ett skript är att använda den inbyggda V8-flaggan. Inga extra paket och inga kodändringar behövs.
node --prof app.jsskriver en råisolate-*.log-fil.node --prof-process isolate-*.log > profile.txtomvandlar den till en läsbar sammanfattning med en uppdelning av ticks.
Sammanfattningen grupperar ticks efter JavaScript, C++ och GC och listar de tyngsta funktionerna. Den består endast av text, så för visuella flame graphs använder vi inspector-protokollet härnäst. Nedan finns en CPU-bunden arbetsbelastning som Ni kan profilera på detta sätt.
function isPrime(n) {
if (n < 2) return false;
for (let i = 2; i * i <= n; i++) {
if (n % i === 0) return false;
}
return true;
}
function countPrimes(limit) {
let count = 0;
for (let n = 0; n < limit; n++) {
if (isPrime(n)) count++;
}
return count;
}
console.log(countPrimes(2_000_000));Spela in programmatiskt med Inspectorn
För en långkörande server vill Ni ofta profilera ett specifikt tidsintervall. Den inbyggda modulen inspector låter Er starta och stoppa V8:s CPU-profilerare inifrån koden och spara en .cpuprofile-fil.
- Öppna en
Session, anslut den och aktivera domänenProfiler. - Anropa
Profiler.start, kör belastningen och anropa sedanProfiler.stop. - Den returnerade profilen är JSON som Ni skriver till disk och läser in i Chrome DevTools eller VS Code.
const inspector = require('node:inspector');
const fs = require('node:fs');
const session = new inspector.Session();
session.connect();
function work() {
let sum = 0;
for (let i = 0; i < 5e7; i++) sum += Math.sqrt(i);
return sum;
}
session.post('Profiler.enable', () => {
session.post('Profiler.start', () => {
work();
session.post('Profiler.stop', (err, { profile }) => {
fs.writeFileSync('./work.cpuprofile', JSON.stringify(profile));
console.log('Saved work.cpuprofile');
session.disconnect();
});
});
});Vad en flame graph faktiskt visar
En flame graph omvandlar stackproverna till en bild. Läs den så här:
- X-axeln visar INTE tid — den visar mängden stackar. Bredden = hur många prover som innehöll den stackraden, alltså hur mycket CPU den använde.
- Y-axeln visar stackdjupet. Ramen längst ned är anroparen; ramarna ovanpå är dess anropade funktioner.
- En bred ram betyder att en funktion (och dess underordnade funktioner) använde mycket CPU. Breda ramar längst upp med få ramar ovanför sig är löven som utför det faktiska arbetet.
Färgerna är vanligtvis slumpmässiga och saknar betydelse — dra inga slutsatser av dem. Leta efter de bredaste platåerna, inte de högsta tornen.
Flame graph kontra flame chart
De här ser likadana ut, men besvarar olika frågor, och Chrome DevTools visar båda.
- Flame chart (tidslinjen "Performance" i DevTools): x-axeln visar väggurstid, från vänster till höger. Utmärkt för att se när något hände och i vilken ordning händelser inträffade.
- Flame graph (aggregerad): identiska ramar slås ihop och sorteras efter bredd. Utmärkt för att se vilken funktion som är het under hela körningen, oavsett när den kördes.
För att hitta heta kodvägar vill du använda den aggregerade flame graphen: en funktion som anropas 10 000 gånger vid utspridda tidpunkter visas som en enda tjock stapel i stället för 10 000 osynliga smala streck.
Generera flame graphs med 0x
Verktyget 0x startar och övervakar processen, samlar in en profil och skapar en interaktiv HTML-baserad flame graph i ett enda steg — idealiskt för Node.js-tjänster.
npx 0x app.jskör appen och öppnar en flame graph i webbläsaren när processen avslutas.- För en server skickar du belastning mot den (till exempel med
autocannon) medan0xspelar in. Stoppa sedan processen för att generera grafen.
I 0x-visningen kan du klicka på valfri ram för att zooma och söka efter namn för att markera varje ställe där en funktion förekommer. Nedan finns en liten HTTP-server som är lämplig att profilera under belastning.
const http = require('node:http');
function renderRow(i) {
return '<tr><td>' + i + '</td><td>' + (i * i) + '</td></tr>';
}
http.createServer((req, res) => {
let html = '<table>';
for (let i = 0; i < 5000; i++) {
html += renderRow(i);
}
html += '</table>';
res.setHeader('Content-Type', 'text/html');
res.end(html);
}).listen(3000, () => console.log('listening on 3000'));Läsa grafen: hitta det bredaste lövet
Ett systematiskt sätt att hitta den heta kodvägen i valfri flame graph:
- Skanna grafens övre kant (lövr amarna). Det här är funktionerna som faktiskt kördes när proverna togs.
- Hitta det bredaste lövet eller den bredaste platån. Den enskilda ramen utgör din största andel egen tid.
- Följ den nedåt för att se anropskedjan som leder dit — det visar vad du bör ändra.
Se upp för brus från ramverk: ramar som (anonymous), module.exports eller interna delar av körmiljön är ofta breda eftersom allt passerar genom dem. Ignorera breda orkestreringsramar och fokusera på breda lövramar.
Identifiera belastning från skräpsamling
Flame graphs visar inte bara er kod — de blottlägger även V8-körmiljön. Om ni ser breda ramar med namn som GC, Scavenge eller Mark-Compact används CPU:n till att samla in skräp, inte till att köra logik.
- En stor GC-bredd betyder vanligtvis att ni allokerar för många kortlivade objekt i den heta kodvägen (strängkonkatenering i loopar eller skapande av closures eller arrayer för varje begäran).
- Lösningen är sällan att "optimera funktionen" — den är att "allokera mindre": återanvänd buffertar, allokera arrayer i förväg och undvik objektliteraler i varje iteration.
Exemplet nedan allokerar ett nytt objekt vid varje iteration, det klassiska mönstret som får GC-ramar att lysa upp.
function process(n) {
const results = [];
for (let i = 0; i < n; i++) {
// a new object every iteration -> GC pressure
results.push({ id: i, squared: i * i, label: 'item-' + i });
}
let sum = 0;
for (const r of results) sum += r.squared;
return sum;
}
console.log(process(1_000_000));Ledtrådar om deoptimering och inlining
V8 kompilerar heta funktioner till optimerad maskinkod. När en funktion tvingas tillbaka till långsammare bytekod är den deoptimerad, vilket syns som oväntat breda ramar.
- Kör med
node --trace-deopt app.jsför att logga varje deoptimering med dess orsak (t.ex. ändrade objektformer eller blandade typer i en array). node --trace-opt app.jsvisar vilka funktioner som optimerades.- Om funktionsargumenten förblir monomorfa (alltid har samma form eller typ) kan V8 fortsätta att optimera och inline:a dem.
Funktionen nedan förblir snabb eftersom den alltid tar emot tal; om ni skickar en sträng till den utlöses en deoptimering och en bredare ram i profilen.
function add(a, b) {
return a + b;
}
let total = 0;
for (let i = 0; i < 1e7; i++) {
total = add(total, i); // monomorphic: always numbers, stays optimized
}
console.log(total);Ett repeterbart arbetsflöde för profilering
Sätt ihop delarna till en loop som ni kan köra på valfri backend:
- Återskapa belastningen deterministiskt (ett benchmark eller återuppspelad trafik), så att två profiler går att jämföra.
- Spela in en profil (
--prof, sessionen iinspectoreller0x). - Läs flame graphen: den bredaste lövramen = mest egen tid = första målet.
- Åtgärda exakt den funktionen och ändra inget annat.
- Profilera på nytt under samma belastning och bekräfta att den tjocka stapeln har krympt.
Ändra en sak per iteration. Om ni åtgärdar tre funktioner samtidigt vet ni inte vilken ändring som hjälpte — och en av dem kan ha gjort saker sämre.
Snabb kontroll: läsa grafen
Ni profilerade en långsam endpoint. I den aggregerade flame graphen är er requesthanterare den bredaste ramen, men den har höga staplar av underordnade funktioner ovanför sig. En liten lövram nära toppen, JSON.stringify, är också mycket bred. Vilken funktion bör ni optimera först?
Sammanfattning
Ni kan nu hitta och åtgärda heta CPU-kodvägar i en Node.js-backend:
- Egen tid (exklusiv) är där CPU:n förbrukas; (inklusiv) omfattar underordnade funktioner. Optimera utifrån egen tid.
- Spela in med
node --prof, den inbyggdainspector-sessionen eller0xför en interaktiv flame graph. - I en flame graph visar x-axeln antalet prover (CPU), inte tid, och y-axeln visar stackdjupet. Leta efter den bredaste lövramen.
- Breda
GC/Scavenge-ramar betyder allokeringsbelastning — allokera mindre i stället för att mikrooptimera logiken. - Använd
--trace-deoptför att upptäcka deoptimeringar som orsakas av polymorf kod eller kod som ändrar form. - Följ loopen: återskapa, spela in, läs, åtgärda en sak och profilera på nytt.
Lär dig JavaScript med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 22
- Lektioner
- 92
Vanliga frågor
Är lektionen ”CPU-profilering och flame graphs för heta kodvägar” gratis?
Ja – hela texten till ”CPU-profilering och flame graphs för heta kodvägar” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Bootcamp i backendutveckling med Node.js, kan Ni uppgradera till CoddyKit PRO. Kursen i Bootcamp i backendutveckling med Node.js innehåller totalt 4 lektioner.
Vad lär jag mig i ”CPU-profilering och flame graphs för heta kodvägar”?
Spela in CPU-profiler och läs flame graphs för att hitta funktionerna som tar mest tid. Ni övar på Bootcamp i backendutveckling med Node.js med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Bootcamp i backendutveckling med Node.js?
Du behöver inga förkunskaper. Utbildningen i Bootcamp i backendutveckling med Node.js på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.
Hur lång tid tar lektionen ”CPU-profilering och flame graphs för heta kodvägar”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Bootcamp i backendutveckling med Node.js-lektionen?
Ja. Varje Bootcamp i backendutveckling med Node.js-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- V8-heapen, generationsbaserad GC och objektslivslängder
- Samla in och jämför heap-ögonblicksbilder
- CPU-profilering och flame graphs för heta kodvägar
- Identifiera och åtgärda vanliga läckagemönster