Rust性能基准测试库criterion-cycles-per-byte的使用:精确测量每字节CPU周期消耗的基准测试工具
Rust性能基准测试库criterion-cycles-per-byte的使用:精确测量每字节CPU周期消耗的基准测试工具
架构支持
CyclesPerByte
使用CPU读取时间戳计数器指令来测量时钟周期。
架构 | 指令 |
---|---|
x86 | rdtsc / rdpru |
x86_64 | rdtsc / rdpru |
aarch64 (运行GNU/Linux内核) | pmccntr |
loongarch64 | rdtime.d |
RDPRU指令仅在Zen 2及更高版本的AMD CPU上可用,默认不启用。要启用它,请使用rdpru
配置标志,例如使用RUSTFLAGS="--cfg rdpru"
。
x86警告
除非启用rdpru
,否则此crate测量的是时钟ticks而非cycles。在现代机器上,除非您计算ticks与cycles的比率并采取措施确保该比率保持一致,否则不会提供准确的结果。
aarch64警告
如果您计划在aarch64
目标上使用此库(运行GNU/Linux内核),建议阅读相关源码注释。
示例代码
以下是使用criterion-cycles-per-byte进行基准测试的完整示例:
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use criterion_cycles_per_byte::CyclesPerByte;
// 慢速斐波那契实现
fn fibonacci_slow(n: u64) -> u64 {
match n {
0 => 0,
1 => 1,
_ => fibonacci_slow(n - 1) + fibonacci_slow(n - 2),
}
}
// 快速斐波那契实现
fn fibonacci_fast(n: u64) -> u64 {
let mut a = 0;
let mut b = 1;
for _ in 0..n {
let c = a + b;
a = b;
b = c;
}
a
}
fn bench(c: &mut Criterion<CyclesPerByte>) {
let mut group = c.benchmark_group("fibonacci");
// 测试从0到19的斐波那契数列
for i in 0..20 {
group.bench_function(BenchmarkId::new("slow", i), |b| b.iter(|| fibonacci_slow(i)));
group.bench_function(BenchmarkId::new("fast", i), |b| b.iter(|| fibonacci_fast(i)));
}
group.finish()
}
// 配置使用CyclesPerByte作为测量方式
criterion_group!(
name = my_bench;
config = Criterion::default().with_measurement(CyclesPerByte);
targets = bench
);
criterion_main!(my_bench);
维护状态
当前维护者表示会进行版本更新和错误修复,但不会添加新功能或尝试解决这种测量方法可能存在的固有问题。
兼容性
Criterion版本 | Cycles Per Byte版本 |
---|---|
0.5 | 0.6 |
0.4 | 0.4 |
完整示例代码扩展
以下是一个更完整的示例,展示了如何使用criterion-cycles-per-byte来测试字符串处理函数的性能:
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use criterion_cycles_per_byte::CyclesPerByte;
// 简单字符串反转函数
fn reverse_string(s: &str) -> String {
s.chars().rev().collect()
}
// 优化版字符串反转函数
fn reverse_string_optimized(s: &str) -> String {
let mut bytes = s.as_bytes().to_vec();
let len = bytes.len();
for i in 0..len/2 {
bytes.swap(i, len - 1 - i);
}
String::from_utf8(bytes).unwrap()
}
fn string_benchmark(c: &mut Criterion<CyclesPerByte>) {
let test_strings = vec![
"hello",
"medium length string",
"very long string with many characters to test performance"
];
let mut group = c.benchmark_group("string_reverse");
for s in test_strings {
group.bench_with_input(
BenchmarkId::new("basic", s),
s,
|b, input| b.iter(|| reverse_string(input))
);
group.bench_with_input(
BenchmarkId::new("optimized", s),
s,
|b, input| b.iter(|| reverse_string_optimized(input))
);
}
group.finish();
}
criterion_group!(
name = benches;
config = Criterion::default().with_measurement(CyclesPerByte);
targets = string_benchmark
);
criterion_main!(benches);
这个扩展示例展示了如何:
- 测试不同长度的输入字符串
- 比较基础实现和优化实现的性能
- 使用
bench_with_input
方法来处理不同输入 - 测量每个实现的每字节CPU周期消耗
Rust性能基准测试库criterion-cycles-per-byte使用指南
criterion-cycles-per-byte
是一个基于criterion的扩展库,专门用于精确测量每字节CPU周期消耗的基准测试工具。它对于需要精细分析算法或数据结构性能的场景特别有用。
安装方法
首先在Cargo.toml中添加依赖:
[dependencies]
criterion = "0.4"
[dev-dependencies]
criterion-cycles-per-byte = "0.3"
基本使用方法
1. 基本基准测试
use criterion::{criterion_group, criterion_main, Criterion};
use criterion_cycles_per_byte::CyclesPerByte;
fn bench_example(c: &mut Criterion<CyclesPerByte>) {
c.bench_function("sum 100", |b| {
b.iter(|| (0..100).sum::<u32>());
});
}
criterion_group!(
name = benches;
config = Criterion::default().with_measurement(CyclesPerByte);
targets = bench_example
);
criterion_main!(benches);
2. 测量每字节周期消耗
use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId};
use criterion_cycles_per_byte::CyclesPerByte;
fn bench_vec_push(c: &mut Criterion<CyclesPerByte>) {
let mut group = c.benchmark_group("Vec::push");
for size in [10, 100, 1000, 10000].iter() {
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
b.iter(|| {
let mut v = Vec::with_capacity(size);
for i in 0..size {
v.push(i);
}
v
});
});
}
group.finish();
}
criterion_group!(
name = benches;
config = Criterion::default().with_measurement(CyclesPerByte);
targets = bench_vec_push
);
criterion_main!(benches);
高级功能
1. 自定义输入大小
use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId};
use criterion_cycles_per_byte::CyclesPerByte;
fn process_data(input: &[u8]) -> usize {
// 模拟数据处理
input.iter().filter(|&&x| x > 128).count()
}
fn bench_data_processing(c: &mut Criterion<CyclesPerByte>) {
let input = vec![0u8; 1024]; // 1KB输入
c.bench_function("process 1KB", |b| {
b.iter(|| process_data(&input));
b.bytes = input.len() as u64; // 设置处理的字节数
});
}
criterion_group!(benches, bench_data_processing);
criterion_main!(benches);
2. 比较不同实现
use criterion::{criterion_group, criterion_main, Criterion};
use criterion_cycles_per_byte::CyclesPerByte;
fn naive_sum(slice: &[u32]) -> u32 {
slice.iter().sum()
}
fn optimized_sum(slice: &[u32]) -> u32 {
slice.iter().fold(0, |acc, &x| acc + x)
}
fn bench_compare(c: &mut Criterion<CyclesPerByte>) {
let data = vec![1u32; 1000];
c.bench_function("naive sum", |b| {
b.iter(|| naive_sum(&data));
b.bytes = (data.len() * std mem::size_of::<u32>()) as u64;
});
c.bench_function("optimized sum", |b| {
b.iter(|| optimized_sum(&data));
b.bytes = (data.len() * std::mem::size_of::<u32>()) as u64;
});
}
criterion_group!(benches, bench_compare);
criterion_main!(benches);
结果解读
运行基准测试后,你会看到类似这样的输出:
Vec::push/10 time: [10.234 cycles/byte 10.567 cycles/byte 10.912 cycles/byte]
Vec::push/100 time: [8.1234 cycles/byte 8.4567 cycles/byte 8.7890 cycles/byte]
Vec::push/1000 time: [7.1234 cycles/byte 7.4567 cycles/byte 7.7890 cycles/byte]
Vec::push/10000 time: [6.1234 cycles/byte 6.4567 cycles/byte 6.7890 cycles/byte]
关键点:
- 结果以每字节周期数(cycles/byte)显示
- 数值越低表示性能越好
- 可以看到不同输入规模下的性能变化
注意事项
- 确保在release模式下运行基准测试:
cargo bench --release
- 关闭CPU频率缩放以获得稳定结果
- 避免在运行基准测试时运行其他CPU密集型任务
- 对于短时间运行的基准测试,可能需要增加样本数量
这个工具特别适合需要精确测量算法或数据结构性能的场景,尤其是当性能与输入大小相关时。
完整示例demo
下面是一个完整的基准测试示例,展示了如何使用criterion-cycles-per-byte测量字符串处理函数的性能:
// 在Cargo.toml中添加依赖:
// [dev-dependencies]
// criterion = "0.4"
// criterion-cycles-per-byte = "0.3"
use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId};
use criterion_cycles_per_byte::CyclesPerByte;
// 需要测试的字符串处理函数
fn count_uppercase(s: &str) -> usize {
s.chars().filter(|c| c.is_uppercase()).count()
}
// 基准测试函数
fn bench_string_processing(c: &mut Criterion<CyclesPerByte>) {
// 准备不同大小的测试数据
let test_strings = [
("small", "Hello World".repeat(10)), // 约110字节
("medium", "Rust Programming".repeat(50)), // 约800字节
("large", "Benchmark Test".repeat(200)), // 约2800字节
];
let mut group = c.benchmark_group("String Processing");
for (name, input) in test_strings.iter() {
group.bench_with_input(
BenchmarkId::new("count_uppercase", name),
input,
|b, s| {
b.iter(|| count_uppercase(s));
b.bytes = s.len() as u64; // 设置处理的字节数
}
);
}
group.finish();
}
// 配置并运行基准测试
criterion_group!(
name = benches;
config = Criterion::default()
.with_measurement(CyclesPerByte)
.sample_size(20); // 增加样本数量提高准确性
targets = bench_string_processing
);
criterion_main!(benches);
这个示例展示了:
- 如何设置不同大小的输入数据
- 如何为每个测试用例设置处理的字节数
- 如何增加样本数量提高测试准确性
- 如何组织多个相关基准测试为组
运行后会输出类似以下格式的结果,显示每个测试用例的每字节周期消耗:
String Processing/count_uppercase/small
time: [15.234 cycles/byte 15.567 cycles/byte 15.912 cycles/byte]
String Processing/count_uppercase/medium
time: [12.123 cycles/byte 12.456 cycles/byte 12.789 cycles/byte]
String Processing/count_uppercase/large
time: [10.123 cycles/byte 10.456 cycles/byte 10.789 cycles/byte]