4.5.24 · D5Software Engineering

Question bank — Performance profiling — CPU, memory, I - O profiling

1,957 words9 min readBack to topic

True or false — justify

The word "true" or "false" alone earns nothing here — the reasoning is the answer.

A deterministic profiler and a sampling profiler will always rank the hotspots in the same order.
False. A deterministic profiler hooks every call, so it inflates call-heavy functions (thousands of tiny calls) disproportionately, distorting the ranking; a sampling profiler weights by wall-clock presence and often ranks such functions lower.
If a CPU profiler reports your code uses very little CPU, the program is fast.
False. Low CPU can mean the program is I/O-bound — waiting on disk or network — so wall-clock time is huge while the CPU sits idle. The CPU profiler simply cannot see waiting.
A function with the highest cumulative (inclusive) time is the best place to optimize.
False. Cumulative time includes all descendants, so a thin wrapper that just calls the real worker shows a huge cumulative number while doing almost no work itself. Sort by self time to find where cycles actually burn.
Sampling profilers give wrong answers because they only look occasionally.
False (for the hotspots that matter). Sampling is a Monte-Carlo poll: a function present in of snapshots uses of CPU, and the standard error (with the true CPU fraction, the sample count) shrinks fast, so big hotspots resolve in under a second.
In a flame graph, a taller stack means a slower function.
False. Height is call-stack depth, not time — it shows who called whom. Cumulative width is time. A short, very wide bar is a much bigger hotspot than a tall, narrow tower. See Flame Graphs.
Amdahl's Law says any optimization eventually makes the program infinitely fast if you try hard enough.
False. As speedup factor , overall speedup approaches only , capped by the untouched fraction . Optimizing a part that is 20% of runtime maxes out at no matter what. See Amdahl's Law.
Big-O tells you which line to optimize after profiling.
False. Big-O predicts scaling with input size, not where wall-clock time actually goes on your real workload — a loop over 5 items is irrelevant next to an database call. Profile the real run; use Big-O Complexity to reason about how the measured hotspot grows.
A microbenchmark that loops a function a million times gives its real-world speed.
False. Loops warm caches, trigger JIT warmup, let the compiler dead-code-eliminate unused results, and strip out the real I/O — so the number is optimistic and unrepresentative. This is benchmarking, not profiling the real path.
Turning on a profiler never changes the numbers it reports.
False. A tracing profiler adds 2–10× overhead concentrated on call boundaries, so heavily-called functions look relatively slower than they are. Use a low-overhead sampling profiler when overhead would distort the ranking.

Spot the error

Each line below contains a flawed reasoning move. Name the flaw and the fix.

"The nested triple loop looks terrifying, so I rewrote it first."
The error is optimizing by appearance, not measurement — the loop may run once on tiny data. Fix: profile, then optimize by measured self (exclusive) time.
"db_query has 75% cumulative width and CPU is low, so I'll rewrite it in C to speed up the CPU work."
The error is treating an I/O wait as CPU work. Flat, wide (75% cumulative time), low-CPU = waiting on the database; rewriting the client language does nothing. Fix the query (index it, batch it) — see N+1 Query Problem.
"Wall time is 100 ms and CPU time is 95 ms, so it must be I/O-bound."
The error is the direction of the inequality. (wait time near zero) means CPU-bound; you'd need (waiting dominating) to call it I/O-bound.
"I profiled the dev machine with 10 rows in the table, and it was instant, so production is fine."
The error is a non-representative workload. The N+1 query cost and I/O scale with real data volume; 10 rows hides the very bottleneck production exposes. Profile a realistic end-to-end load.
"Memory usage climbs forever, but the garbage collector runs, so there's no leak."
The error is assuming GC frees everything. In a managed language a leak is memory that is still reachable but forgotten — a growing global cache or dangling listener — which GC will never collect. See Memory Management & Garbage Collection.
"Adding a cache made this endpoint faster, so I'll cache everything."
The error is ignoring the measured slice. If the cached call was only 5% of runtime, Amdahl caps the win near while you add cache-invalidation bugs. Cache the measured big slice — see Caching Strategies.

Why questions

Why do we profile before optimizing instead of just fixing the obvious slow part?
Because developer intuition about bottlenecks is famously wrong, and Amdahl's Law means effort spent on a small fraction yields at most overall — you must find the big first or you gain nothing.
Why does a sampling profiler need a different tool for I/O than for CPU?
A sampling profiler asks "what CPU instruction is running now?"; during I/O the CPU runs nothing of yours, so those waits are invisible. You need syscall tracing (strace), iostat, or distributed traces to see the wait — see Observability — Logs, Metrics, Traces.
Why is "self time" the right sort key for finding where to optimize?
Self (exclusive) time is the work done inside a function excluding its callees, so it points at the exact frame burning cycles rather than at wrappers whose big cumulative number is really their children's work.
Why does sampling error shrink as rather than as ?
Because each of the samples is an independent Binomial trial, and the standard error of the estimated CPU fraction is — variance falls linearly with , so the error (its square root) falls as , giving fast but diminishing returns.
Why can time ./prog be called "the cheapest profiler you own"?
Because its real, user, sys outputs decompose (where ) for free, instantly telling you whether to reach for a CPU profiler or an I/O tool — the single most important distinction.
Why does the N+1 query problem show up as an I/O bottleneck rather than a CPU one?
Each of the N extra queries is a network round-trip to the database; the program spends its time waiting for responses, so wall-clock balloons while CPU stays near idle — the classic I/O signature.

Edge cases

If the optimizable part is (touches nothing on the hot path), what overall speedup does Amdahl predict?
. Zero — no speedup at all, because you optimized code that never runs on the measured path.
If a function is of runtime () and you speed it times, what is the overall speedup?
. The whole program speeds up by exactly , the only case where local and global speedup coincide.
What does a sampling profiler report for a program that spends all its time sleep()-ing?
Essentially no CPU samples land in your code — it appears to do "nothing" — yet wall-clock time is huge. This is the pure degenerate I/O/wait case a CPU profiler cannot represent.
You take only sample of a hotspot with true CPU fraction . Is the estimate meaningful?
No. With one sample the estimate is either 0 or 1 and — as large as the value itself. Sampling only becomes trustworthy once is large enough that is a small fraction of .
What happens to a deterministic profiler's reported ranking when a function makes millions of trivial calls versus one function doing heavy math in a tight loop?
The call-heavy function is inflated by per-call hook overhead and may outrank the genuinely heavy one — a distortion that vanishes under sampling, which is why you switch tools for call-heavy hot code.
If wall time equals CPU time and both are tiny, but users report slowness, where is the time going?
Not in this process at all — it is upstream or downstream (a slow dependency, queue wait, or network hop). You need end-to-end tracing across services, not a single-process profiler, to locate it — see Observability — Logs, Metrics, Traces.
For a memory leak in a non-GC language like C, what distinguishes it from a GC-language leak?
A C leak is memory that became unreachable but was never freed (a lost pointer); a GC-language leak is memory that stays reachable through a forgotten reference. Same symptom (memory grows), opposite root cause — and only the GC one is invisible to a leak detector that hunts unreachable blocks.

Recall One-line self-test before you leave

Cover every answer above; if you can justify each in your own words in one sentence, you have internalized the traps. The single sentence that ties them together: measure the big slice with the right stopwatch, then read self (exclusive) time, not appearances.