Course contentsShow
← Rust
Lesson 1454 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Profiling Memory Bandwidth Usage

You will identify and resolve memory-bound bottlenecks by optimizing access patterns.

In this lesson, you will learn how to identify and resolve performance bottlenecks caused by saturating the memory bus, shifting your focus from instruction count to data throughput.

The Memory Wall

Even with highly optimized algorithms, your code may be "memory-bound," meaning the CPU spends most of its cycles waiting for data to arrive from RAM rather than executing instructions. When your application exceeds the bandwidth capacity of your memory controllers, adding more CPU cores or streamlining assembly won't improve throughput. To profile this, we look for high rates of cache misses that force expensive round-trips to main memory.

Identifying Throughput Bottlenecks

On Linux, tools like perf are essential for profiling bandwidth. You can monitor hardware events that track memory access patterns:

# Monitor cache misses and bandwidth-related events
perf stat -e LLC-load-misses,LLC-loads,node-loads,node-load-misses ./your_executable

High LLC-load-misses (Last Level Cache) indicate that your CPU is constantly fetching data from DRAM. In Rust, this often occurs due to "pointer chasing" (traversing linked lists or trees) where the next address is unknown until the current fetch finishes.

Optimizing Access Patterns

To reduce memory bandwidth usage, we must maximize spatial locality—ensuring that when a cache line is fetched, we use every byte of it before it is evicted.

If you are using a structure of arrays (SoA) versus an array of structures (AoS), consider how the CPU pulls data. If you only need one field from a 64-byte struct, you waste 90% of your memory bandwidth. Switching to a packed, column-oriented layout ensures that the hardware prefetcher can stream data linearly into the cache:

// Inefficient (AoS): High bandwidth usage due to striding across disparate fields
struct Entity { position: [f32; 3], velocity: [f32; 3], id: u64 }

// Efficient (SoA): Sequential access minimizes bus traffic
struct EntityManager {
    positions: Vec<[f32; 3]>,
    velocities: Vec<[f32; 3]>,
}

By realigning your data structures to match the hardware's cache line size (usually 64 bytes), you minimize the number of requests sent to the memory controller, effectively lowering the pressure on the system bus.

Key Takeaway: Memory-bound performance is improved not by writing faster code, but by restructuring data to ensure the hardware prefetcher can load contiguous blocks into cache, thereby reducing redundant DRAM requests.