Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
238: Link-Time Optimization (LTO) Configuration
I've been profiling this data-processing tool I'm working on, and something is bugging me. I have a separate crate called fast_math that handles some heavy lifting—specifically, a tight loop performing coordinate transformations. Even in --release mode, the profiler shows a surprising amount of time spent jumping back and forth between my main binary and that utility crate. It's not a massive bottleneck, but it's inefficient.
The "Inline" Illusion
My first instinct was to just slap #[inline] on the transformation functions in fast_math. I did that, pushed the code, and ran the benchmark again. The result? Almost no change.
Here is the catch that trips a lot of people up: #[inline] is a suggestion, not a command. More importantly, when Rust compiles your project, it usually compiles each crate independently. By the time the linker gets involved to stitch the fast_math library and the main binary together, the opportunity to inline that function into the main loop has already passed. The compiler has already finished its optimization pass for the individual units.
// In fast_math/src/lib.rs
#[inline]
pub fn transform_coord(x: f64, y: f64) -> f64 {
(x * 0.707) + (y * 0.707) // Simplified math for the example
}
If I want the compiler to actually look across the boundary of these two crates and say, "Hey, I can just shove the logic of transform_coord directly into the main loop," I need Link-Time Optimization (LTO).
Turning the Dial to "Fat"
Let's see what happens when we tell Cargo to go all out. I'll head into my Cargo.toml and add this to the release profile:
[profile.release]
lto = true
When I run cargo build --release now, I notice something immediately: the "Linking" stage is taking significantly longer. Like, way longer. It's not just a few seconds; it's a noticeable pause where my CPU fans start spinning up. This is "Fat LTO".
With lto = true (or "fat"), the compiler doesn't just optimize crates individually; it generates a massive amount of intermediate representation for everything and then performs a global optimization during the linking phase. The performance gain on my coordinate loop was about 8%—not huge, but measurable—because the compiler finally decided to inline those calls across the crate boundary.
Finding a Middle Ground
The problem is that as the project grows, "Fat" LTO becomes a nightmare for build times. I don't want to wait three minutes for a linker to finish every time I make a tiny change to a leaf crate. I wondered if there was a way to get most of the benefit without the agonizing wait.
I switched the config to "thin":
[profile.release]
lto = "thin"
I ran the build again. The linking phase was suddenly much faster—almost back to normal—but the execution speed of my program remained nearly identical to the "Fat" version. Thin LTO achieves this by performing optimizations in parallel and using a more lightweight summary of the crates rather than the full representation. In my experience, unless you are squeezing every single clock cycle out of a tiny embedded system, "thin" is almost always the right choice.
The Trade-off Matrix
Looking back at the three states I've tried, here is how I think about it now when I'm configuring a new project:
- lto = false (Default): Fast builds. Each crate is an island. Use this for development and most standard releases where performance is "good enough."
- lto = "thin": A great balance. You get cross-crate inlining and dead-code elimination with a modest hit to build time. This is my new default for production.
- lto = "fat": Maximum optimization. The compiler sees everything. Use this for final release candidates or performance-critical libraries where build time doesn't matter, but binary size and speed do.
📋 Practical Task
Optimizing the High-Frequency Signal Processor
You have been handed a project consisting of two crates: signal_proc (a library that handles Fourier transforms) and signal_app (the binary that feeds data into the library). The app is currently too slow for real-time processing because the core transformation function is being called 10 million times per second across the crate boundary.
Your Task:
- Open the
Cargo.tomlof thesignal_appcrate. - Configure the
[profile.release]section to enable "Thin" LTO to improve cross-crate inlining without causing excessive build times. - Additionally, set
codegen-units = 1in the same profile. (This forces the compiler to optimize the crate as a single unit, which often complements LTO by providing more optimization opportunities). - Run
cargo build --releaseand verify that the build completes successfully.
Expected Configuration:
[profile.release]
lto = "thin"
codegen-units = 1There are no comments for now.