微基准测试:如何测量多线程CPU时间及计算吞吐量?
多线程队列基准测试:解决波动误报与CPU时间测量方案
一、先缓解基准测试的波动误报
既然波动源于线程调度或线程创建开销,先做两个简单优化:
- 预热线程池:在基准测试的
setup阶段提前初始化生产者/消费者线程,复用线程而非每次测试重建,消除线程创建的耗时波动 - 增加样本量:通过Criterion的
sample_size(100)或measurement_time(Duration::from_secs(10))配置,提升测试次数,平滑单次调度的随机波动
二、基于CPU时间的自定义测量实现
要使用CLOCK_PROCESS_CPUTIME_ID统计总CPU时间,结合Criterion实现自定义测量器:
1. 添加依赖
在Cargo.toml中补充:
[dependencies] criterion = "0.5" libc = "0.2"
2. 实现CPU时间测量器
替换Criterion默认的挂钟时间测量:
use criterion::measurement::{Measurement, ValueFormatter}; use libc::{clock_gettime, timespec, CLOCK_PROCESS_CPUTIME_ID}; use std::time::Duration; #[derive(Clone, Default)] struct ProcessCpuTime; impl Measurement for ProcessCpuTime { type Intermediate = timespec; type Value = Duration; fn start(&self) -> Self::Intermediate { let mut ts = timespec { tv_sec: 0, tv_nsec: 0 }; unsafe { clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &mut ts) }; ts } fn end(&self, start: Self::Intermediate) -> Self::Value { let mut end_ts = timespec { tv_sec: 0, tv_nsec: 0 }; unsafe { clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &mut end_ts) }; let sec_diff = (end_ts.tv_sec - start.tv_sec) as u64; let nsec_diff = (end_ts.tv_nsec - start.tv_nsec) as u64; Duration::from_secs(sec_diff) + Duration::from_nanos(nsec_diff) } fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value { *v1 + *v2 } fn zero(&self) -> Self::Value { Duration::ZERO } fn to_f64(&self, value: &Self::Value) -> f64 { value.as_secs_f64() } fn formatter(&self) -> &dyn ValueFormatter { &criterion::measurement::ValueFormatterCpuTime } }
3. 在基准测试中使用自定义测量器
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; fn custom_queue_bench(c: &mut Criterion<ProcessCpuTime>) { let mut group = c.benchmark_group("multi_threaded_queue"); // 替换为你的生产者/消费者数量 let (producers, consumers) = (Y, X); group.bench_with_input( BenchmarkId::new("prod_cons", format!("{}p_{}c", producers, consumers)), &(producers, consumers), |b, &(p_count, c_count)| { b.iter(|| { // 这里放入你的队列测试逻辑:初始化队列、启动生产者/消费者线程、等待任务完成 let queue = CustomQueue::new(); // ... 生产消费逻辑 ... }); }, ); group.finish(); } criterion_group!(benches, custom_queue_bench); criterion_main!(benches);
三、基于CPU时间计算吞吐量
不需要依赖挂钟时间,直接用总处理项数 / 总CPU时间计算items/s:
方法1:手动统计计算
在测试逻辑中固定或统计每次迭代处理的item总数,测试结束后计算:
group.bench_with_input(..., |b, &(p, c)| { const TOTAL_ITEMS: usize = 100_000; // 每次迭代处理的item数 b.iter(|| { run_queue_test(p, c, TOTAL_ITEMS); // 执行生产消费,处理指定数量的item }); // 获取平均CPU时间并计算吞吐量 if let Some(avg) = group.average() { let avg_cpu_secs = self.measurement().to_f64(&avg.value); let throughput = TOTAL_ITEMS as f64 / avg_cpu_secs; println!("CPU-based throughput: {:.2} items/s", throughput); } });
方法2:扩展测量器记录item数
如果需要更精准的统计,可以修改测量器的Value为包含CPU时间和item数的结构体,在迭代结束时更新统计,最终直接计算吞吐量。
注意事项
CLOCK_PROCESS_CPUTIME_ID统计的是进程所有线程的CPU时间总和,适合对比不同实现的CPU利用率,可读性差的问题可以通过格式化输出(比如转成秒+毫秒)改善- 若需要单线程CPU时间,可改用
CLOCK_THREAD_CPUTIME_ID,但多线程场景下总CPU时间更能反映整体性能开销 - Fedora x86-64环境下
clock_gettime是稳定调用,无需额外权限
内容的提问来源于stack exchange,提问作者nee
相关产品推荐
相关产品推荐

