Course contentsShow
← Linux
Lesson 1702 of 1,81914. Observability and Performance TroubleshootingFree lesson

Tracking Cache Locality in Applications

Optimize data structures to increase L3 cache hit rates based on hardware counter data.

In this lesson, you will learn how to bridge the gap between hardware-level cache performance counters and high-level application data structures to maximize L3 cache efficiency.

Understanding the L3 Bottleneck

At the advanced level, your performance bottleneck often hides in the disparity between CPU execution speed and memory latency. While the L1/L2 caches are private to cores, the L3 (Last Level Cache) is shared across cores on a socket. When your application structure forces constant L3 misses, the CPU must fetch data from DRAM, a process that can take hundreds of cycles.

To diagnose this, we use perf stat to monitor hardware events, specifically looking for LLC-load-misses and LLC-loads. A high miss rate suggests that your data access patterns are "scattering" across memory rather than exploiting spatial or temporal locality.

Optimizing Data Structures

If hardware counters reveal a high L3 miss rate, the culprit is often a pointer-heavy structure, such as a linked list or a tree where nodes are scattered throughout the heap. Because these nodes are not contiguous, prefetchers fail to predict the next memory address, and every traversal potentially triggers a new L3 miss.

Consider a linked list of objects versus a contiguous array:

// Poor Cache Locality (Pointer Chasing)
struct Node { int data; struct Node* next; }; 
// Each node is allocated independently; memory is fragmented.

// High Cache Locality (Data-Oriented Design)
struct DataBlock { int data[1024]; };
// Contiguous memory ensures that when the CPU fetches one int, 
// the cache line (typically 64 bytes) pulls in the next several values.

By refactoring your data structures to be contiguous (using flat arrays or std::vector in C++), you align your data with the hardware’s cache-line fetching logic. When you iterate through a contiguous block, the hardware prefetcher recognizes the sequential access pattern and populates the L3 cache before your code even requests the subsequent elements.

Once you modify your structures, run perf again. A significant drop in LLC-load-misses relative to the total number of loads confirms that your data layout is now cache-friendly.

Key Takeaway: Improving L3 hit rates requires transforming pointer-chasing, fragmented structures into contiguous memory layouts that allow the CPU's hardware prefetcher to anticipate and load your data ahead of execution.