You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

微基准测试:如何测量多线程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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 10:05:09