Modern microprocessors operate at clock frequencies measured in gigahertz, executing instructions in fractions of a nanosecond. However, dynamic random-access memory (DRAM) access speeds have not kept pace with CPU instruction throughput—a fundamental bottleneck known as the memory wall. While a Level 1 (L1) data cache hit resolves in approximately 4 to 5 CPU cycles, fetching a data line from main RAM requires 150 to 250 cycles. When CPU execution pipelines stall waiting for cache line fills, computational performance plummets.
Diagnosing whether an application’s poor throughput is caused by algorithmic complexity or memory access stalls requires low-level hardware performance counters. Through the hardware Performance Monitoring Unit (PMU) integrated into modern Intel, AMD, and ARM processors, the Linux kernel profiling subsystem provides deep visibility into cache efficiency. By using perf stat with targeted cache hardware events, systems engineers can quantify L1 data cache misses, Last-Level Cache (LLC) line invalidations, and memory bus saturation directly from the command line.
Understanding the CPU Cache Hierarchy and PMU Events
Modern CPUs organize cache memory into hierarchical tiers designed to balance speed, size, and electrical power:
- L1 Data Cache (L1d): Dedicated per-core cache (typically 32 KB to 48 KB) offering the lowest access latency (~1 nanosecond). Events include
L1-dcache-loads,L1-dcache-load-misses, andL1-dcache-stores. - L2 Cache: Dedicated per-core or shared cache (typically 512 KB to 1 MB) acting as a secondary buffer (~3 to 4 nanoseconds). Events include
l2_rqsts.all_demand_data_rdandl2_rqsts.miss. - Last-Level Cache (LLC / L3): Massive shared on-die cache (typically 16 MB to 96 MB+) shared across all CPU cores on a socket (~10 to 15 nanoseconds). Events include
LLC-loads,LLC-load-misses,LLC-stores, andLLC-store-misses. - Main System RAM: External memory accessed over the memory controller bus when an LLC miss occurs.
A cache miss rate (calculated as cache-misses / cache-references * 100) exceeding 10% on an intensive loop strongly suggests poor data locality, non-contiguous memory access, or false sharing across threads.
Profiling Overall Cache Misses with perf stat
To inspect generalized hardware cache references and misses across a specific command or workload, invoke perf stat using standard architectural event aliases:
# Profile generalized hardware cache metrics for a command
sudo perf stat -e cache-references,cache-misses,cycles,instructions -- ./database_bench
The command outputs hardware event counts along with calculated ratios:
Performance counter stats for './database_bench':
1,845,120,910 cache-references
210,481,200 cache-misses # 11.41% of all L1D hits
8,412,019,450 cycles
12,891,440,210 instructions # 1.53 insn per cycle
2.145019821 seconds time elapsed
Here, the cache miss rate stands at 11.41%. Notice the instructions per cycle (IPC) score of 1.53; if the cache miss rate were significantly higher (e.g. 30%), the IPC would plummet below 0.6 due to memory stalls.
Measuring L1 Data Cache and Last-Level Cache (LLC) Hierarchy
To isolate exactly where memory bottlenecks occur within the cache hierarchy, specify granular hardware cache events covering both L1 and LLC layers:
# Profile L1 and LLC load miss rates simultaneously
sudo perf stat -e \
L1-dcache-loads,L1-dcache-load-misses,\
LLC-loads,LLC-load-misses \
-p 18452 -- sleep 10
The terminal presents hierarchical breakdown metrics:
Performance counter stats for process id '18452':
3,410,290,110 L1-dcache-loads
185,420,109 L1-dcache-load-misses # 5.44% of all L1-dcache hits
42,109,820 LLC-loads
5,120,401 LLC-load-misses # 12.16% of all LL-cache hits
10.001290141 seconds time elapsed
In this profile:
- 94.56% of data requests hit the L1 data cache directly.
- Of the data requests that missed L1 and reached the LLC (42.1 million requests), only 12.16% missed the LLC, meaning only 5.12 million requests required expensive main RAM access over the 10-second period.
System-Wide Cache Profiling Across CPU Sockets
On multi-socket NUMA servers hosting virtualization or database clusters, you can profile cache performance aggregated across all CPU cores or segmented by socket:
# Profile cache misses aggregated per physical CPU socket
sudo perf stat -a -A -e cache-misses,cache-references -- sleep 5
The -A flag displays statistics for each CPU core independently:
CPU0 2,145,200 cache-misses
CPU1 2,091,400 cache-misses
CPU2 8,451,900 cache-misses
CPU3 8,120,100 cache-misses
Significant disparities between cores highlight asymmetric workload distribution or NUMA memory node misalignments where a thread on socket 0 is accessing memory allocated on socket 1.
Pinpointing Cache-Intensive Code with perf record
Once perf stat confirms that high cache misses are hurting performance, switch to perf record targeting the cache-misses event to identify the exact functions and machine instructions responsible:
# Sample the hottest instruction addresses causing cache misses
sudo perf record -e cache-misses -g -p 18452 -- sleep 15
# View the interactive report sorted by cache miss percentage
perf report -i perf.data
In the interactive browser, drill down into the top functions and press a to disassemble the code. Instructions associated with memory pointers (such as mov (%rax), %rcx) showing large percentages indicate where cache-line unaligned structures or pointer chasing are occurring.
Using perf stat to profile CPU cache hierarchies empowers Linux software developers and performance engineers to optimize data layout, restructure loops for cache blocking, and eliminate expensive memory access stalls.