Speed & Performance
Links:
- https://bencher.dev/learn/benchmarking/rust/criterion/
- https://bheisler.github.io/criterion.rs/book/criterion_rs.html
- https://github.com/flamegraph-rs/flamegraph
- https://rust.nizeclub.ru/_29.html
- https://doc.rust-lang.org/cargo/reference/profiles.html
- https://github.com/apollographql/rust-best-practices/blob/main/book/chapter_03.md
Существует две основные категории программных бенчмарков: микро-бенчмарки и макро-бенчмарки. Микро-бенчмарки работают на уровне, аналогичном модульным тестам. Макро-бенчмарки работают на уровне, аналогичном интеграционным тестам.
В целом, лучше всего тестировать на максимально низком уровне абстракции. В случае бенчмарков это делает их более простыми в поддержке и помогает уменьшить количество шума в измерениях. Однако, как и наличие некоторых сквозных тестов может быть очень полезным для проверки того, что вся система работает как ожидается, так и наличие макро-бенчмарков может быть очень полезным для обеспечения того, чтобы критические пути в вашем программном обеспечении оставались производительными.
std::time для быстрого замера
use std::time::Instant;
fn array_diff<T: PartialEq>(a: Vec<T>, b: Vec<T>) -> Vec<T> {
a.into_iter().filter(|x| !b.contains(x)).collect()
}
fn array_diff2<T: PartialEq>(mut a: Vec<T>, b: Vec<T>) -> Vec<T> {
a.retain(|x| !b.contains(x));
a
}
fn main() {
let start = Instant::now(); // начало замера 1
println!("{:?}", array_diff(vec![1, 2, 2], vec![1]));
let duration_a = start.elapsed(); // конец
let start = Instant::now(); // начало замера 2
println!("{:?}", array_diff2(vec![1, 2, 2], vec![1]));
let duration_b = start.elapsed(); // конец
println!("Замеры: 1) {:?}; 2) {:?}", duration_a, duration_b);
}Для более точного замера следует собирать код с cargo run --release ключом.
Criterion
В Cargo.toml надо добавить зависимости:
[dev-dependencies]
criterion = "0.8"
[[bench]]
name = "my_benchmark"
harness = falseДалее в проекте создать папку и файл benches\my_benchmark.rs:
use criterion::{Criterion, criterion_group, criterion_main};
fn array_diff<T: PartialEq>(a: Vec<T>, b: Vec<T>) -> Vec<T> {
a.into_iter().filter(|x| !b.contains(x)).collect()
} // функция 1 для проверки
fn array_diff2<T: PartialEq>(mut a: Vec<T>, b: Vec<T>) -> Vec<T> {
a.retain(|x| !b.contains(x));
a
} // функция 2 для проверки
fn benchmark_functions(c: &mut Criterion) {
let mut group = c.benchmark_group("Function Comparison");
group.bench_function("array_diff", |b| {
b.iter(|| {
std::hint::black_box(array_diff(vec![1, 2, 2], vec![1]));
}) // black_box блокирует оптимизации компилятора
});
group.bench_function("array_diff2", |b| {
b.iter(|| {
std::hint::black_box(array_diff2(vec![1, 2, 2], vec![1]));
})
});
group.finish();
}
criterion_group!(benches, benchmark_functions);
criterion_main!(benches);Для запуска тестов необходима команда cargo bench. Результаты замеров сохраняются и сравниваются с новыми результатами после внесения изменений.
flamegraph - визуализация стека вызовов
flamegraph — это инструмент для создания интерактивных SVG-диаграмм, которые визуализируют стек вызовов и время, затраченное в каждой функции. Он строится на основе данных от perf (xctrace в macOS) и помогает быстро понять, где сосредоточена нагрузка.
Установка:
cargo install flamegraphДля того, чтобы видеть заголовки запускаемых функций (не их хэши), нужно в файле cargo.toml добавить профиль разработки. При этом профилирование скорости нужно проводить в режиме release со всеми оптимизациями, поэтому следует сделать отдельный профиль под release, но с debug-символами:
[profile.perfmon]
inherits = "release"
debug = trueИспользование:
# принудительно указать свой профиль perfmon (по умолчанию --release)
cargo flamegraph --profile perfmon
# данные по выбранной функции
cargo flamegraph --profile perfmon --flamechart-opts "--grep functionName"
# Профилирование unit-тестов
# Разделитель `--` нужен, если `--unit-test` последний флаг.
cargo flamegraph --unit-test -- test::in::package::with::single::crate
cargo flamegraph --unit-test crate_name -- test::in::package::with::multiple:crate
# Профилирование интеграционных тестов
cargo flamegraph --test test_name
# Профилирование примера из папки examples в workspace
cargo flamegraph --example some_example --features some_features