interviewpath
  • 0day streak
  • 10total XP
  • Level 1
0/6

Unit 3 · OS & concurrency · Lesson 10 of 11

Profiling, bottlenecks and benchmarking pitfalls

Profiling and benchmarking turn "it is slow" into a located, verified cause. Interviews probe whether you follow a method (define the metric, find the limiting resource, change one thing, verify) and whether you know how measurements mislead: sampling gaps, warmup, cache state, frequency scaling and coordinated omission.
About 4 hFocus mode

Step 1: Learn the ideas

  • Investigation method and the USE method
  • Workload characterization and goals
  • Sampling versus instrumentation versus tracing
  • perf stat counters: cycles, instructions, IPC, cache and branch misses
  • perf record, stack walking and symbols
  • CPU and off-CPU flame graphs
  • CPU, memory, I/O, lock and scheduler bottlenecks
  • Latency histograms and percentiles
  • Coordinated omission and open-loop load
  • Benchmarking pitfalls: warmup, cache state, frequency scaling, noise
  • Measuring the wrong thing: client limits, dead code, unrealistic data
  • Continuous profiling in production

Why it matters

"The service got slow, what do you do?" is a common prompt for SRE, infrastructure and performance-sensitive roles, and the weak answer is a list of tools. The strong answer is a method that finds the limiting resource quickly and verifies the fix with a measurement you trust. The second half matters as much: a reported speedup, including one in a paper, can come from a benchmark that measured a warm cache, a throttled CPU, the load generator itself, or an average that hid the tail.

Core ideas

Method first. State the metric, such as p99 latency at a given load or throughput within an SLO, and reproduce the problem. For each resource (CPUs, memory, disks, network, locks, thread pools) check utilization, saturation and errors, the USE method, before reading code. Then change one thing at a time and re-measure under the same conditions.

Sampling, instrumentation, tracing.

  • Sampling profilers such as perf record, py-spy or async-profiler interrupt at a fixed rate and record stacks. Overhead is low enough for production, but results are statistical and depend on stack walking: missing frame pointers or symbols give broken stacks.
  • Instrumentation such as cProfile, manual timers or tracing spans records every call or region. Counts are exact, but overhead distorts short functions.
  • Tracing with tracepoints or eBPF tools records specific events (system calls, disk I/O, scheduler switches) with timestamps, which is how you attribute latency rather than CPU time.

Counters. perf stat reports cycles, instructions, cache and branch misses, context switches and page faults. Low instructions per cycle with many cache misses points to memory stalls rather than "slow code". High CPU utilization can hide spinning or stalls, so utilization alone does not prove the work is useful.

Flame graphs. Each box is a function in the sampled stacks; its width is the share of samples containing it, and callees sit on top of callers. The x-axis is sorted alphabetically, not by time, so look for wide plateaus rather than a left-to-right sequence. A CPU flame graph shows only on-CPU time; latency spent waiting on locks, I/O or the run queue needs off-CPU analysis, which records where threads block and for how long.

Classifying bottlenecks.

  • CPU-bound: busy cores and a wide hot path; improve the algorithm or parallelize.
  • Memory-bound: low IPC and many cache misses; change layout or access pattern.
  • I/O-bound: high device utilization or queue depth and threads blocked in I/O; batch, cache or change the access pattern.
  • Lock-bound: low CPU and low throughput with threads waiting in futex calls; shrink critical sections or shard.
  • Scheduler-bound: long run-queue latency or, in containers, quota throttling recorded in cpu.stat.

Measuring latency. Record full distributions in histograms, and never average percentiles across machines; merge histograms instead. Many load generators are closed loops: each connection waits for a response before sending its next request, so when the server stalls they stop sending and never record the requests that would have waited. This coordinated omission hides exactly the bad periods. Use constant-rate, open-loop generators such as wrk2, or correct each latency to the request's intended send time.

Benchmarking pitfalls. Missing warmup (JIT compilation, lazy initialization, connection pools, page cache); unstated cache state; CPU frequency scaling, turbo and thermal throttling; noisy neighbors on shared hosts; a client, network or logging bottleneck masquerading as the system under test; compilers deleting code whose result is unused; data sets that fit in cache unlike production; too few runs and no variance. Even link order and environment size can shift results, as Mytkowicz et al. showed in "Producing Wrong Data Without Doing Anything Obviously Wrong!" (ASPLOS 2009). Active benchmarking means profiling the benchmark while it runs to confirm what limits it.

