B6. Profiling
The last lab of the course applies the earlier modules to one practical
question: why is this slow? The main skill here is the order of steps.
A function profiler shows where a program computes, but first you have to
find out whether it is computing at all, or waiting on the disk or on memory.
The path of read() through the kernel and the page cache is described in module 13,
cache lines and misses in module 2, and tracing with
eBPF in module 18.
After this lab you will be able to:
- from
topand the process state, say whether a program lacks CPU or is waiting on I/O; - read the output of
strace -cand name the system call where the time goes, and how many times it is called; - find a hot function with
perf recordandperf report; - use IPC and
cache-missesfromperf statto tell computation from waiting on memory; - show the distribution of disk latencies with
biolatencyoriostat -xz.
This is what the first hour of any “the service got slow” investigation looks like: before touching the code, prove with numbers where the time is lost, and after the fix, measure again.
The result is a report with three analyzed cases of a slow program: for each, the cause is named, proven by measurement, and the numbers before and after the fix are shown.
What to do: the three tasks below. In each one you need to find the cause, prove it by measurement and fix it, and then show the numbers before and after. Follow the “Order of steps”: first find out whether the program is waiting or computing, and only then reach for the profiler.
-
A program that waits.
Take or write a program that reads a file in tiny chunks of 1 byte. Find this with
strace -c, fix it with buffering, and show the difference in the number of system calls and in time (module 3). -
A program that misses the cache.
Traversing a two-dimensional array by columns instead of rows. Prove with
perf statthat the problem is memory access, not the number of operations: the instruction count should stay almost the same (module 2).Look first at
cache-references, not only atcache-misses: it is the number of accesses beyond L1 that explodes. For reference, a 3000×3000 array (34 MiB), the same 127 M instructions in both cases:Traversal cache-references cache-misses by rows 2.0 M 1.35 M by columns 35.8 M 1.59 M -
A system slowed down by the disk.
Create a load (
fioorddin a loop), find the culprit withiostat -xzandbiolatencyfrom bpftrace, and show the latency distribution (module 13, module 14).
What not to do. Do not optimize blindly: a change without a measurement
before and after does not count. Flame graphs and your own bpftrace programs
are left for the “Going further” section.
Constraints. Task 2 requires a C program built with -g:
otherwise perf report will show addresses instead of function names. For tasks 1 and 3
any language will do.
What goes in the report:
- for task 1: the number of system calls and the time from
strace -cbefore and after buffering; - for task 2:
instructions,cache-referencesandcache-missesfromperf statfor traversal by rows and by columns; - for task 3: the latency distribution from
biolatencyorr_await/w_awaitfromiostat -xz, and the name of the process generating the load; - for each task: the process state and
%wafromtopat the first step of the order of steps, and the conclusion you drew from them.
Done when:
- you can confirm with a command each of the seven claims in the “What you must be able to prove” table;
- the report names the cause for each of the three tasks and has the numbers before and after the fix;
- the report contains the four items above.
Before you start
Section titled “Before you start”- Read the sections “The path of one
read()” and “Buffering and caching” in module 13, the section “Memory hierarchy” in module 2, and the section “eBPF: the kernel became programmable” in module 18. For task 1 the section “What it costs” in module 3 will help. - The Vagrant machine from the archive has
linux-perf,bpftrace,fioandsysstat(iostat). On your own system install the same withsetup/provision.sh: it also lowersperf_event_paranoidandkptr_restrict, without whichperfandbpftracecannot see kernel symbols. - The
biolatencyscript is one of thebpftraceexamples. There is no starter code orcheck.shfor this lab in the archive: the result is confirmed by the claims table and the report.
Order of steps
Section titled “Order of steps”This is the core material of the lab. Remember the sequence: it saves hours.
-
Is it the CPU at all?
top: if the process is in stateSorDand%wais high, the CPU is sufficient and the program is waiting. A function profiler will not help here. -
What exactly is it waiting on?
strace -T -cshows which system calls the time went into. For stateD,cat /proc/<pid>/wchanorstack: that gives the name of the kernel function in which the process went to sleep. -
If it is the CPU, then where?
perf topfor a live picture,perf record+perf reportfor analysis. Look at the share first, function names later. -
Is it computation at all?
perf stat: the ratio of instructions to cycles (IPC). A value below one means the CPU is waiting on memory instead of computing (module 2). Then look atcache-missesanddTLB-load-misses(module 11). -
If you need something the ready-made tools do not provide, use
bpftrace.
What you must be able to prove
Section titled “What you must be able to prove”| Claim | How to prove it |
|---|---|
| You can tell waiting from computing | top %wa + process state |
| You found which call the time goes into | strace -c -T |
| You found the hot function | perf report |
| You know whether the CPU is computing or waiting | perf stat, IPC |
| Cache misses are measured, not assumed | perf stat -e cache-misses |
| Disk latencies are shown as a distribution | biolatency or iostat -xz |
| The fix had an effect | numbers before and after for each task |
Common mistakes
Section titled “Common mistakes”Profiling without symbols. perf report will show addresses instead of
names. You need -g at build time and packages with debug symbols.
Measuring without warm-up. The first run reads from disk, and the following ones from the page cache (module 13). The difference is a hundredfold, and it has nothing to do with your optimization.
Optimizing the wrong thing. A function takes eighty percent of the time, but it is called where the program is waiting on the disk anyway. That is why you start with the first step of the order of steps.
strace as a profiler. It slows the program down several times over, because
it intercepts every call. It is fine for the distribution of time between calls,
not for absolute numbers.
A conclusion without a second measurement. “It got faster” without numbers before and after does not count as a result.
Going further
Section titled “Going further”Build a flame graph from perf record and see what
the same profile looks like as a call tree. Or write your own
bpftrace one-liner that counts the latency distribution of a specific
system call.