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);

这个扩展示例展示了如何:

  1. 测试不同长度的输入字符串
  2. 比较基础实现和优化实现的性能
  3. 使用bench_with_input方法来处理不同输入
  4. 测量每个实现的每字节CPU周期消耗

1 回复

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)显示
  • 数值越低表示性能越好
  • 可以看到不同输入规模下的性能变化

注意事项

  1. 确保在release模式下运行基准测试:cargo bench --release
  2. 关闭CPU频率缩放以获得稳定结果
  3. 避免在运行基准测试时运行其他CPU密集型任务
  4. 对于短时间运行的基准测试,可能需要增加样本数量

这个工具特别适合需要精确测量算法或数据结构性能的场景,尤其是当性能与输入大小相关时。

完整示例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);

这个示例展示了:

  1. 如何设置不同大小的输入数据
  2. 如何为每个测试用例设置处理的字节数
  3. 如何增加样本数量提高测试准确性
  4. 如何组织多个相关基准测试为组

运行后会输出类似以下格式的结果,显示每个测试用例的每字节周期消耗:

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]
回到顶部