Common misconceptions

  • "The flame graph x-axis is time." It is an alphabetical merge of stacks; flame charts show time order.
  • "100 percent CPU means we need faster CPUs." It may be spinning, garbage collection or memory stalls; check IPC and what the cycles are doing.
  • "Low CPU means the service has headroom." Threads may be blocked on locks or I/O, or throttled by a CPU quota.
  • "The profiler shows why requests are slow." CPU profilers miss waiting; latency needs off-CPU analysis or tracing.
  • "Averaging each host's p99 gives the fleet p99." Percentiles do not average; merge the distributions.
  • "A microbenchmark speedup carries over to production." Cache state, data shape and contention differ.

How to approach it

Practice the method aloud on a scenario until it takes five minutes. Then do it for real: profile a deliberately slow program with perf (or py-spy for Python), produce a flame graph, fix the hot spot and verify. In interviews, name the metric, the resource checks, one profiling step and how you will confirm the fix.

Suggested order

  1. 1Read the USE method page and write its checklist for a single Linux server from memory.
  2. 2Read the Flame Graphs and CPU Flame Graphs pages, then generate a flame graph for a toy program with perf record and the FlameGraph scripts (or py-spy for Python).
  3. 3Read the Off-CPU Analysis page to learn how blocked time is measured.
  4. 4Keep Linux perf Examples open while running perf stat on a cache-friendly and a cache-hostile loop.
  5. 5Read Active Benchmarking, then watch How NOT to Measure Latency for percentiles and coordinated omission.
  6. 6Do the OSTEP system-call and TLB measurement homeworks as benchmarking-hygiene practice: pin CPUs, warm up, repeat and report variance.

Step 2: Check off what you can do

Tap each one you can genuinely do or explain. Each earns 10 XP.

Step 3: Go deeper

  • The USE MethodStart here

    Brendan Gregg's website

    Primary method for checking every resource’s utilization, saturation and errors before profiling code.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • Flame GraphsStart here

    Brendan Gregg's website

    Section: CPU Flame Graphs

    How to generate and read CPU flame graphs; continue to the Off-CPU Analysis page for blocked time.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • Linux perf Examples

    Brendan Gregg's website

    Hands-on perf commands for counters, sampling and fixing broken stacks.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • Active Benchmarking

    Brendan Gregg's website

    Discipline for confirming what limits a benchmark while it runs.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • How NOT to Measure Latency (talk)

    InfoQ

    Percentiles, histograms and coordinated omission in latency measurement.

    Open(opens in a new tab)Link responded, 5 Oct 2026

Step 4: Practice

Use a Linux machine or VM, since perf is Linux-only (on macOS, Instruments or py-spy can cover some steps). (1) Write a program with an obvious hot loop, a cache-hostile access pattern and a lock-contended section; profile it with perf stat and perf record, produce a flame graph and explain each finding. (2) Add a sleep or a blocking read and show that the CPU flame graph misses it while off-CPU analysis finds it. (3) Load-test a small HTTP service with a closed-loop tool and then with a constant-rate tool such as wrk2, and compare p99 near saturation. (4) Rerun one benchmark cold and warm, and with frequency scaling left on, and record how much each changes the result.

Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).

Step 5: Test yourself

Answer each one out loud or on paper before you finish the lesson.

  1. Question 1What can a sampling profiler miss that instrumentation catches, and vice versa?
  2. Question 2What does a low instructions-per-cycle value suggest?
  3. Question 3How would you tell a lock-bound service from an I/O-bound one?
  4. Question 4Why should you not average p99 latencies across hosts?
  5. Question 5Which warmup effects can make the first minutes of a benchmark unrepresentative?
  6. Question 6How can a container show low CPU usage and still suffer CPU-related latency?

You’re done when you can

  • Walk through a slow-service investigation aloud in five minutes using the USE method and one profiling step.
  • Produce a CPU flame graph for your own program and explain its widest frames correctly.
  • Demonstrate a case where CPU profiling misses the cause and off-CPU analysis finds it.
  • Explain coordinated omission with a numeric example and how open-loop load avoids it.
  • List six benchmarking pitfalls with the control you would use for each.

Finish the lesson

How confident do you feel?

Lesson complete!

+50 XP

Reviews will come up on the Practice tab so it sticks.

Unlocks next: Database benchmark and workload design, Performance modeling, traces and bottleneck attribution

Your notes and study time

Log study time