Course contentsShow
← Rust
Lesson 1734 of 1,91015. Performance and Architectural EngineeringFree lesson

Loop Invariant Code Motion

Extract constant calculations out of loops to reduce redundant CPU cycles.

In this lesson, you will learn how to boost performance by identifying and extracting computations from inside loops that yield the same result on every iteration.

The Concept: Hoisting Invariance

Loop Invariant Code Motion (LICM) is a compiler optimization technique where we move code that produces a constant value—relative to the loop's lifecycle—outside of the loop body. If a calculation depends only on variables that do not change while the loop is running, executing it inside the loop is wasteful. Every redundant cycle spent re-calculating that value steals time from your CPU's execution pipeline.

Putting it into Rust

Consider a scenario where you are calculating a weight factor for a series of data points based on a static global configuration:

fn process_data(data: &mut [f64], multiplier: f64, offset: f64) {
    for val in data.iter_mut() {
        // The 'factor' calculation never changes, but it repeats every iteration!
        let factor = (multiplier * 2.0) + offset; 
        *val *= factor;
    }
}

In the code above, the CPU is forced to compute (multiplier * 2.0) + offset thousands of times, even though the inputs to this expression remain constant throughout the entire loop. We can manually perform LICM by "hoisting" the calculation:

fn process_data_optimized(data: &mut [f64], multiplier: f64, offset: f64) {
    let factor = (multiplier * 2.0) + offset; // Computed once
    for val in data.iter_mut() {
        *val *= factor;
    }
}

Why This Matters for Performance

While the Rust compiler (LLVM) is exceptionally good at identifying these opportunities during release builds, relying solely on the optimizer can be risky. Complex expressions involving function calls or boundary checks might prevent the compiler from safely hoisting the code for you. By explicitly performing LICM, you ensure that the instruction count remains minimal regardless of how complex the surrounding logic becomes. This is particularly vital in hot loops where even a single floating-point multiplication can add up to significant latency.

Key Takeaway: Manually hoist any expression that does not depend on the loop's iteration variable to ensure the CPU only computes it once per loop execution.