π¦ Rust Master Class - Chapter 26: Performance
Marathon finish line. Didn't win, but beat my PB by 30 seconds. Those seconds came from a thousand small decisions. Performance isn't one big thing β it's a thousand small ones.
Measuring and improving performance in Rust involves a combination of accurate benchmarking to identify bottlenecks and the application of specific coding patterns to minimize memory allocations and maximize execution speed.
1. Measuring Performance: Benchmarking
To accurately measure code performance, the Criterion crate is the standard tool used in the Rust ecosystem. It provides detailed statistics and allows you to compare different implementations of the same logic .
- Key Concept: Benchmarking should be done using a dedicated harness to account for CPU thermal throttling and background noise.
- Configuration: You must add Criterion to your
dev-dependenciesinCargo.tomland define a benchmark target .
Code Example (Comparing Sorting Algorithms):
// sorting_benchmark.rs
use criterion::{criterion_group, criterion_main, Criterion};
use learning_rust::{sort_algo_1, sort_algo_2};
fn sort_benchmark(c: &mut Criterion) {
// Create a mutable variable
let mut numbers = vec![3-10];
// Compares the execution time of two different sorting implementations
// Make an independent copy
c.bench_function("sort_algo_1", |b| b.iter(|| sort_algo_1(&mut numbers.clone())));
// Make an independent copy
c.bench_function("sort_algo_2", |b| b.iter(|| sort_algo_2(&mut numbers.clone())));
}
criterion_group!(benches, sort_benchmark);
criterion_main!(benches);
[Source: 172, 173]
2. Minimizing Memory Allocations
Unnecessary heap allocations are a primary source of performance degradation. Rust provides several ways to avoid them.
-
with_capacity: When creating a collection like aVec, usingwith_capacityinstead ofnewprevents the performance hit of multiple reallocations as the collection grows . - Avoiding Re-allocations with
std::mem: Instead of cloning or creating new objects, useswap,take, orreplaceto move data out of a mutable location and put a default or new value in its place .
Code Example (Using swap):
use std::mem::swap;
fn main() {
// Create a mutable variable
let mut text = "the developer".to_string();
// Create a mutable variable
let mut copied_text = "developer".to_string();
// Swaps the contents of text and copied_text without re-allocating memory
swap(&mut text, &mut copied_text);
}
[Source: 12, 355]
3. Efficient Function Inputs and Coercion
To make functions more flexible and performant, use slices (&str or &[T]) rather than owned collections (&String or &Vec<T>).
- Deref Coercion: Slices allow for deref coercion, meaning the function can accept both a slice and a reference to the owned type without extra allocation .
- Performance Gain: This avoids unnecessary "double redirection" (e.g., a reference to a
Boxthat contains a string) .
Code Example:
// More efficient: accepts &str (slice) and &String (via coercion)
fn vowels(word: &str) -> u8 {
// processing logic
0
}
fn main() {
// Create a new variable
let s = String::from("Rust");
vowels(&s); // Works via coercion
vowels("Rust"); // Works directly
}
[Source: 53, 54, 100, 285]
4. Optimizing Data Structures
- Boxing Large Enum Variants: The size of an enum is determined by its largest variant. If one variant is significantly larger than others, it "bloats" the entire enum . By wrapping the large variant in a
Box, the enum only stores a pointer, significantly reducing its memory footprint on the stack . - Static vs. Dynamic Dispatch: Use Static Dispatch (Generics with Trait Bounds) whenever possible. The compiler generates specific code for each type used, which allows for inlining and is generally faster than Dynamic Dispatch (
&dyn Trait), which relies on a vtable lookup at runtime [18-20].
Code Example (Static Dispatch):
trait Print { fn print(&self); }
// Faster: Resolved at compile time
fn static_display<T: Print>(value: T) {
value.print();
}
[Source: 33, 133, 283]
5. Efficient Programming Tips
- Initialization: Use the
Defaulttrait and struct update syntax (..Default::default()) to initialize only the necessary fields of a struct efficiently . - Performance Lints: Utilize Rustβs performance lints to catch common sub-optimal patterns during development .
- String Concatenation: Be mindful of ownership when concatenating strings to avoid unnecessary copies; the
format!macro is often useful for combining data into a new string without taking ownership of the inputs .
π Download the full PDF: https://drive.google.com/file/d/13Ui9FWMikWqjRVx4l3V6aYibXOn9ehHC/view?usp=sharing
Part 26 of the Rust Master Class series β STEM EdTech | Automation Consulting | Rust Tutoring
RustLang #Programming #LearnToCode #STEM #EdTech
π Practice Resources
GitHub Repository: https://github.com/PacktPublishing/Rust-Programming-Master-Class-from-Beginner-to-Expert
Try it yourself: https://play.rust-lang.org/
Run the code from this chapter in the Rust playground, then clone the repo to continue your Rust journey!
Part 26 of the Rust Master Class series β STEM EdTech | Automation Consulting | Rust Tutoring
Top comments (0)