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

Instruction Cache Optimization

You will structure your binary layout to minimize instruction cache misses during critical paths.

In this lesson, you will learn how to physically structure your binary’s layout to minimize instruction cache (I-cache) misses during critical execution paths.

The Problem: Locality of Reference

The CPU’s instruction cache is a high-speed buffer that stores the machine code currently being executed. If the CPU needs to fetch instructions not currently in the cache, it suffers a "cache miss," forcing a stall while data is fetched from slower main memory. When your critical "hot" code is scattered across different memory pages or spread far apart, the I-cache must constantly evict and reload lines, drastically degrading performance.

Controlling Binary Layout

In Rust, the compiler generally handles function placement, but you can influence the I-cache footprint by grouping your "hot" code together. By placing functions that frequently call each other in the same translation unit (or using linker scripts to force them into adjacent memory segments), you ensure they share the same cache lines.

Consider this hot-path scenario:

#[inline(always)]
fn hot_logic_part_a() { /* ... */ }

#[inline(always)]
fn hot_logic_part_b() { /* ... */ }

/// This function is marked cold to move it out of the hot cache lines
#[cold]
fn error_handling_path() { /* ... */ }

pub fn critical_path() {
    hot_logic_part_a();
    hot_logic_part_b();
    if unlikely_condition() {
        error_handling_path();
    }
}

Strategic Placement

By utilizing the #[cold] attribute, you hint to the compiler that a function is rarely executed. The compiler will often move these functions to the end of the binary (the "cold" section), effectively pushing them out of the primary cache lines used by your critical_path.

Conversely, when writing critical, performance-sensitive code:

  1. Reduce binary bloat: Excessive templating or massive generics can lead to code bloat, filling up your I-cache with redundant machine instructions.
  2. Cluster hot functions: If you have a tight loop, keep the helper functions invoked within that loop physically near the loop body.
  3. Minimize branching: Use #[inline(always)] sparingly on short functions to help the compiler fuse code blocks into a single linear stream, which is much friendlier to the CPU’s prefetcher.

Key Takeaway: To optimize I-cache performance, cluster your frequently executed code blocks in close physical proximity and move cold, error-handling logic to the fringes of your binary.