Sling Academy
Home/Rust/Benchmarking Math Operations in Rust with Criterion

Benchmarking Math Operations in Rust with Criterion

Last updated: January 03, 2025

Benchmarking is a critical exercise for optimizing software performance, particularly in languages like Rust that aim to provide fine-grained control over system resources. One popular library for benchmarking in Rust is Criterion. Criterion is highly regarded for its statistical analysis capabilities, providing insights that are far more comprehensive than traditional methods.

Setting Up Criterion in Rust

To begin using Criterion, you first need to add it as a dependency in your Rust project. You can do this by adding the following line under the [dependencies] section in your Cargo.toml file:

[dependencies]
criterion = "0.3"

Once Criterion is specified as a dependency, it is essential to enable the criterion-benchmark feature. First, create a new directory named benches in your project root, and inside, create a Rust file, let's say my_bench.rs.

Writing Your First Criterion Benchmark

Let’s demonstrate a simple example of benchmarking a mathematical operation, such as calculating factorials, with Criterion.

extern crate criterion;
use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn factorial(n: u64) -> u64 {
    match n {
        0 | 1 => 1,
        _ => n * factorial(n - 1),
    }
}

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("factorial 20", |b| b.iter(|| factorial(black_box(20))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

Here are the steps broken down:

  • Import the required modules from Criterion.
  • Define a simple recursive factorial function that calculates the factorial of a number.
  • Within criterion_benchmark, called c.bench_function to create a benchmark test. black_box is used here to prevent compiler optimizations that might eliminate the loop operation as a no-op if judged unnecessary by optimization passes.
  • Create a benchmark group and main function using the macros criterion_group! and criterion_main!

Interpreting Criterion's Output

You run your benchmarks by executing:

cargo bench

Criterion will generate detailed output showing the time taken for each function call under test and providing averaged results with confidence intervals. This data helps identify bottlenecks effectively.

Adding Complexity to Math Benchmarks

For more comprehensive benchmarking, let’s explore investigating computations involving floating-point operations or matrix manipulations. Let’s consider benchmarking a function that calculates the dot product of two vectors.

fn dot_product(v1: &[f64], v2: &[f64]) -> f64 {
    v1.iter().zip(v2).map(|(x, y)| x * y).sum()
}

fn bench_dot_product(c: &mut Criterion) {
    let vec1 = vec![1.0_f64; 1000];
    let vec2 = vec![2.0_f64; 1000];
    c.bench_function("dot product 1000", |b| b.iter(|| dot_product(black_box(&vec1), black_box(&vec2))));
}

criterion_group!(benches, bench_dot_product);
criterion_main!(benches);

Conclusion

Criterion provides a comprehensive suite for performance analysis of Rust code. By following the examples above, you can gain insights into how mathematical operations perform under certain conditions, ultimately optimizing your code for performance-intensive applications. The library's utility underpins its power, especially for systems where efficiency and reliability are paramount.

Next Article: Parsing Numbers from Strings and Command-Line Inputs in Rust

Previous Article: Using the `BigInt` and `BigUint` Types for Arbitrary Precision in Rust

Series: Math and Numbers in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior