news 2026/10/12 4:17:33

criterion.rs 的 bencher 兼容层(criterion_bencher_compat):把 bencher 基准测试平滑迁移到 Criterion.rs

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
criterion.rs 的 bencher 兼容层(criterion_bencher_compat):把 bencher 基准测试平滑迁移到 Criterion.rs
  • 开发工具
  • 性能测试

【免费下载链接】criterion.rs

Statistics-driven benchmarking library for Rust

项目地址:https://gitcode.com/gh_mirrors/cr/criterion.rs
点击查看免费下载

导读

本文聚焦 Criterion.rs 仓库中提供的criterion_bencher_compat(即bencher_compat子 crate)——一个专为旧bencher基准测试打造的“兼容垫片(shim)”。它让你在几乎不改动基准测试代码的前提下,把基于bencher的基准测试交给 Criterion.rs 驱动,从而立即获得统计学显著性检验、异常值检测、回归分析、HTML 报告与图表演示等能力。读完本文,你将掌握完整的迁移步骤(依赖替换、导入改写、cargo bench运行与输出解读),并通过源码剖析理解Bencher、benchmark_group!、benchmark_main!三个核心 API 在底层是如何桥接到 Criterion.rs 的,以及它的能力边界与后续进一步迁移到原生 Criterion.rs 接口的路径。


一、为什么需要一个 bencher 兼容层

bencher是 Rust 生态中一个轻量级的微基准测试库,许多早期项目用它编写形如fn bench_x(b: &mut Bencher)的基准函数,并通过benchmark_group!与benchmark_main!两个宏组织基准集合。而 Criterion.rs 是一个统计驱动的基准测试库,能够执行置信区间估计、异常值检测、基线(baseline)比较与图形化报告,功能远超bencher。

但把既有bencher基准全部重写为 Criterion.rs 接口,需要投入不少改造时间,也让想“先试用一下 Criterion.rs”的用户望而却步。为此,Criterion.rs 仓库专门维护了一个垫片 crate。正如 bencher_compat/README.md 所述:

This crate is a shim that can be used to easily convert mostbencherbenchmarks to Criterion.rs benchmarks.

即:这是一个“垫片”,用于把大多数bencher基准测试轻松转换为 Criterion.rs 基准测试。它的包描述(见 bencher_compat/Cargo.toml)也写得很直白:Drop-in replacement for commonly-used parts of Bencher——对bencher常用 API 的直接替换品。

兼容层刻意只覆盖bencher最常用的 API 子集,让你用最小的代码改动先跑起来;如果 Criterion.rs 用起来顺手,官方推荐再进一步迁移到原生接口(详见后文“进一步迁移”一节)。

二、兼容层提供了哪些 API

criterion_bencher_compat对外暴露的接口非常精简,完整实现见 bencher_compat/src/lib.rs,共四个部分:

API形态作用
Bencher<'a, 'b>结构体替代bencher::Bencher,作为基准回调的入参类型
benchmark_group!宏替代bencher::benchmark_group!,声明一组基准函数
benchmark_main!宏替代bencher::benchmark_main!,生成程序入口
black_box/Criterion重导出pub use std::hint::black_box与pub use criterion::Criterion,免去额外导入

其中Bencher的声明为:

/// Stand-in for `bencher::Bencher` which uses Criterion.rs to perform the benchmark instead. pub struct Bencher<'a, 'b> { pub bytes: u64, pub bencher: &'a mut ::criterion::Bencher<'b, WallTime>, }

它内部持有一个&mut criterion::Bencher(默认测量类型为WallTime,即墙上时钟),并保留了bencher风格的bytes字段用于维持 API 兼容;其核心方法iter只是把计时循环直接转发给底层 Criterion.rs 的Bencher:

pub fn iter<T, F>(&mut self, inner: F) where F: FnMut() -> T { self.bencher.iter(inner); }

也就是说,你在基准函数里写的bench.iter(|| ...)在编译期被原样委托给了 Criterion.rs 的计时引擎,统计与报告全部由 Criterion.rs 完成。

三、实战迁移:三步把 bencher 基准换成 Criterion.rs

book/src/user_guide/bencher_compatibility.md 给出了完整的迁移示范。以下逐步复现。

3.1 原始 bencher 基准

假设你手头有一个典型的bencher基准文件,例如来自bencher官方示例:

use bencher::{benchmark_group, benchmark_main, Bencher}; fn a(bench: &mut Bencher) { bench.iter(|| { (0..1000).fold(0, |x, y| x + y) }) } fn b(bench: &mut Bencher) { const N: usize = 1024; bench.iter(|| { vec![0u8; N] }); bench.bytes = N as u64; } benchmark_group!(benches, a, b); benchmark_main!(benches);

注意基准函数a、b都接受&mut Bencher,且b通过bench.bytes记录每次迭代处理的字节数(用于吞吐量展示)。整个迁移过程只需要改两处。

3.2 第一步:替换 Cargo.toml 中的依赖

把:

[dev-dependencies] bencher = "0.1"

改为:

[dev-dependencies] criterion_bencher_compat = "0.4"

当前仓库内该 crate 的版本正是0.4.0(见 bencher_compat/Cargo.toml),与指南中的版本号一致。它作为dev-dependencies引入即可,基准测试代码通过它间接获得 Criterion.rs 的完整能力。

3.3 第二步:改写基准文件的导入

把文件顶部的:

use bencher::{benchmark_group, benchmark_main, Bencher};

改为:

use criterion_bencher_compat as bencher; use bencher::{benchmark_group, benchmark_main, Bencher};

这里有一个巧妙的细节:用as bencher给兼容 crate 起别名,于是基准文件里其余的代码(函数签名、bench.iter、bench.bytes、两个宏的调用)可以一行都不动。这正是“drop-in replacement”的体现。

第三步就是把基准文件的[[bench]]段配置好harness = false(如果还没配置的话),然后运行:

cargo bench

3.4 第三步:运行与输出解读

指南给出了一个真实的运行输出示例:

Running target/release/deps/bencher_example-d865087781455bd5 a time: [234.58 ps 237.68 ps 241.94 ps] Found 9 outliers among 100 measurements (9.00%) 4 (4.00%) high mild 5 (5.00%) high severe b time: [23.972 ns 24.218 ns 24.474 ns] Found 4 outliers among 100 measurements (4.00%) 4 (4.00%) high mild

这段输出是典型的 Criterion.rs 统计风格,和bencher的简单输出有本质区别:

  • time: [下界 点估计 上界]:Criterion.rs 以置信区间形式报告耗时(默认为 95% 置信区间),而不是单一数字;
  • Found N outliers among 100 measurements:自动执行异常值检测,并标注high mild(高轻度)与high severe(高严重)等级;
  • 测量次数:Criterion.rs 默认执行多次采样(示例中为 100 次测量),通过不断调整迭代次数保证统计精度。

仓库中的基准示例 bencher_compat/benches/bencher_example.rs 就是按上述模式编写的(harness = false已在 bencher_compat/Cargo.toml 的[[bench]]段声明),可以直接作为你的起点模板。

四、源码级剖析:两个宏如何桥接到 Criterion.rs

兼容层的魔法集中在两个宏的展开逻辑里。理解它们,你就理解了整个 shim 的运作机制。

4.1benchmark_group!:为每个函数生成一个 Criterion 基准

宏定义见 bencher_compat/src/lib.rs:

#[macro_export] macro_rules! benchmark_group { ($group_name:ident, $($function:path),+) => { pub fn $group_name() { use $crate::Criterion; let mut criterion: Criterion = Criterion::default().configure_from_args(); $( criterion.bench_function(stringify!($function), |b| { let mut wrapped = $crate::Bencher { bytes: 0, bencher: b, }; $function(&mut wrapped); }); )+ } }; ($group_name:ident, $($function:path,)+) => { benchmark_group!($group_name, $($function),+); }; }

展开后它做三件事:

  1. 创建Criterion实例并解析命令行参数:Criterion::default().configure_from_args()。configure_from_args是 Criterion.rs 的 CLI 解析入口(见 src/lib.rs),支持过滤器位置参数、--color、--verbose、--quiet、--noplot、--save-baseline、--baseline、--list、--format等开关;
  2. 为列表中的每个函数调用criterion.bench_function:bench_function(见 src/lib.rs)会为该函数创建独立的基准组并注册计时回调;
  3. 包装Bencher:Criterion.rs 的回调参数b: &mut criterion::Bencher被包装进兼容层的Bencher { bytes: 0, bencher: b },再传给你的基准函数$function(&mut wrapped)。

两个模式分支(带不带尾部逗号)是为了兼容bencher宏对尾逗号的不同写法,实质是同一个展开。

4.2benchmark_main!:生成统一的入口函数

#[macro_export] macro_rules! benchmark_main { ($($group_name:path),+) => { fn main() { $( $group_name(); )+ $crate::Criterion::default() .configure_from_args() .final_summary(); } }; ($($group_name:path,)+) => { benchmark_main!($($group_name),+); }; }

