Hardware performance counters
Every modern core exposes a performance monitoring unit (PMU) that counts events: cycles, retired instructions, cache and TLB misses, branch mispredictions. This skill reads those counters three ways: whole-program totals with perf stat -e, in-process regions with the PAPI library, and per-source-line attribution by sampling on a miss event and annotating. The numbers are ratios to compare across builds on one machine, not grades against a fixed scale.
Contract
| Field | Bound contract |
|---|---|
| Trigger | The user asks for a cache miss rate, branch misprediction rate, IPC, MPKI, memory bandwidth from counters, PAPI instrumentation, or which source lines cause the misses. |
| Authority | Reversible local: writes only perf.data files, PAPI-instrumented test binaries, and any PMU tool checkout in the working directory; rollback is deleting them. sysctl and MSR module changes are proposed to the user, never applied. No remote mutation. |
| Side effect | Profile data on disk. Counting mode (perf stat) adds negligible overhead; sampling mode slows the program by a small factor. |
| Done | Each metric is reported with its formula, the raw counts it came from, the workload, and the microarchitecture, and the comparison it supports (build A versus build B) is stated. |
Inputs
- Target binary built with
-gat the release optimization level. - CPU model (
lscpu) and whether the host is virtualized: a VM often exposes no hardware events. perf_event_paranoidlevel (seelinux-perffor the meaning of each value).- The metric of interest, which selects the events: IPC needs
instructionsandcycles; miss rates need the access and miss pair for one cache level; MPKI needsinstructionsand one miss event. - For PAPI:
libpapiheaders and library (-lpapi), PAPI 7.3.0 at grounding.
Procedure
Confirm what the CPU exposes.
perf list hwprints generic hardware events,perf list cachethe cache events,perf list pmuthe CPU-specific named events. Ifperf list hwis empty, the host is virtualized or the PMU is locked and only software events remain. Done when: the events for the chosen metric appear inperf list.Take the default summary:
perf stat ./prog. perf prints the counts and derived annotations such asinsn per cycleand% of all cache refs. Done when: the default table is recorded.Count the events the metric needs:
perf stat -e instructions,cycles,cache-references,cache-misses,branches,branch-misses ./prog perf stat -e L1-dcache-loads,L1-dcache-load-misses,LLC-loads,LLC-load-misses ./prog perf stat -e dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses ./prog perf stat -r 5 -e instructions,cycles ./progGroup events that must be measured together in one
-elist; when more events are requested than counters exist, perf multiplexes and scales them, and prints the percentage of time each was counted. Repeat with-rand report the spread. Done when: raw counts for every event in the metric are recorded with the run spread.Compute and read the metrics as conditional signals:
Metric Formula Reading IPC instructions / cyclesCeiling is the core's issue width. Compare two builds on the same core; a memory-bound or pointer-chasing loop has a low IPC by nature, and a lower IPC after a change is the signal, not the level L1 miss rate L1-dcache-load-misses / L1-dcache-loadsMeaningful with the absolute miss count beside it; streaming through a large array raises it by design LLC miss rate LLC-load-misses / LLC-loadsHigh rate plus high absolute count means DRAM traffic; check bandwidth in step 6 Branch miss rate branch-misses / branchesData-dependent branches drive it; if sorting the input lowers it, the branches are the cost MPKI misses / (instructions / 1000)Normalizes misses to work done; use it to compare builds with different instruction counts Done when: each metric carries its formula, raw counts, workload, and CPU model.
Attribute misses to source. Sample on the miss event and annotate:
perf record -e LLC-load-misses -g ./prog, thenperf annotate --stdioorperf annotate --symbol=<fn> --stdio; inperf report,aon a function opens the same view. The percentage next to a source line is the share of miss samples landing there; a loop body with a strided access pattern shows the load instruction at the top. Precise sampling (:por:ppsuffix) tightens instruction attribution where the PMU supports it. Done when: the top source lines by miss samples are named.Measure memory bandwidth when LLC misses are high. Uncore IMC events count DRAM transactions:
perf stat -e uncore_imc/cas_count_read/,uncore_imc/cas_count_write/ -a ./progon Intel hosts that expose theuncore_imcPMU inperf list pmu(system-wide-a, so it needs paranoid level 0 or root). Intel PCM (https://github.com/intel/pcm, built withcmake, binaries inbuild/bin) reports socket bandwidth withpcm-memory 1and core metrics withpcm 1, both with a-csvmode; PCM reads MSRs and needs root orCAP_SYS_RAWIO, or its daemon mode for unprivileged readers. Done when: achieved bandwidth is recorded next to the platform peak.Instrument a region with PAPI when whole-program totals are too coarse. The low-level API (the old
PAPI_start_countersandPAPI_stop_countersare gone from PAPI 7.x):#include <papi.h> #include <stdio.h> int main(void) { int events[] = { PAPI_TOT_INS, PAPI_TOT_CYC, PAPI_L2_TCM, PAPI_BR_MSP }; long long values[4]; int set = PAPI_NULL; if (PAPI_library_init(PAPI_VER_CURRENT) != PAPI_VER_CURRENT) return 1; if (PAPI_create_eventset(&set) != PAPI_OK) return 1; for (int i = 0; i < 4; i++) if (PAPI_add_event(set, events[i]) != PAPI_OK) return 1; PAPI_start(set); do_work(); PAPI_stop(set, values); printf("IPC %.2f L2 misses %lld branch mispredicts %lld\n", (double)values[0] / values[1], values[2], values[3]); return 0; }Build with
gcc -O2 -g -o prog prog.c -lpapi. The high-level API wraps the same inPAPI_hl_region_begin("name")andPAPI_hl_region_end("name")with events chosen throughPAPI_EVENTS.papi_avail -alists the presets this CPU supports (PAPI_TOT_INS,PAPI_TOT_CYC,PAPI_L1_DCM,PAPI_L2_TCM,PAPI_L3_TCM,PAPI_BR_MSP,PAPI_TLB_DM,PAPI_FP_INS,PAPI_VEC_INS);papi_native_availlists native events. A preset absent frompapi_avail -afails inPAPI_add_event. Done when: the region's counts print and the preset list confirms each event.Reach raw events when the generic names do not cover the question.
perf list pmuprints the CPU's named events; the raw form isperf stat -e cpu/event=0x..,umask=0x../.pmu-tools(git clone https://github.com/andikleen/pmu-tools, run./ocperf.pyfrom the checkout) translates vendor event names to raw codes;showevtinfofrom libpfm4 lists what the library knows. Done when: the raw event's name and code are recorded together.
Failure and recovery
| Failure | Cause | Fix |
|---|---|---|
<not supported> beside an event |
Event absent on this CPU or in this VM | Choose from perf list; fall back to software events or a bare-metal host |
<not counted> or low multiplex percentage |
More events than counters | Split the events across runs, or group the ones that must be read together |
| Miss rate looks alarming | Absolute count is small | Report the count beside the rate; a rate on few references is noise |
PAPI_add_event fails |
Preset not available or counter conflict | Check papi_avail -a; reduce the event set |
| Uncore events missing | PMU not exposed or paranoid level too high | Propose paranoid level 0 or root for -a; use PCM or the vendor profiler (intel-vtune-amd-uprof) |
| Numbers differ run to run | Frequency scaling, SMT sibling, cache state | Use -r, pin with taskset, report the spread |
Output
A counter report listing each metric with its formula, raw counts, run spread, workload, and CPU model; the top source lines by miss samples when attribution was requested; the bandwidth figure and platform peak when measured; and the PAPI region output when instrumented.