Как правильно замерять производительность (бенчмаркинг) программ на Rust?
Правильный подход к бенчмаркингу в Rust
Ваша проблема типична для новичков: std::time::Instant даёт неточные результаты из-за оптимизаций компилятора и шума в измерениях. Рассмотрим три ключевых аспекта: защита от оптимизаций, усреднение замеров и профессиональные инструменты.
1. Использование std::time::Instant с защитой от оптимизаций
Компилятор Rust (LLVM) может полностью удалить код, результат которого не используется. Чтобы этого избежать, используйте std::hint::black_box (стабильно с Rust 1.66):
use std::time::Instant;
use std::hint::black_box;
fn sum_vec(vec: &[i32]) -> i32 {
vec.iter().sum()
}
fn main() {
let data = vec![1; 1_000_000];
// Прогревочный запуск (убираем влияние кэша процессора)
black_box(sum_vec(&data));
let start = Instant::now();
let result = black_box(sum_vec(&data));
let duration = start.elapsed();
println!("Sum: {}, Time: {:?}", result, duration);
}
black_box заставляет компилятор считать, что аргумент и результат используются, предотвращая оптимизацию. Также лучше передавать срез &[i32], а не &Vec<i32> — это более идиоматично.
2. Многократные замеры и статистика
Одиночный замер ненадёжен. Лучше выполнить много итераций и усреднить:
fn benchmark(f: fn(), iterations: u32) -> std::time::Duration {
let mut total = std::time::Duration::new(0, 0);
for _ in 0..iterations {
let start = Instant::now();
black_box(f());
total += start.elapsed();
}
total / iterations
}
fn main() {
let data = vec![1; 1_000_000];
let closure = || sum_vec(&data);
// Прогрев
for _ in 0..10 { black_box(closure()); }
let avg = benchmark(closure, 1000);
println!("Average time: {:?}", avg);
}
3. Профессиональный инструмент: criterion
Для серьёзных проектов используйте крейт criterion. Он автоматически делает прогревы, усредняет, строит графики и устойчив к шуму.
Установка в Cargo.toml:
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "my_benchmark"
harness = false
Создайте файл benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn sum_vec(vec: &[i32]) -> i32 {
vec.iter().sum()
}
fn criterion_benchmark(c: &mut Criterion) {
let data = vec![1; 1_000_000];
c.bench_function("sum_vec", |b| {
b.iter(|| sum_vec(black_box(&data)))
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
Запуск: cargo bench. Вы получите:
- Среднее время выполнения
- Доверительные интервалы (99%)
- График распределения времени в формате HTML
- Сравнение с предыдущими запусками (регрессионное тестирование)
Почему ваш код давал 0 микросекунд?
Без black_box компилятор видит, что результат sum_vec не используется, и полностью удаляет вызов. С Instant это даёт время, близкое к нулю. Прогрев и усреднение решают проблему шума.
Совет: для микробенчмарков (функции, выполняющиеся < 1 мкс) всегда используйте criterion — он корректно измеряет даже наносекунды.
Альтернативный подход: встроенный test с атрибутом #[bench]
Если вы не хотите подключать внешние крейты, Rust предоставляет нестабильный API для бенчмаркинга через #![feature(test)]. Этот метод требует nightly-версии компилятора.
В Cargo.toml добавьте:
[lib]
bench = false # отключаем стандартный bench
[[bench]]
name = "my_bench"
harness = false # используем свой бенчмарк
Файл benches/my_bench.rs:
#![feature(test)]
extern crate test;
use test::Bencher;
#[bench]
fn bench_sum_vec(b: &mut Bencher) {
let data = vec![1; 1_000_000];
b.iter(|| {
let _ = data.iter().sum::<i32>();
});
}
Запуск: cargo +nightly bench. Вы получите:
- Количество итераций (автоматически подбирается)
- Среднее время на итерацию
- Доверительный интервал
Недостатки:
- Требуется nightly Rust (нестабильный канал)
- Нет графиков и сравнения с предыдущими запусками
- Меньше настроек, чем у
criterion
Когда использовать: Если вы уже используете nightly для других целей и нужен быстрый замер без установки дополнительных крейтов. Для production-проектов лучше выбрать criterion.
Важно: Не забудьте про black_box — в этом API он встроен в Bencher, но можно дополнительно обернуть данные.
Ответов пока нет.
Войдите чтобы оставить ответ