#rust
# 1. Criterion.rs
- 移植自 [Haskell's Criterion](https://hackage.haskell.org/package/criterion)
- **Github**:[bheisler/criterion.rs](https://github.com/bheisler/criterion.rs)
- **API Doc**: http://bheisler.github.io/criterion.rs/criterion/
- 开启 debug output:
````bash
CRITERION_DEBUG=1 cargo bench
````
## 1.1. Getting Started
- Cargo 将每个 benchmarks 编译成单独的 crate,独立于 main crate(测试的目标 crate)
- 需要以外部 crate 的形式引入 library crate
- 只能测试 public functions
```rust
use bench::fibonacci;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
```
- `criterion_main` macro:展开成 main 函数,来运行给定 groups 中的所有 benchmarks
- `criterion_group` macro:定义一个 group
- `bench_function()` :指定 benchmark ID 和 运行的 closure
-----------
# 2. User Guide
## 2.1. Migrating from libtest
## 2.2. Command-Line Output
输出示例:
```sh
$ cargo bench -- --verbose
Benchmarking alloc
Benchmarking alloc: Warming up for 1.0000 s
Benchmarking alloc: Collecting 100 samples in estimated 13.354 s (5050 iterations)
Benchmarking alloc: Analyzing
alloc time: [2.5094 ms 2.5306 ms 2.5553 ms]
thrpt: [391.34 MiB/s 395.17 MiB/s 398.51 MiB/s]
change: [-38.292% -37.342% -36.524%] (p = 0.00 < 0.05)
Performance has improved.
Found 8 outliers among 100 measurements (8.00%)
4 (4.00%) high mild
4 (4.00%) high severe
slope [2.5094 ms 2.5553 ms] R^2 [0.8660614 0.8640630]
mean [2.5142 ms 2.5557 ms] std. dev. [62.868 us 149.50 us]
median [2.5023 ms 2.5262 ms] med. abs. dev. [40.034 us 73.259 us]
```
### Warmup
- 自动地 iterate 被测试的函数,持续一段可配置的 warmup 时间(默认是 3 秒)。
- 用途:warm up CPU caches 和 文件系统 caches(如果有)
### Collecting Samples
- 自动地 iterate 多次(次数不定),统计每次迭代的耗时。
- 采样的时间可配置
### Time
```sh
time: [2.5094 ms 2.5306 ms 2.5553 ms]
thrpt: [391.34 MiB/s 395.17 MiB/s 398.51 MiB/s]
```
- 显示了该基准测试的每次迭代时间的置信区间
- 左边和右边的值分别是 lower 和 upper bounds
- 中间值是最好的值
- confidence level 可配置
- level 越大(例如 99%),will widen the interval and thus provide the user with less information about the true slope
- level 越小(例如 90%)
- 95% 通常是一个比较好的平衡点
- 通过执行 [bootstrap resampling](https://en.wikipedia.org/wiki/Bootstrapping_(statistics)) 来生成这些 confidence intervals。
- bootstrap samples 的次数是可配置的,默认是 100,000
- 可选地,也可以报告吞吐(每秒的字节数或者元素数)
### Change
```sh
change: [-38.292% -37.342% -36.524%] (p = 0.00 < 0.05)
Performance has improved.
```
- benchmark 运行时,会在 `target/criterion` 中保存统计信息。
- 后续的执行会加载之前的数据,跟当前的 sample 进行比较。
- 第二行是一句总结,性能是提升了还是倒退了。
- noise_threshold 内会忽略,默认是 `+-2%`
### Detecting Outliers
```sh
Found 8 outliers among 100 measurements (8.00%)
4 (4.00%) high mild
4 (4.00%) high severe
```
- 检测异常高或低的样本,将其报告会异常值
- 如果出现大量异常值表明基准结果存在噪音,应以适当的怀疑态度看待
- 异常值较多可能的原因:
- 机器负载变化
- 线程/进程调度
- 目标函数本身耗时不规律
- 措施:
- 增加压测时间来减少异常值的影响
- 增加 warmup 时间
### Additional Statistics
```sh
slope [2.5094 ms 2.5553 ms] R^2 [0.8660614 0.8640630]
mean [2.5142 ms 2.5557 ms] std. dev. [62.868 us 149.50 us]
median [2.5023 ms 2.5262 ms] med. abs. dev. [40.034 us 73.259 us]
```
- 使用线性回归来计算每次迭代的时间。
- 第一行显示了线性回归的斜率的置信区间,R^2区域表示该置信区间下界和上界的拟合优度值。
- R^2 的值很低,可能表明基准测试在每次迭代中执行的工作量不同。
- 第二行显示了每次迭代时间的平均值和标准偏差的置信区间。
- 如果 std. dev. 比上面的 time values 大,说明有噪音。
- 第三行类似第二行,不一样的地方在于它使用了中位数和 [median absolute deviation](https://en.wikipedia.org/wiki/Median_absolute_deviation).
## 2.3. Command-Line Options
- 执行`cargo bench -- -h` 列出所有选项。
- 过滤 benchmarks:`cargo bench -- <filter>`
- `<filter>` 是一个正则表达式来匹配 benchmark ID。
- 如 `cargo bench -- fib_\d+` 可以匹配 fib_20
- 打印更多详细输出: `cargo bench -- --verbose`
- 禁用 colored output:`cargo bench -- --color never`
- 禁用 plot generation: `cargo bench -- --noplot`
- 运行指定时长,且不保存、分析、绘制结果,使用 `cargo bench -- --profile-time <num_seconds>`
- 用途:profiling
- 保存 baseline:`cargo bench -- --save-baseline <name>`
- 与已有的 baseline 比较:`cargo bench -- --baseline <name>`
- 只验证是否成功,不关注性能:`cargo test --benches`
- 指定默认的 plotting backend:`cargo bench -- --plotting-backend gnuplot`
- 更改输出格式:`cargo bench -- --output-format <name>`,支持的格式
- `crierion`:原生格式
- `bencher`:类似 bencher crate 或者 libtest
### Baselines
- 默认跟上次运行的进行比较。
- 支持自定义 baselines:
- `--save-baseline <name>` 与指定的 baseline 比较,然后覆盖它
- `--baseline <name>` 只与指定的 baseline 比较,不覆盖
- `--load-baseline <name>` 加载指定的 baseline 作为 new data set
```sh
git checkout master
cargo bench -- --save-baseline master
git checkout feature
cargo bench -- --save-baseline feature
git checkout optimizations
# Some optimization work here
# Measure again
cargo bench
# Now compare against the stored baselines without overwriting it or re-running the measurements
cargo bench -- --load-baseline new --baseline master
cargo bench -- --load-baseline new --baseline feature
```
## 2.4. HTML Report
- 生成目录:`target/criterion/report/index.html`
- 默认使用 gnuplot 绘图(如果不存在使用 [plotters](https://github.com/38/plotters) crate)
## 2.5. Plots & Graphs
### File Structure
- plots 和 saved data 存储在 `target/criterion/$BENCHMARK_NAME/` 下面。
```
$BENCHMARK_NAME/
├── base/
│ ├── raw.csv
│ ├── estimates.json
│ ├── sample.json
│ └── tukey.json
├── change/
│ └── estimates.json
├── new/
│ ├── raw.csv
│ ├── estimates.json
│ ├── sample.json
│ └── tukey.json
└── report/
├── both/
│ ├── pdf.svg
│ ├── regression.svg
│ └── iteration_times.svg
├── change/
│ ├── mean.svg
│ ├── median.svg
│ └── t-test.svg
├── index.html
├── MAD.svg
├── mean.svg
├── median.svg
├── pdf.svg
├── pdf_small.svg
├── regression.svg (optional)
├── regression_small.svg (optional)
├── iteration_times.svg (optional)
├── iteration_times_small.svg (optional)
├── relative_pdf_small.svg
├── relative_regression_small.svg (optional)
├── relative_iteration_times_small.svg (optional)
├── SD.svg
└── slope.svg
```
- `new`文件夹包含了最近一次的统计信息
- `base`文件夹是名为 `base` 的 baseline 的最近一次运行结果
- 图表位于 `report` 文件夹内,只保存最后一次运行的数据
- `report/both`:一张图上显示两次运行
- `report/change`:两次运行的差异
## 2.6. Benchmarking With Inputs
- 支持使用一个或者多个不同的输入值来运行压测,来研究不同输入下的性能表现。
### One Input
```rust
#![allow(unused)]
fn main() {
use criterion::BenchmarkId;
use criterion::Criterion;
use criterion::{criterion_group, criterion_main};
fn do_something(size: usize) {
// Do something with the size
}
fn from_elem(c: &mut Criterion) {
let size: usize = 1024;
c.bench_with_input(BenchmarkId::new("input_example", size), &size, |b, &s| {
b.iter(|| do_something(s));
});
}
criterion_group!(benches, from_elem);
criterion_main!(benches);
}
```
- 自动地通过 `black_box` 来传入 input,不需要手动调用。
### A Range Of Values
- 可以使用 BenchmarkGroup 在一系列输入上比较函数的性能。
```rust
#![allow(unused)]
fn main() {
use std::iter;
use criterion::BenchmarkId;
use criterion::Criterion;
use criterion::Throughput;
fn from_elem(c: &mut Criterion) {
static KB: usize = 1024;
let mut group = c.benchmark_group("from_elem");
for size in [KB, 2 * KB, 4 * KB, 8 * KB, 16 * KB].iter() {
group.throughput(Throughput::Bytes(*size as u64));
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
b.iter(|| iter::repeat(0u8).take(size).collect::<Vec<_>>());
});
}
group.finish();
}
criterion_group!(benches, from_elem);
criterion_main!(benches);
}
```
- `throughput` 函数:表示每次迭代操作 `size` bytes
## 2.7. Advanced Configuration
### Configuring Sample Count & Other Statistical Settings
- 允许用户调整某些统计参数。
- 使用 `BenchmarkGroup`结构来设置
```rust
#![allow(unused)]
fn main() {
use criterion::*;
fn my_function() {
...
}
fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("sample-size-example");
// Configure Criterion.rs to detect smaller differences and increase sample size to improve
// precision and counteract the resulting noise.
group.significance_level(0.1).sample_size(500);
group.bench_function("my-function", |b| b.iter(|| my_function());
group.finish();
}
criterion_group!(benches, bench);
criterion_main!(benches);
}
```
- 使用 `criterion_group`宏的完整形式来设置
```rust
criterion_group!{
name = benches;
// This can be any expression that returns a `Criterion` object.
config = Criterion::default().significance_level(0.1).sample_size(500);
targets = bench
}
```
### Throughput Measurements
### Sampling Mode
## 2.8. Comparing Functions
- 可以自动地比较一个函数的多个实现,进行对比。
## 2.9. CSV Output
- `raw.csv`文件内,可以结合 [lolbench](https://github.com/anp/lolbench) 使用跟踪历史性能
## 2.10. Known Limitations
- 需要提供自己的 `main` 函数(使用 `criterion_main` 宏),导致一些限制
- 不能包含 `src` 目录下的 benchmarks
- 不能 benchmark 非 pub 函数
- 不能 benchmark binary crates 中的函数(binary crate 不能被其他 crate 依赖)
- 无法对不提供 rlib 的 crate 中的函数进行基准测试。
- `black_box`函数不如官方版本的可靠,可能会导致 dead-code-elimination
- Nightly rust 可以使用 `real_blackbox` feature
```toml
criterion = { version = '...', features=['real_blackbox'] }
```
## 2.11. Bencher Compatibility Layer
- 提供了一个小 crate `criterion_bencher_compat` 可以兼容 `bencher`
## 2.12. Timing Loops
- [`Bencher`](https://bheisler.github.io/criterion.rs/criterion/struct.Bencher.html) 结构体提供了许多函数来实现不同的 timling loops,用于测试一个函数的性能。
### iter
- 在一个 tight loop 中执行 benchmark N 次,然后记录整个循环的耗时。
- 轻量级:只在循环前后记录时间
- 问题:
- 如果某个 value 实现了 Drop,那么 drop 函数的时间也会被统计在内。
- 不支持 per-iteration setup,例如排序函数需要先准备无序数据,但同时不想准备工作详细测量
### iter_with_large_drop
- 解决 drop 问题。
- 先将 benchmark 的结果收集到一个 `Vec`,测量完成后再释放
- 问题:内存占用
### iter_batched/iter_batched_ref
- 需要两个 closures
- 第一个用于生成 setup data
- 第二个是 benchmark 的目标函数
- 生成一批 inputs,然后测量这一批 inputs 的执行时间。
- 同时也类似 `iter_with_large_drop` 收集结果到 `Vec` 中,避免 drop 问题。
- 使用于 input 数据是动态的,如果输入是固定的,可以直接使用 `iter`
- 接收一个参数用于控制 batch 的大小。
## 2.13. Custom Measurements
## 2.14. Profiling
### Note on running benchmark executables directly
- 使用 `--bench`参数直接运行
### --profile-time
- 执行指定时长,但进行分析、保存结果,跳过 criterion.rs 自身的代码逻辑
### Implementing In-Process Profiling Hooks
```rust
#![allow(unused)]
fn main() {
pub trait Profiler {
fn start_profiling(&mut self, benchmark_id: &str, benchmark_dir: &Path);
fn stop_profiling(&mut self, benchmark_id: &str, benchmark_dir: &Path);
}
}
```
- `--profile-time` 模式下会自动调用
## 2.15. Custom Test Framework
- 需要 nightly compiler
- 使用 `#[criterion]` 宏
## 2.16. Benchmarking async functions
- 需要提供一个 async runtime
- 异步函数会有额外开销,小函数推荐同步方式
--------------
# 3. cargo-criterion
- cargo-criterion 是一个实验性的 Cargo 扩展,可以替代 cargo bench。
----------
# FAQ
## Unrecognized Options
- 只运行 criterion benchmark:`cargo bench --bench my_benchmark -- --verbose`
- 或者为 lib / app crate 禁用 benchmarks,例如:
```
[lib]
bench = false
```
## When Should I Use criterion::black_box
- `black_box`是一个用于阻塞某些编译器优化的函数。
- 例如经常使用 constant parameters 来压测函数,rustc 可能会进行优化(将函数调用替换为一个常量)
-----------