Profiling and Benchmarking
This chapter covers measuring performance: profiling (where time goes) and benchmarking (how fast something is). Measurement comes before optimization.
Why This Matters
Optimizing without measuring is guessing. Profiling tells you *where* to optimize; benchmarking tells you whether a change actually helped. Without both, you waste time and can easily make things slower.
Prerequisites
c.opt.1— optimizer basics.
Core Concept
- Profiling observes a running program to attribute time/CPU/memory to
functions and lines. It answers "where is the time going?"
- Benchmarking measures the time (or throughput) of a specific operation,
repeated enough to be stable. It answers "is A faster than B?"
Tools
- Perf (Linux): sampling profiler;
perf record+perf report. - gprof: instrumented profiler (older).
- Valgrind --tool=callgrind / cachegrind: call graph / cache simulation.
- clock_gettime(CLOCK_MONOTONIC): high-resolution timing (POSIX).
Examples
Wall-clock timing (POSIX)
#include <stdio.h>
#include <time.h>
int main(void)
{
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
/* ... work ... */
clock_gettime(CLOCK_MONOTONIC, &t1);
double elapsed = (t1.tv_sec - t0.tv_sec)
+ (t1.tv_nsec - t0.tv_nsec) / 1e9;
printf("%.6f s\n", elapsed);
return 0;
}
Perf (Linux)
perf record ./app
perf report
This samples the CPU and shows a ranked list of functions by time.
How It Works
A sampling profiler interrupts the program periodically and records the current instruction address; aggregation shows hot functions. An instrumented profiler counts function entries/edges. A benchmark runs the target many times and takes a stable statistic (median/min, not just mean).
Variations
Microbenchmarking
Measure a tiny operation in a loop, but beware the optimizer eliminating the work — use volatile or an opaque sink.
Statistical pitfalls
- Cold cache vs. warm cache.
- Turbo/thermal throttling.
- System noise from other processes.
Common Mistakes
- Optimizing without profiling first.
- Benchmarking a single run (too noisy).
- Letting the compiler optimize the benchmarked code away.
- Comparing wall-clock time when CPU time matters.
Undefined Behavior
- Benchmarking UB code measures meaningless results.
Portability
clock_gettimeis POSIX, not ISO C. ISO C hasclock()(CPU time) and
time() (calendar). Perf is Linux.
Under the Hood
Modern CPUs have hardware performance counters (instructions retired, cache misses, branch mispredictions); perf reads them via the kernel.
Practical Usage
- Profile first; fix the hot spots.
- Benchmark with a stable, repeatable harness.
- Use
CLOCK_MONOTONICfor elapsed time (immune to wall-clock jumps).
Exercises
1. Time a function with clock_gettime and report median over many runs. 2. Use perf record/perf report on a small program and identify the hottest function. 3. Write a microbenchmark and prevent the optimizer from eliminating it.
Deep Challenge
Benchmark two implementations of a hot function, and explain how you control for cache warmth, compiler elimination, and system noise. Present the results as medians and discuss significance.
Related Concepts
c.perf.2— cache locality.c.opt.1— optimizer.c.debug.4— Valgrind callgrind.
References
- perf documentation, clock_gettime POSIX docs.
Verification
- Profiling/benchmarking methods are standard practice.
VERIFIED clock_gettimeis POSIX.PLATFORM-SPECIFIC- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Profiling
- [ ] Benchmarking
- [ ] Timing with clock_gettime
- [ ] Perf
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.perf.profile | 0 | 6 |
| c.perf.benchmark | 0 | 6 |