它生成main:先依次调用每个benchmark_group!生成的pub fn $group_name(),最后再构建一个Criterion实例调用final_summary()。final_summary(见 src/lib.rs)的作用是在整轮基准跑完后输出跨基准的总结报告(如相对速度对比),并且只有在当前模式为基准模式(mode.is_benchmark())时才执行,因此不会干扰--list、--profile-time等特殊模式。

4.3 底层计时模型:Bencher::iter做了什么

兼容层的iter最终调用的是 Criterion.rs 的criterion::Bencher::iter(见 src/bencher.rs)。其时序模型在源码注释中有明确说明:

elapsed = Instant::now + iters * (routine + mem::drop(O) + Range::next)

即:iter会反复执行你的闭包iters次,累计总耗时,并且把闭包返回值的drop开销也计入计时。因此:

  • 如果闭包返回值带有昂贵析构(如大Vec),计时会包含释放开销——这是刻意设计的计时模型,适合返回值析构开销可忽略的场景;
  • 需要精细控制计时边界时,Criterion.rs 还提供了iter_custom、iter_batched、iter_batched_ref、iter_with_large_drop等更多计时循环(同一文件 src/bencher.rs 的文档注释有完整说明),但兼容层出于“只覆盖常用子集”的定位只转发iter。

此外,Criterion.rs 的iter内部对每次routine()的返回值应用了black_box(src/bencher.rs),防止编译器把基准代码当作死代码消除,这也是兼容层重导出black_box的原因。

五、bytes字段与吞吐量的说明

在原始bencher中,bench.bytes = N用于让bencher报告吞吐量(bytes/s)。兼容层的Bencher保留了pub bytes: u64字段以维持源码兼容——示例基准b中bench.bytes = N as u64;依然能编译通过。

不过需要说明:在 Criterion.rs 中,吞吐量并不是通过Bencher::bytes驱动,而是通过BenchmarkGroup::throughput(Throughput::Bytes(...))等方式显式配置(见 src/benchmark_group.rs 与 src/lib.rs 中Throughput枚举的Bytes/Elements/Bits变体)。也就是说,兼容层中的bytes字段只起到 API 占位的作用,不会自动参与 Criterion.rs 的吞吐量统计;如果你需要吞吐量报告,应在迁移到原生 Criterion.rs 接口后改用throughput配置。

六、特性开关:real_blackbox

bencher_compat/Cargo.toml 声明了一个特性:

[features] real_blackbox = ["criterion/real_blackbox"] default = []

该特性会转发启用 Criterion.rs 的real_blackbox特性。Criterion.rs 默认(尤其在非 nightly 工具链上)使用较廉价的black_box近似实现;启用real_blackbox后使用真实的std::hint::black_box,以更强的防优化保证换取微小的调用开销。如果你对基准结果中的优化消除风险比较敏感,可以在Cargo.toml中这样启用:

[dev-dependencies] criterion_bencher_compat = { version = "0.4", features = ["real_blackbox"] }

七、能力边界与后续迁移路径

兼容层是有意“小而精”的。指南 book/src/user_guide/bencher_compatibility.md 明确列出了两条限制:

  1. 不实现bencher的完整 API,只覆盖最常用的子集。如果基准用到了不支持的 API(例如自定义测量、批量迭代等),在试用期间可能需要临时禁用相关代码;
  2. 不暴露 Criterion.rs 的大多数高级特性——基线比较、参数化基准(bench_with_input、BenchmarkGroup)、自定义测量类型(Measurementtrait)、CSV 导出、HTML 报告定制等都无法通过 shim 触达。

因此官方建议:如果 Criterion.rs 用起来符合预期,就把基准最终迁移到 Criterion.rs 原生接口。完整的迁移示范见 book/src/user_guide/migrating_from_libtest.md,其要点是:

  • 在Cargo.toml中声明[[bench]]段的harness = false;
  • 将bencher依赖替换为criterion;
  • 把fn bench_x(b: &mut Bencher)改写为fn bench_x(c: &mut Criterion),并用c.bench_function("名字", |b| b.iter(|| ...))包裹计时闭包;
  • 用criterion_group!+criterion_main!生成入口。

完成这一步后,你就能全面使用 Criterion.rs 的基准组、输入参数化、吞吐量统计、自定义测量与 HTML 报告等能力。

八、参考文件索引

