Rust Performance Tuning Best Practices¶
Objective: Master senior-level Rust performance optimization patterns for production systems. When you need to optimize for speed and memory efficiency, when you want to build high-performance applications, when you need enterprise-grade performance strategiesโthese best practices become your weapon of choice.
Core Principles¶
- Measure First: Profile before optimizing
- Zero-Cost Abstractions: Leverage Rust's zero-cost abstractions
- Memory Efficiency: Minimize allocations
- Cache Locality: Optimize for CPU cache performance
- Parallel Processing: Utilize multiple cores effectively
Profiling & Benchmarking¶
Performance Measurement¶
// rust/01-performance-measurement.rs
/*
Performance measurement and profiling patterns for Rust
*/
use std::time::{Duration, Instant};
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use rayon::prelude::*;
/// A high-performance data processor for benchmarking.
pub struct DataProcessor {
data: Vec<u64>,
cache: HashMap<u64, u64>,
}
impl DataProcessor {
pub fn new(size: usize) -> Self {
Self {
data: (0..size as u64).collect(),
cache: HashMap::new(),
}
}
/// Process data with caching for performance measurement.
pub fn process_with_cache(&mut self) -> u64 {
let mut sum = 0u64;
for &value in &self.data {
if let Some(&cached) = self.cache.get(&value) {
sum += cached;
} else {
let processed = self.expensive_calculation(value);
self.cache.insert(value, processed);
sum += processed;
}
}
sum
}
/// Process data without caching for comparison.
pub fn process_without_cache(&self) -> u64 {
let mut sum = 0u64;
for &value in &self.data {
sum += self.expensive_calculation(value);
}
sum
}
/// Process data in parallel for performance measurement.
pub fn process_parallel(&self) -> u64 {
self.data.par_iter()
.map(|&value| self.expensive_calculation(value))
.sum()
}
/// Expensive calculation for benchmarking.
fn expensive_calculation(&self, value: u64) -> u64 {
// Simulate expensive computation
let mut result = value;
for _ in 0..1000 {
result = result.wrapping_mul(3).wrapping_add(1);
}
result
}
}
/// Performance measurement utilities.
pub struct PerformanceProfiler {
measurements: Vec<(String, Duration)>,
}
impl PerformanceProfiler {
pub fn new() -> Self {
Self {
measurements: Vec::new(),
}
}
/// Measure the execution time of a function.
pub fn measure<F, R>(&mut self, name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let start = Instant::now();
let result = f();
let duration = start.elapsed();
self.measurements.push((name.to_string(), duration));
result
}
/// Get all measurements.
pub fn get_measurements(&self) -> &[(String, Duration)] {
&self.measurements
}
/// Get the fastest measurement.
pub fn get_fastest(&self) -> Option<&(String, Duration)> {
self.measurements.iter().min_by_key(|(_, duration)| *duration)
}
/// Get the slowest measurement.
pub fn get_slowest(&self) -> Option<&(String, Duration)> {
self.measurements.iter().max_by_key(|(_, duration)| *duration)
}
/// Calculate performance improvement.
pub fn calculate_improvement(&self, baseline: &str, optimized: &str) -> Option<f64> {
let baseline_duration = self.measurements.iter()
.find(|(name, _)| name == baseline)
.map(|(_, duration)| duration.as_nanos())?;
let optimized_duration = self.measurements.iter()
.find(|(name, _)| name == optimized)
.map(|(_, duration)| duration.as_nanos())?;
Some(baseline_duration as f64 / optimized_duration as f64)
}
}
/// Benchmark suite for performance testing.
pub struct BenchmarkSuite {
profiler: PerformanceProfiler,
}
impl BenchmarkSuite {
pub fn new() -> Self {
Self {
profiler: PerformanceProfiler::new(),
}
}
/// Run comprehensive benchmarks.
pub fn run_benchmarks(&mut self, data_size: usize) {
let mut processor = DataProcessor::new(data_size);
// Benchmark without caching
self.profiler.measure("without_cache", || {
processor.process_without_cache()
});
// Benchmark with caching
self.profiler.measure("with_cache", || {
processor.process_with_cache()
});
// Benchmark parallel processing
self.profiler.measure("parallel", || {
processor.process_parallel()
});
// Benchmark with different data sizes
for size in [100, 1000, 10000] {
let mut small_processor = DataProcessor::new(size);
self.profiler.measure(&format!("cached_{}", size), || {
small_processor.process_with_cache()
});
}
}
/// Print benchmark results.
pub fn print_results(&self) {
println!("Benchmark Results:");
println!("==================");
for (name, duration) in self.profiler.get_measurements() {
println!("{}: {:?}", name, duration);
}
if let Some(fastest) = self.profiler.get_fastest() {
println!("\nFastest: {} ({:?})", fastest.0, fastest.1);
}
if let Some(slowest) = self.profiler.get_slowest() {
println!("Slowest: {} ({:?})", slowest.0, slowest.1);
}
if let Some(improvement) = self.profiler.calculate_improvement("without_cache", "with_cache") {
println!("Cache improvement: {:.2}x", improvement);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_performance_measurement() {
let mut profiler = PerformanceProfiler::new();
let result = profiler.measure("test", || {
thread::sleep(Duration::from_millis(10));
42
});
assert_eq!(result, 42);
assert_eq!(profiler.get_measurements().len(), 1);
}
#[test]
fn test_benchmark_suite() {
let mut suite = BenchmarkSuite::new();
suite.run_benchmarks(1000);
let measurements = suite.profiler.get_measurements();
assert!(measurements.len() >= 3);
}
}
Memory Optimization¶
// rust/02-memory-optimization.rs
/*
Memory optimization patterns for Rust applications
*/
use std::collections::HashMap;
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
/// Custom memory allocator for tracking allocations.
pub struct TrackingAllocator {
inner: System,
allocated: AtomicUsize,
peak: AtomicUsize,
}
unsafe impl GlobalAlloc for TrackingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr = self.inner.alloc(layout);
if !ptr.is_null() {
let size = layout.size();
let current = self.allocated.fetch_add(size, Ordering::Relaxed);
let peak = self.peak.load(Ordering::Relaxed);
if current + size > peak {
self.peak.store(current + size, Ordering::Relaxed);
}
}
ptr
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.inner.dealloc(ptr, layout);
self.allocated.fetch_sub(layout.size(), Ordering::Relaxed);
}
}
impl TrackingAllocator {
pub fn new() -> Self {
Self {
inner: System,
allocated: AtomicUsize::new(0),
peak: AtomicUsize::new(0),
}
}
pub fn get_allocated(&self) -> usize {
self.allocated.load(Ordering::Relaxed)
}
pub fn get_peak(&self) -> usize {
self.peak.load(Ordering::Relaxed)
}
}
/// Memory-efficient data structure using object pooling.
pub struct ObjectPool<T> {
objects: Vec<T>,
available: Vec<usize>,
next_id: usize,
}
impl<T: Default> ObjectPool<T> {
pub fn new(capacity: usize) -> Self {
let mut objects = Vec::with_capacity(capacity);
let mut available = Vec::with_capacity(capacity);
for i in 0..capacity {
objects.push(T::default());
available.push(i);
}
Self {
objects,
available,
next_id: 0,
}
}
pub fn acquire(&mut self) -> Option<usize> {
self.available.pop()
}
pub fn release(&mut self, id: usize) {
if id < self.objects.len() {
self.available.push(id);
}
}
pub fn get_mut(&mut self, id: usize) -> Option<&mut T> {
self.objects.get_mut(id)
}
pub fn get(&self, id: usize) -> Option<&T> {
self.objects.get(id)
}
}
/// Memory-efficient string interning.
pub struct StringInterner {
strings: Vec<String>,
indices: HashMap<String, usize>,
}
impl StringInterner {
pub fn new() -> Self {
Self {
strings: Vec::new(),
indices: HashMap::new(),
}
}
pub fn intern(&mut self, s: &str) -> usize {
if let Some(&index) = self.indices.get(s) {
index
} else {
let index = self.strings.len();
self.strings.push(s.to_string());
self.indices.insert(s.to_string(), index);
index
}
}
pub fn get(&self, index: usize) -> Option<&str> {
self.strings.get(index).map(|s| s.as_str())
}
pub fn len(&self) -> usize {
self.strings.len()
}
}
/// Memory-efficient data structure using small string optimization.
#[derive(Clone)]
pub struct SmallString {
data: SmallStringData,
}
#[derive(Clone)]
enum SmallStringData {
Inline([u8; 23]), // 23 bytes for inline storage
Heap(String),
}
impl SmallString {
pub fn new(s: &str) -> Self {
if s.len() <= 23 {
let mut bytes = [0u8; 23];
bytes[..s.len()].copy_from_slice(s.as_bytes());
Self {
data: SmallStringData::Inline(bytes),
}
} else {
Self {
data: SmallStringData::Heap(s.to_string()),
}
}
}
pub fn as_str(&self) -> &str {
match &self.data {
SmallStringData::Inline(bytes) => {
let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
unsafe { std::str::from_utf8_unchecked(&bytes[..len]) }
}
SmallStringData::Heap(s) => s.as_str(),
}
}
pub fn len(&self) -> usize {
match &self.data {
SmallStringData::Inline(bytes) => {
bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len())
}
SmallStringData::Heap(s) => s.len(),
}
}
}
/// Memory-efficient data structure using bit packing.
pub struct BitPackedData {
data: Vec<u64>,
bits_per_item: usize,
items_per_word: usize,
}
impl BitPackedData {
pub fn new(bits_per_item: usize, capacity: usize) -> Self {
let items_per_word = 64 / bits_per_item;
let words_needed = (capacity + items_per_word - 1) / items_per_word;
Self {
data: vec![0; words_needed],
bits_per_item,
items_per_word,
}
}
pub fn set(&mut self, index: usize, value: u64) {
let word_index = index / self.items_per_word;
let item_index = index % self.items_per_word;
let bit_offset = item_index * self.bits_per_item;
let mask = (1u64 << self.bits_per_item) - 1;
let shifted_value = value << bit_offset;
self.data[word_index] &= !(mask << bit_offset);
self.data[word_index] |= shifted_value;
}
pub fn get(&self, index: usize) -> u64 {
let word_index = index / self.items_per_word;
let item_index = index % self.items_per_word;
let bit_offset = item_index * self.bits_per_item;
let mask = (1u64 << self.bits_per_item) - 1;
(self.data[word_index] >> bit_offset) & mask
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_object_pool() {
let mut pool = ObjectPool::<String>::new(10);
let id1 = pool.acquire().unwrap();
let id2 = pool.acquire().unwrap();
*pool.get_mut(id1).unwrap() = "Hello".to_string();
*pool.get_mut(id2).unwrap() = "World".to_string();
assert_eq!(pool.get(id1).unwrap(), "Hello");
assert_eq!(pool.get(id2).unwrap(), "World");
pool.release(id1);
pool.release(id2);
}
#[test]
fn test_string_interner() {
let mut interner = StringInterner::new();
let id1 = interner.intern("Hello");
let id2 = interner.intern("World");
let id3 = interner.intern("Hello"); // Should return same ID
assert_eq!(id1, id3);
assert_ne!(id1, id2);
assert_eq!(interner.get(id1), Some("Hello"));
assert_eq!(interner.get(id2), Some("World"));
}
#[test]
fn test_small_string() {
let s1 = SmallString::new("Hello");
let s2 = SmallString::new("This is a very long string that should be stored on the heap");
assert_eq!(s1.as_str(), "Hello");
assert_eq!(s1.len(), 5);
assert_eq!(s2.as_str(), "This is a very long string that should be stored on the heap");
assert_eq!(s2.len(), 58);
}
#[test]
fn test_bit_packed_data() {
let mut data = BitPackedData::new(4, 100); // 4 bits per item
data.set(0, 5);
data.set(1, 10);
data.set(2, 15);
assert_eq!(data.get(0), 5);
assert_eq!(data.get(1), 10);
assert_eq!(data.get(2), 15);
}
}
CPU Optimization¶
// rust/03-cpu-optimization.rs
/*
CPU optimization patterns for Rust applications
*/
use std::arch::x86_64::*;
use std::mem;
use std::simd::*;
/// SIMD-optimized vector operations.
pub struct SimdVector {
data: Vec<f32>,
}
impl SimdVector {
pub fn new(size: usize) -> Self {
Self {
data: vec![0.0; size],
}
}
pub fn from_slice(slice: &[f32]) -> Self {
Self {
data: slice.to_vec(),
}
}
/// SIMD-optimized dot product.
pub fn dot_product(&self, other: &SimdVector) -> f32 {
if self.data.len() != other.data.len() {
panic!("Vector dimensions must match");
}
let mut sum = 0.0;
let chunks = self.data.chunks_exact(8);
let other_chunks = other.data.chunks_exact(8);
for (chunk, other_chunk) in chunks.zip(other_chunks) {
if chunk.len() == 8 {
let a = f32x8::from_slice(chunk);
let b = f32x8::from_slice(other_chunk);
let product = a * b;
sum += product.reduce_sum();
}
}
// Handle remaining elements
let remainder = self.data.len() % 8;
if remainder > 0 {
let start = self.data.len() - remainder;
for i in start..self.data.len() {
sum += self.data[i] * other.data[i];
}
}
sum
}
/// SIMD-optimized vector addition.
pub fn add(&self, other: &SimdVector) -> SimdVector {
if self.data.len() != other.data.len() {
panic!("Vector dimensions must match");
}
let mut result = vec![0.0; self.data.len()];
let chunks = self.data.chunks_exact(8);
let other_chunks = other.data.chunks_exact(8);
let result_chunks = result.chunks_exact_mut(8);
for ((chunk, other_chunk), result_chunk) in chunks.zip(other_chunks).zip(result_chunks) {
if chunk.len() == 8 {
let a = f32x8::from_slice(chunk);
let b = f32x8::from_slice(other_chunk);
let sum = a + b;
result_chunk.copy_from_slice(&sum.to_array());
}
}
// Handle remaining elements
let remainder = self.data.len() % 8;
if remainder > 0 {
let start = self.data.len() - remainder;
for i in start..self.data.len() {
result[i] = self.data[i] + other.data[i];
}
}
SimdVector { data: result }
}
/// SIMD-optimized vector scaling.
pub fn scale(&self, factor: f32) -> SimdVector {
let mut result = vec![0.0; self.data.len()];
let chunks = self.data.chunks_exact(8);
let result_chunks = result.chunks_exact_mut(8);
for (chunk, result_chunk) in chunks.zip(result_chunks) {
if chunk.len() == 8 {
let a = f32x8::from_slice(chunk);
let scaled = a * f32x8::splat(factor);
result_chunk.copy_from_slice(&scaled.to_array());
}
}
// Handle remaining elements
let remainder = self.data.len() % 8;
if remainder > 0 {
let start = self.data.len() - remainder;
for i in start..self.data.len() {
result[i] = self.data[i] * factor;
}
}
SimdVector { data: result }
}
}
/// Cache-friendly data structure using SoA (Structure of Arrays).
pub struct SoAData {
x: Vec<f32>,
y: Vec<f32>,
z: Vec<f32>,
mass: Vec<f32>,
}
impl SoAData {
pub fn new(capacity: usize) -> Self {
Self {
x: Vec::with_capacity(capacity),
y: Vec::with_capacity(capacity),
z: Vec::with_capacity(capacity),
mass: Vec::with_capacity(capacity),
}
}
pub fn add_particle(&mut self, x: f32, y: f32, z: f32, mass: f32) {
self.x.push(x);
self.y.push(y);
self.z.push(z);
self.mass.push(mass);
}
/// Cache-friendly computation on all particles.
pub fn compute_center_of_mass(&self) -> (f32, f32, f32) {
let mut total_mass = 0.0;
let mut weighted_x = 0.0;
let mut weighted_y = 0.0;
let mut weighted_z = 0.0;
for i in 0..self.x.len() {
let mass = self.mass[i];
total_mass += mass;
weighted_x += self.x[i] * mass;
weighted_y += self.y[i] * mass;
weighted_z += self.z[i] * mass;
}
if total_mass > 0.0 {
(
weighted_x / total_mass,
weighted_y / total_mass,
weighted_z / total_mass,
)
} else {
(0.0, 0.0, 0.0)
}
}
/// Cache-friendly computation with SIMD.
pub fn compute_center_of_mass_simd(&self) -> (f32, f32, f32) {
let mut total_mass = 0.0;
let mut weighted_x = 0.0;
let mut weighted_y = 0.0;
let mut weighted_z = 0.0;
let chunks = self.x.len() / 8;
for i in 0..chunks {
let start = i * 8;
let x_chunk = f32x8::from_slice(&self.x[start..start + 8]);
let y_chunk = f32x8::from_slice(&self.y[start..start + 8]);
let z_chunk = f32x8::from_slice(&self.z[start..start + 8]);
let mass_chunk = f32x8::from_slice(&self.mass[start..start + 8]);
total_mass += mass_chunk.reduce_sum();
weighted_x += (x_chunk * mass_chunk).reduce_sum();
weighted_y += (y_chunk * mass_chunk).reduce_sum();
weighted_z += (z_chunk * mass_chunk).reduce_sum();
}
// Handle remaining elements
let remainder = self.x.len() % 8;
if remainder > 0 {
let start = chunks * 8;
for i in start..self.x.len() {
let mass = self.mass[i];
total_mass += mass;
weighted_x += self.x[i] * mass;
weighted_y += self.y[i] * mass;
weighted_z += self.z[i] * mass;
}
}
if total_mass > 0.0 {
(
weighted_x / total_mass,
weighted_y / total_mass,
weighted_z / total_mass,
)
} else {
(0.0, 0.0, 0.0)
}
}
}
/// Branch prediction optimization.
pub struct BranchOptimizedSorter {
data: Vec<i32>,
}
impl BranchOptimizedSorter {
pub fn new(data: Vec<i32>) -> Self {
Self { data }
}
/// Branch-optimized sorting using bitonic sort.
pub fn bitonic_sort(&mut self) {
let n = self.data.len();
if n <= 1 {
return;
}
for k in 2..=n {
for j in (k / 2)..k {
for i in 0..n {
let l = i ^ j;
if l > i {
if ((i & k) == 0) == (self.data[i] > self.data[l]) {
self.data.swap(i, l);
}
}
}
}
}
}
/// Branch-optimized sorting using radix sort.
pub fn radix_sort(&mut self) {
let max = self.data.iter().max().copied().unwrap_or(0);
let mut exp = 1;
while max / exp > 0 {
self.counting_sort(exp);
exp *= 10;
}
}
fn counting_sort(&mut self, exp: i32) {
let n = self.data.len();
let mut output = vec![0; n];
let mut count = vec![0; 10];
for &item in &self.data {
count[((item / exp) % 10) as usize] += 1;
}
for i in 1..10 {
count[i] += count[i - 1];
}
for i in (0..n).rev() {
let index = ((self.data[i] / exp) % 10) as usize;
output[count[index] - 1] = self.data[i];
count[index] -= 1;
}
self.data.copy_from_slice(&output);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simd_vector() {
let v1 = SimdVector::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
let v2 = SimdVector::from_slice(&[2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
let dot = v1.dot_product(&v2);
assert!((dot - 204.0).abs() < 1e-6);
let sum = v1.add(&v2);
assert_eq!(sum.data.len(), 8);
}
#[test]
fn test_soa_data() {
let mut data = SoAData::new(100);
data.add_particle(1.0, 2.0, 3.0, 1.0);
data.add_particle(4.0, 5.0, 6.0, 2.0);
let (x, y, z) = data.compute_center_of_mass();
assert!((x - 3.0).abs() < 1e-6);
assert!((y - 4.0).abs() < 1e-6);
assert!((z - 5.0).abs() < 1e-6);
}
#[test]
fn test_branch_optimized_sorter() {
let mut sorter = BranchOptimizedSorter::new(vec![3, 1, 4, 1, 5, 9, 2, 6]);
sorter.bitonic_sort();
let expected = vec![1, 1, 2, 3, 4, 5, 6, 9];
assert_eq!(sorter.data, expected);
}
}
TL;DR Runbook¶
Quick Start¶
// 1. Performance measurement
use std::time::Instant;
let start = Instant::now();
// Your code here
let duration = start.elapsed();
// 2. Memory optimization
use std::collections::HashMap;
let mut cache = HashMap::new();
// Use object pooling and string interning
// 3. CPU optimization
use std::simd::*;
let a = f32x8::from_slice(&data);
let b = f32x8::from_slice(&other_data);
let result = a * b;
// 4. Cache optimization
// Use SoA (Structure of Arrays) instead of AoS (Array of Structures)
// 5. Branch prediction
// Use branchless programming techniques
Essential Patterns¶
// Complete performance optimization setup
pub fn setup_rust_performance() {
// 1. Profiling and benchmarking
// 2. Memory optimization
// 3. CPU optimization
// 4. Cache optimization
// 5. SIMD programming
// 6. Branch prediction
// 7. Parallel processing
// 8. Zero-cost abstractions
println!("Rust performance optimization setup complete!");
}
This guide provides the complete machinery for Rust performance tuning. Each pattern includes implementation examples, optimization strategies, and real-world usage patterns for enterprise performance optimization.