핫 경로를 위한 CPU 프로파일링 및 플레임 그래프
CPU 프로파일을 기록하고 플레임 그래프를 읽어 가장 많은 시간을 소비하는 함수를 찾습니다.
핫 경로를 위한 CPU 프로파일링 및 플레임 그래프은(는) CoddyKit의 무료 Node.js Backend Development Bootcamp 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Node.js Backend Development Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Node.js Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Why CPU Profiling Matters
When a Node.js backend feels slow, the cause is usually one of two things: the process is waiting (I/O, database, network) or it is computing (burning CPU on the single main thread). A CPU profile tells you exactly where the second kind of time goes.
- It samples the call stack at a fixed frequency (V8 uses ~1000 Hz, one sample per millisecond).
- Each sample records the function currently executing and its whole stack of callers.
- Functions that appear in many samples are your hot paths — the code worth optimizing.
Because Node.js runs JavaScript on one thread, a single hot function can block every incoming request. Profiling finds it instead of you guessing.
Self Time vs Total Time
Every profiler distinguishes two numbers per function, and confusing them is the most common profiling mistake.
- Self time (a.k.a. exclusive): time spent executing the function's own body, excluding the children it called.
- Total time (a.k.a. inclusive): self time plus all time spent inside callees.
A function high in total time may just be an orchestrator that calls expensive children. The function high in self time is where the CPU actually burns. Optimize by self time first.
Recording a Profile from the CLI
The fastest way to get a profile of a script is the built-in V8 flag. No extra packages, no code changes.
node --prof app.jswrites a rawisolate-*.logfile.node --prof-process isolate-*.log > profile.txtturns it into a human-readable summary with a ticks breakdown.
The summary groups ticks by JavaScript, C++, and GC, and lists the heaviest functions. It is text-only, so for visual flame graphs we will use the inspector protocol next. Below is a CPU-bound workload you can profile this way.
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));Recording Programmatically with the Inspector
For a long-running server you often want to profile a specific window of time. The built-in inspector module lets you start and stop the V8 CPU profiler from inside your code and save a .cpuprofile file.
- Open a
Session, connect it, and enable theProfilerdomain. - Call
Profiler.start, run the workload, thenProfiler.stop. - The returned profile is JSON you write to disk and load into Chrome DevTools or 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();
});
});
});What a Flame Graph Actually Shows
A flame graph turns the stack samples into a picture. Read it like this:
- The x-axis is NOT time — it is the population of stacks. Width = how many samples contained that frame, i.e. how much CPU it used.
- The y-axis is stack depth. The frame at the bottom is the caller; frames stacked on top are its callees.
- A wide frame means a function (and its children) consumed a lot of CPU. Wide frames at the top with little above them are the leaves doing the real work.
Colors are usually random and carry no meaning — do not read into them. You hunt for the widest plateaus, not the tallest towers.
Flame Graph vs Flame Chart
These look similar but answer different questions, and Chrome DevTools shows both.
- Flame chart (DevTools "Performance" timeline): x-axis is wall-clock time, left to right. Great for seeing when something happened and ordering of events.
- Flame graph (aggregated): identical frames are merged and sorted by width. Great for seeing which function is hot across the whole run, regardless of when it ran.
For finding hot paths you want the aggregated flame graph: a function called 10,000 times in scattered moments shows up as one fat bar instead of 10,000 invisible slivers.
Generating Flame Graphs with 0x
The 0x tool wraps your process, captures a profile, and produces an interactive HTML flame graph in one step — ideal for Node.js services.
npx 0x app.jsruns the app, and on exit opens a browser flame graph.- For a server, hit it with load (for example with
autocannon) while0xrecords, then stop the process to generate the graph.
In the 0x viewer you can click any frame to zoom, and search by name to highlight every place a function appears. Below is a tiny HTTP server worth profiling under load.
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'));Reading the Graph: Find the Widest Leaf
A disciplined way to locate the hot path in any flame graph:
- Scan the top edge of the graph (the leaf frames). These are the functions actually running when samples were taken.
- Find the widest leaf or plateau. That single frame is your largest pool of self time.
- Trace downward from it to learn the call chain that leads there — that tells you who to change.
Beware framework noise: frames like (anonymous), module.exports, or runtime internals are often wide because everything flows through them. Ignore broad orchestrator frames and focus on wide leaf frames.
Spotting Garbage Collection Pressure
Flame graphs do not only reveal your code — they expose the V8 runtime too. If you see wide frames named things like GC, Scavenge, or Mark-Compact, the CPU is being spent collecting garbage, not running logic.
- High GC width usually means you are allocating too many short-lived objects on the hot path (string concatenation in loops, creating closures or arrays per request).
- The fix is rarely "optimize the function" — it is "allocate less": reuse buffers, preallocate arrays, avoid per-iteration object literals.
The example below allocates a fresh object on every iteration, the classic pattern that lights up GC frames.
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));Deoptimization and Inlining Clues
V8 compiles hot functions to optimized machine code. When a function is forced back to slower bytecode it is deoptimized, and that shows up as unexpectedly wide frames.
- Run with
node --trace-deopt app.jsto log every deopt with its reason (e.g. changing object shapes, mixing types in an array). node --trace-opt app.jsshows which functions got optimized.- Keeping function arguments monomorphic (always the same shape/type) lets V8 keep them optimized and inlined.
The function below stays fast because it always receives numbers; passing it a string would trigger a deopt and a wider frame in the profile.
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);A Repeatable Profiling Workflow
Put the pieces together into a loop you can run on any backend:
- Reproduce the load deterministically (a benchmark or replayed traffic), so two profiles are comparable.
- Record a profile (
--prof, theinspectorsession, or0x). - Read the flame graph: widest leaf frame = biggest self time = first target.
- Fix exactly that function, change nothing else.
- Re-profile under the same load and confirm the fat bar shrank.
Change one thing per iteration. If you fix three functions at once you will not know which change helped — and one might have made things worse.
Quick Check: Reading the Graph
You profiled a slow endpoint. In the aggregated flame graph, your request handler is the widest frame, but it has tall stacks of children above it. One small leaf frame near the top, JSON.stringify, is also very wide. Which function should you optimize first?
Recap
You can now find and fix CPU hot paths in a Node.js backend:
- Self time (exclusive) is where the CPU burns; total time (inclusive) includes children. Optimize by self time.
- Record with
node --prof, the built-ininspectorSession, or0xfor an interactive flame graph. - In a flame graph the x-axis is sample count (CPU), not time, and the y-axis is stack depth. Hunt for the widest leaf frame.
- Wide
GC/Scavengeframes mean allocation pressure — allocate less rather than micro-optimizing logic. - Use
--trace-deoptto catch deoptimizations from polymorphic, shape-changing code. - Work the loop: reproduce, record, read, fix one thing, re-profile.
자주 묻는 질문
“핫 경로를 위한 CPU 프로파일링 및 플레임 그래프” 강의는 무료인가요?
네 — “핫 경로를 위한 CPU 프로파일링 및 플레임 그래프” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Node.js Backend Development Bootcamp 강의 전체를 잠금 해제할 수 있습니다. Node.js Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
“핫 경로를 위한 CPU 프로파일링 및 플레임 그래프”에서 뭘 배우나요?
CPU 프로파일을 기록하고 플레임 그래프를 읽어 가장 많은 시간을 소비하는 함수를 찾습니다. 브라우저에서 직접 실행하는 실습 코드로 Node.js Backend Development Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Node.js Backend Development Bootcamp을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Node.js Backend Development Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“핫 경로를 위한 CPU 프로파일링 및 플레임 그래프” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Node.js Backend Development Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Node.js Backend Development Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- V8 힙, 세대별 GC 및 객체 수명
- 힙 스냅샷 캡처 및 비교
- 핫 경로를 위한 CPU 프로파일링 및 플레임 그래프
- 일반적인 누수 패턴 탐지 및 해결