想深入钻研的读者,可以继续阅读仓库中的以下文件:

  • bencher_compat/README.md:兼容层的 crate 级说明(本文主体文档)
  • bencher_compat/src/lib.rs:Bencher、benchmark_group!、benchmark_main!的完整实现
  • bencher_compat/Cargo.toml:依赖声明与real_blackbox特性
  • bencher_compat/benches/bencher_example.rs:可直接运行的工作示例
  • book/src/user_guide/bencher_compatibility.md:官方用户指南中的兼容层章节
  • book/src/user_guide/migrating_from_libtest.md:从 libtest/bencher 迁移到原生 Criterion.rs 的完整示范
  • src/bencher.rs:Criterion.rs 底层Bencher与各计时循环的时序模型
  • src/lib.rs:configure_from_args、final_summary、bench_function的实现
  • src/benchmark_group.rs:throughput等高级配置的入口

总结:criterion_bencher_compat是 Criterion.rs 为bencher用户准备的“零摩擦入场券”——改两行配置、加一行as bencher别名,即可用上统计驱动的基准测试基础设施;而其精简的 API 表面也暗示了它的定位是过渡工具,真正发挥 Criterion.rs 全部实力仍建议最终迁移到原生接口。

  • 开发工具
  • 性能测试

【免费下载链接】criterion.rs

Statistics-driven benchmarking library for Rust

项目地址:https://gitcode.com/gh_mirrors/cr/criterion.rs
点击查看免费下载

相关推荐

上一篇:终极指南:在Mac上免费实现NTFS硬盘读写完整解决方案
下一篇:SciPy 的 cython_lapack:在 Cython 中直接调用 LAPACK 的官方 Cython 级封装指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 4:16:22

从空链接到完整干货:内容策划的实操五步法

这次接到的项目有点特殊&#xff1a;项目标题是一串专栏链接&#xff0c;正文和摘要全部为空&#xff0c;相关热搜词、最新网络热词也是空白。在内容这一行&#xff0c;这种“空标题链接”的需求其实并不少见&#xff0c;以前我总会下意识地点开链接&#xff0c;越快看到正文越…

作者头像 李华
网站建设 2026/10/12 4:14:45

开放代码评审:一种提升协作效率与知识沉淀的工程实践

1. 项目概述&#xff1a;这不是代码检查&#xff0c;而是一场协作范式的重构“open-code-review”这个词组乍看像一个技术名词&#xff0c;实则是一次开发文化层面的微小但坚定的转向。它不指代某个具体工具、平台或开源项目&#xff0c;而是描述一种将代码审查&#xff08;Cod…

作者头像 李华
网站建设 2026/10/12 4:14:31

从软件到硬件:三角测量攻击的芯片级检测线索

1. 从软件到硬件的攻击演进&#xff1a;为什么"三角测量"值得警惕过去几年&#xff0c;安全圈对移动终端威胁的讨论大多集中在App漏洞、恶意软件、钓鱼链接这些软件层面。大家普遍默认一个前提&#xff1a;只要系统保持更新、不随意安装未知来源应用&#xff0c;手机…

作者头像 李华
网站建设 2026/10/12 4:13:20

智能体发现室温磁性半导体:四步闭环筛选路径解析

我大概在三周前第一次认真读完这个结果时&#xff0c;第一反应不是“AI又发了一篇论文”&#xff0c;而是“室温磁性半导体这个被卡了二十多年的方向&#xff0c;终于被一条新路径撬开了一道缝”。我的背景是材料计算出身&#xff0c;做过不少高通量筛选的工作&#xff0c;所以…

作者头像 李华
网站建设 2026/10/12 4:13:18

零基础学网络安全入门指南:从网络协议到渗透测试的完整学习路线

很多人问我"零基础学网络安全到底怎么入门"&#xff0c;这个问题我回答过不下上百遍。说实话&#xff0c;真正劝退大多数人的不是技术难度&#xff0c;而是信息太杂、路线太乱。今天这篇文章就基于我这些年的学习经验和带新人的经历&#xff0c;把从零开始的完整路径…

作者头像 李华
网站建设 2026/10/12 4:13:17

深入理解应用层协议:HTTP、DNS与抓包排障实战

做了几年网络相关的活儿&#xff0c;最深的感受就是&#xff1a;很多人排障排到头大&#xff0c;最后发现根本不是代码问题&#xff0c;而是对应用层协议的理解不到位。HTTP报文的格式、DNS解析的链路、TCP三次握手和应用层之间到底是什么关系&#xff0c;这些概念如果只是考试…

作者头像 李华