如何使用 Gungraun 对 Rust 代码进行基准测试

Everett Pompeii

Everett Pompeii


什么是基准测试?

基准测试是测试代码性能的实践, 以查看它的速度(延迟)或工作量(吞吐量)。 在软件开发中,这一步通常被忽视, 但对于创建和维护快速且高性能的代码至关重要。 基准测试为开发人员提供了必要的指标, 以了解他们的代码在各种工作负载和条件下的表现。 出于与编写单元测试和集成测试以防止功能回归相同的原因, 你应该编写基准测试以防止性能回归。 性能错误也是错误!

用 Rust 编写 FizzBuzz

为了编写基准测试,我们需要一些源代码来进行基准测试。 首先,我们将编写一个非常简单的程序, FizzBuzz

FizzBuzz的规则如下:

写一个程序,打印从 1100 的整数(包含):

  • 对于三的倍数,打印 Fizz
  • 对于五的倍数,打印 Buzz
  • 对于既是三的倍数又是五的倍数的数,打印 FizzBuzz
  • 对于所有其他数,打印这个数字

许多种编写FizzBuzz的方法。 所以我们会选择我最喜欢的一种:

fn main() {
for i in 1..=100 {
match (i % 3, i % 5) {
(0, 0) => println!("FizzBuzz"),
(0, _) => println!("Fizz"),
(_, 0) => println!("Buzz"),
(_, _) => println!("{i}"),
}
}
}
  • 创建一个 main 函数
  • 1 迭代到 100(含)。
  • 对于每个数字,分别计算 35 的取余(除后余数)。
  • 对两个余数进行模式匹配。 如果余数为 0,那么这个数就是给定因素的倍数。
  • 如果 35 的余数都为 0,则打印 FizzBuzz
  • 如果只有 3 的余数为 0,则打印 Fizz
  • 如果只有 5 的余数为 0,则打印 Buzz
  • 否则,就打印这个数字。

按步骤操作

为了与本教程进行同步学习,您需要 安装 Rust

🐰 这篇文章的源代码在 GitHub 上可以找到

安装好 Rust 后,您可以打开一个终端窗口,然后输入:cargo init game

然后导航至新创建的 game 目录。

game
├── Cargo.toml
└── src
└── main.rs

你应该能看到一个名为 src 的目录,其中有一个名为 main.rs 的文件:

fn main() {
println!("Hello, world!");
}

将其内容替换为上述的 FizzBuzz 实现。然后运行 cargo run。 输出结果应该像这样:

$ cargo run
Compiling playground v0.0.1 (/home/bencher)
Finished dev [unoptimized + debuginfo] target(s) in 0.44s
Running `target/debug/game`
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...
97
98
Fizz
Buzz

🐰 砰!你正在破解编程面试!

应该生成了一个新的 Cargo.lock 文件:

game
├── Cargo.lock
├── Cargo.toml
└── src
└── main.rs

在进一步探讨之前,有必要讨论微基准测试和宏基准测试之间的区别。

微基准测试 vs 宏基准测试

有两大类软件基准测试:微基准测试和宏基准测试。 微基准测试的操作层次和单元测试类似。 例如,为一个确定单个数字是 FizzBuzz,还是 FizzBuzz 的函数设立的基准测试,就是一个微基准测试。 宏基准测试的操作层次和集成测试类似。 例如,为一函数设立的基准测试,该函数可以玩完整个 FizzBuzz 游戏,从 1100,这就是一个宏基准测试。

通常,尽可能在最低的抽象级别进行测试是最好的。 在基准测试的情况下,这使得它们更易于维护, 并有助于减少测量中的噪声。 然而,就像有一些端到端测试对于健全性检查整个系统根据预期组合在一起非常有用一样, 拥有宏基准测试对于确保您的软件中的关键路径保持高性能也非常有用。

在 Rust 中进行基准测试

在 Rust 中进行基准测试的三个热门选项是: libtest bench, Criterion, 和 Iai

libtest 是 Rust 内置的单元测试和基准测试框架。虽然它是 Rust 标准库的一部分,但 libtest bench 仍被认为是不稳定的,因此仅在 nightly 编译器版本中可用。要在稳定版 Rust 编译器上工作,则需要使用一个单独的基准测试工具 。然而,这两者都没有得到积极的开发。

Rust 生态系统中最流行的基准测试工具是 Criterion。它可以在稳定版和 nightly Rust 编译器版本上工作,并且已经成为 Rust 社区内事实上的标准。相比 libtest bench,Criterion 还具有更多的功能。

Criterion 的一个实验性替代品是 Iai,同样出自 Criterion 的创建者。然而,它使用指令计数而不是墙钟时间:CPU 指令、L1 访问、L2 访问和 RAM 访问。这允许一次性基准测试,因为这些指标在不同运行之间应该几乎保持相同。

四种都受到 Bencher 支持。那么为什么选择 Gungraun(Iai-Callgrind 的重命名继承者)? Gungraun 使用指令计数而不是挂钟时间。 这使得它非常适合持续基准测试,即在 CI 中进行基准测试。 我建议使用 Gungraun 进行持续基准测试,特别是如果您使用共享运行器。 Gungraun 得到积极维护,并拥有全面的在线文档, 使其成为长期项目的可靠选择。 重要的是要理解,Gungraun 只测量您真正关心的事物的代理。 从 1,000 条指令增加到 2,000 条指令会使您的应用程序延迟翻倍吗? 也许会,也许不会。 因此,在运行基于指令计数的基准测试的同时,并行运行基于挂钟时间的基准测试也可能很有用。

安装 Valgrind

Gungraun 使用名为 Valgrind 的工具来收集指令计数。 Valgrind 支持 Linux、Solaris、FreeBSD 和 macOS。 但是,macOS 支持仅限于 x86_64 处理器,因为 arm64(M1、M2 等)处理器尚未支持

在 Debian 上运行:sudo apt-get install valgrind

在 macOS 上(仅 x86_64/Intel 芯片):brew install valgrind

重构 FizzBuzz

为了测试我们的 FizzBuzz 应用程序,我们将逻辑从程序的 main 函数中解耦。 与其他基准测试工具不同,Gungraun 可以对基准测试二进制文件 和 main 函数进行基准测试,但这纯粹是宏基准测试。我们想同时进行宏基准测试和微基准测试。 为此,我们需要做一些更改。

src 下,创建一个名为 lib.rs 的新文件:

game
├── Cargo.lock
├── Cargo.toml
└── src
└── lib.rs
└── main.rs

将以下代码添加到 lib.rs

pub fn play_game(n: u32, print: bool) {
let result = fizz_buzz(n);
if print {
println!("{result}");
}
}
pub fn fizz_buzz(n: u32) -> String {
match (n % 3, n % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
(_, _) => n.to_string(),
}
}
  • play_game:接受一个无符号整数 n,用该数字调用 fizz_buzz,如果 printtrue 则打印结果。
  • fizz_buzz:接受一个无符号整数 n,执行实际的 FizzBuzzFizzBuzz 或数字逻辑,并将结果作为字符串返回。

然后更新后的 main.rs 如下所示:

use game::play_game;
fn main() {
for i in 1..=100 {
play_game(i, true);
}
}
  • game::play_game:从我们刚刚用 lib.rs 创建的 game crate 中导入 play_game
  • main:程序的主入口点,遍历从 1100(含)的数字,并为每个数字调用 play_gameprint 设置为 true

对 FizzBuzz 进行基准测试

为了对我们的代码进行基准测试,我们需要创建一个 benches 目录并 添加一个包含基准测试的文件 play_game.rs。 请注意,为了简单起见,我们偏离了推荐的方式来构建基准测试。 对于您的项目,您应该遵循建议:

game
├── Cargo.lock
├── Cargo.toml
└── benches
└── play_game.rs
└── src
└── lib.rs
└── main.rs

play_game.rs 中添加以下代码:

use gungraun::prelude::*;
use std::hint::black_box;
use game::play_game;
#[library_benchmark]
fn bench_play_game() {
for i in 1..=100 {
play_game(black_box(i), black_box(false))
}
}
library_benchmark_group!(
name = bench_play_game_group,
benchmarks = [bench_play_game]
);
main!(library_benchmark_groups = bench_play_game_group);
  • 导入 gungraun::prelude 模块,它带来了必要的宏。
  • 使用 std::hint::black_box 防止编译器优化掉我们的基准测试。
  • 从我们的 game crate 导入 play_game 函数。
  • 使用 #[library_benchmark] 属性创建一个名为 bench_play_game 的库基准测试函数。
  • 1 循环到 100,并在 print 设为 false 的情况下调用 play_game
  • 创建一个名为 bench_play_game_group 的库基准测试组,包含我们的 bench_play_game 基准测试。
  • 使用 main! 宏来运行基准测试组。

现在,我们需要配置 game crate 来运行我们的基准测试。

将以下内容添加到 Cargo.toml 文件的_底部_:

[dev-dependencies]
gungraun = "0.18.0"
[[bench]]
name = "play_game"
harness = false
[profile.bench]
debug = true
  • gungraun:将 gungraun 添加为开发依赖,因为我们仅将其用于性能测试。
  • bench:将 play_game 注册为基准测试,并将 harness 设置为 false,因为我们将使用 Gungraun 作为基准测试工具。
  • debug = true:在基准测试构建中启用调试信息,这是 Gungraun 提供详细输出所必需的。

现在我们准备好对代码进行基准测试了,运行 cargo bench

$ cargo bench
Finished `bench` profile [optimized + debuginfo] target(s) in 0.73s
Running benches/play_game.rs (target/release/deps/play_game-84c12f98b1991829)
play_game::bench_play_game_group::bench_play_game_100
Instructions: 17902|N/A (*********)
L1 Hits: 24984|N/A (*********)
LL Hits: 1|N/A (*********)
RAM Hits: 20|N/A (*********)
Total read+write: 25005|N/A (*********)
Estimated Cycles: 25689|N/A (*********)
Gungraun result: Ok. 1 without regressions; 0 regressed; 0 filtered; 1 benchmarks finished in 0.15258s

🐰 生菜萝卜甜菜!我们得到了第一个基准测试指标!

终于,我们可以让疲惫的开发者头脑休息一下了… 开玩笑的,我们的用户想要新功能!

用 Rust 编写 FizzBuzzFibonacci

我们的关键绩效指标(KPI)下降了,所以我们的产品经理(PM)希望我们添加新功能。 经过多次头脑风暴和许多用户采访后,我们决定光有 FizzBuzz 已经不够了。 现在的孩子们希望有一个新的游戏,FizzBuzzFibonacci。

FizzBuzzFibonacci的规则如下:

编写一个程序,打印从 1100 的整数(包括):

  • 对于三的倍数,打印 Fizz
  • 对于五的倍数,打印 Buzz
  • 对于既是三的倍数又是五的倍数的,打印 FizzBuzz
  • 对于是斐波那契数列的数字,只打印 Fibonacci
  • 对于所有其他的,打印该数

斐波那契数列是一个每个数字是前两个数字之和的序列。 例如,从 01开始,斐波那契数列的下一个数字将是 1。 后面是:2, 3, 5, 8 等等。 斐波那契数列的数字被称为斐波那契数。所以我们将不得不编写一个检测斐波那契数的函数。

许多方法可以编写斐波那契数列,同样也有许多方法可以检测一个斐波那契数。 所以我们将采用我的最爱:

fn is_fibonacci_number(n: u32) -> bool {
for i in 0..=n {
let (mut previous, mut current) = (0, 1);
while current < i {
let next = previous + current;
previous = current;
current = next;
}
if current == n {
return true;
}
}
false
}
  • 创建一个名为 is_fibonacci_number 的函数,该函数接收一个无符号整数,并返回一个布尔值。
  • 遍历从 0 到我们给定的数 n(包含 n)的所有数字。
  • 01 分别作为前一个当前 数字来初始化我们的斐波那契序列。
  • 当前数字小于当前迭代 i 时持续迭代。
  • 添加前一个当前 数字来获得 下一个 数字。
  • 前一个 数字更新为 当前 数字。
  • 当前 数字更新为 下一个 数字。
  • 一旦 当前 大于或等于给定数字 n,我们将退出循环。
  • 检查 当前 数字是否等于给定数字 n,如果是,则返回 true
  • 否则,返回 false

现在我们需要更新我们的 fizz_buzz 功能:

pub fn fizz_buzz_fibonacci(n: u32) -> String {
if is_fibonacci_number(n) {
"Fibonacci".to_string()
} else {
match (n % 3, n % 5) {
(0, 0) => "FizzBuzz".to_string(),
(0, _) => "Fizz".to_string(),
(_, 0) => "Buzz".to_string(),
(_, _) => n.to_string(),
}
}
}
  • fizz_buzz 功能重命名为 fizz_buzz_fibonacci 以使其更具描述性。
  • 调用我们的 is_fibonacci_number 辅助函数。
  • 如果 is_fibonacci_number 的结果为 true,则返回 Fibonacci
  • 如果 is_fibonacci_number 的结果为 false,则执行相同的 FizzBuzzFizzBuzz 或数字逻辑,并返回结果。

因为我们将 fizz_buzz 重命名为 fizz_buzz_fibonacci,我们也需要更新我们的 play_game 功能:

pub fn play_game(n: u32, print: bool) {
let result = fizz_buzz_fibonacci(n);
if print {
println!("{result}");
}
}

我们的 mainbench_play_game 功能可以保持完全相同。

对 FizzBuzzFibonacci 进行基准测试

现在我们可以重新运行基准测试:

$ cargo bench
Finished `bench` profile [optimized + debuginfo] target(s) in 0.73s
Running benches/play_game.rs (target/release/deps/play_game-84c12f98b1991829)
play_game::bench_play_game_group::bench_play_game_100
Instructions: 331835|17902 (+1753.62%) [+18.5362x]
L1 Hits: 338828|24984 (+1256.18%) [+13.5618x]
LL Hits: 2|1 (+100.000%) [+2.00000x]
RAM Hits: 22|20 (+10.0000%) [+1.10000x]
Total read+write: 338852|25005 (+1255.14%) [+13.5514x]
Estimated Cycles: 339608|25689 (+1222.00%) [+13.2200x]
Gungraun result: Ok. 1 without regressions; 0 regressed; 0 filtered; 1 benchmarks finished in 0.15254s

哦,太棒了!Gungraun 告诉我们 FizzBuzz 和 FizzBuzzFibonacci 游戏的估计周期差异。 您的数字会与我的略有不同。 然而,两个游戏之间的差异可能在 10-15x 范围内。 我觉得不错!特别是为我们的游戏添加了像 Fibonacci 这样高大上的功能。 孩子们会喜欢的!

在 Rust 中展开 FizzBuzzFibonacci

我们的游戏很受欢迎!孩子们确实喜欢玩 FizzBuzzFibonacci。 为此,高层下达了他们想要续集的消息。 但这是现代世界,我们需要的是年度循环收入(ARR),而不是一次性购买! 我们游戏的新愿景是开放性的,不再固定在 1100 之间(即使是包含在内的)。 不,我们正在开拓新的疆域!

Open World FizzBuzzFibonacci的规则如下:

编写一个程序,它接受_任何_正整数并打印:

  • 对于三的倍数,打印 Fizz
  • 对于五的倍数,打印 Buzz
  • 对于同时是三和五的倍数的,则打印 FizzBuzz
  • 对于是斐波那契数列的数字,只打印 Fibonacci
  • 对于其他所有数字,打印该数字

为了让我们的游戏适应任何数字,我们需要接受一个命令行参数。 将 main 函数更新为如下形式:

fn main() {
let args: Vec<String> = std::env::args().collect();
let i = args
.get(1)
.map(|s| s.parse::<u32>())
.unwrap_or(Ok(15))
.unwrap_or(15);
play_game(i, true);
}
  • 收集所有从命令行传递给我们游戏的参数(args)。
  • 获取传递给我们游戏的第一个参数,并将其解析为无符号整数 i
  • 如果解析失败或没有传入参数,就默认以 15 作为输入运行我们的游戏。
  • 最后,用新解析的无符号整数 i 来玩我们的游戏。

现在我们可以用任何数字来玩我们的游戏了! 使用 cargo run 后跟 -- 将参数传递给我们的游戏:

$ cargo run -- 9
Compiling playground v0.0.1 (/home/bencher)
Finished dev [unoptimized + debuginfo] target(s) in 0.44s
Running `target/debug/game 9`
Fizz
$ cargo run -- 10
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/game 10`
Buzz
$ cargo run -- 13
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/game 13`
Fibonacci

如果我们省略或提供了无效的数字:

$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/game`
FizzBuzz
$ cargo run -- bad
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/game bad`
FizzBuzz

哇,这是一个仔细的测试过程!CI 通过了。我们的上司非常高兴。 让我们发布吧!🚀

结束


海绵宝宝三周后
一切正常的模因

🐰 … 也许这是你的职业生涯的结束?


开玩笑的!其实一切都在燃烧!🔥

起初,一切看似进行得顺利。 但在周六早上02:07,我的寻呼机响了起来:

📟 你的游戏起火了!🔥

从床上匆忙爬起来后,我试图弄清楚发生了什么。 我试图搜索日志,但这非常困难,因为一切都在不停地崩溃。 最后,我发现了问题。孩子们!他们非常喜欢我们的游戏,以至于玩了高达一百万次! 在一股灵感的闪现中,我添加了两个新的基准测试:

这就是 Gungraun 参数化基准测试大放异彩的地方!我们不需要为每个输入编写单独的基准测试函数,而是可以使用 #[benches::...] 属性:

#[library_benchmark]
#[benches::play(100, 1_000_000)]
fn bench_play_game(n: u32) {
play_game(black_box(n), black_box(false));
}
library_benchmark_group!(
name = bench_play_game_group,
benchmarks = [bench_play_game_100, bench_play_game]
);
  • 添加 #[benches::play(100, 1_000_000)] 属性,创建输入为 100 的基准测试变体和输入为 1_000_000 的另一个变体。
  • 基准测试函数接受一个 n: u32 参数,接收每个值。
  • bench_play_game 函数添加到 library_benchmark_group!

很好!一个基准测试函数,多个测试用例!

当我运行它时,得到了这个结果:

$ cargo bench
Finished `bench` profile [optimized + debuginfo] target(s) in 0.73s
Running benches/play_game.rs (target/release/deps/play_game-84c12f98b1991829)
play_game::bench_play_game_group::bench_play_game_100
Instructions: 331835|331835 (No change)
L1 Hits: 338831|338828 (+0.00089%) [+1.00001x]
LL Hits: 1|2 (-50.0000%) [-2.00000x]
RAM Hits: 20|22 (-9.09091%) [-1.10000x]
Total read+write: 338852|338852 (No change)
Estimated Cycles: 339536|339608 (-0.02120%) [-1.00021x]
play_game::bench_play_game_group::bench_play_game play_0:(100)
Instructions: 7072|N/A (*********)
L1 Hits: 7128|N/A (*********)
LL Hits: 1|N/A (*********)
RAM Hits: 9|N/A (*********)
Total read+write: 7138|N/A (*********)
Estimated Cycles: 7448|N/A (*********)
play_game::bench_play_game_group::bench_play_game play_1:(1_000_000)
Instructions: 183930316|N/A (*********)
L1 Hits: 183930372|N/A (*********)
LL Hits: 1|N/A (*********)
RAM Hits: 9|N/A (*********)
Total read+write: 183930382|N/A (*********)
Estimated Cycles: 183930692|N/A (*********)
Gungraun result: Ok. 3 without regressions; 0 regressed; 0 filtered; 3 benchmarks finished in 1.45441s

基准测试在 1.45 秒内完成。真快!与挂钟基准测试多次运行不同, 每个 Gungraun 基准测试只运行一次。但是等等,为什么第一个基准测试 bench_play_game_100 有变化, 虽然我们没有改变这个基准测试中的任何东西? 没错,但我们在基准测试文件中改变了其他东西,由于 Gungraun 和 Valgrind 是灵敏的仪器,即使非常小的变化也会被记录。 然而,这种小变化,特别是缓存指标中的变化,是可以忽略不计的。随着时间的推移,您将 对指标中的关键变化产生直觉。让我们仔细看看输出。

play_game::bench_play_game_group::bench_play_game play_1:(1_000_000)
Instructions: 183930316|N/A (*********)
L1 Hits: 183930372|N/A (*********)
LL Hits: 1|N/A (*********)
RAM Hits: 9|N/A (*********)
Total read+write: 183930382|N/A (*********)
Estimated Cycles: 183930692|N/A (*********)

什么!7,448 估计周期 x 1,000 应该是 7,448,000 估计周期,而不是 183,930,692 估计周期 🤯 尽管我的斐波那契数列代码在功能上是正确的,但我一定在某处有一个性能 bug。

修复 Rust 中的 FizzBuzzFibonacci

让我们再次看一下 is_fibonacci_number 函数:

fn is_fibonacci_number(n: u32) -> bool {
for i in 0..=n {
let (mut previous, mut current) = (0, 1);
while current < i {
let next = previous + current;
previous = current;
current = next;
}
if current == n {
return true;
}
}
false
}

现在我在考虑性能,我意识到我有一个不必要的,额外的循环。 我们可以完全摆脱 for i in 0..=n {} 循环, 只需直接比较 current 值和给定的数字 (n) 🤦

fn is_fibonacci_number(n: u32) -> bool {
let (mut previous, mut current) = (0, 1);
while current < n {
let next = previous + current;
previous = current;
current = next;
}
current == n
}
  • 更新您的 is_fibonacci_number 函数。
  • 01 初始化我们的斐波那契序列作为 previouscurrent 数字。
  • current 数字小于 给定数字 n 时迭代。
  • previouscurrent 数字相加以获得 next 数字。
  • previous 数字更新为 current 数字。
  • current 数字更新为 next 数字。
  • 一旦 current 大于或等于给定的数字 n,我们将退出循环。
  • 检查 current 数字是否等于给定的数字 n 并返回该结果。

现在让我们重新运行这些基准测试,看看结果如何:

$ cargo bench
Finished `bench` profile [optimized + debuginfo] target(s) in 0.73s
Running benches/play_game.rs (target/release/deps/play_game-84c12f98b1991829)
play_game::bench_play_game_group::bench_play_game_100
Instructions: 23679|331835 (-92.8642%) [-14.0139x]
L1 Hits: 30675|338831 (-90.9468%) [-11.0458x]
LL Hits: 2|1 (+100.000%) [+2.00000x]
RAM Hits: 19|20 (-5.00000%) [-1.05263x]
Total read+write: 30696|338852 (-90.9412%) [-11.0390x]
Estimated Cycles: 31350|339536 (-90.7668%) [-10.8305x]
play_game::bench_play_game_group::bench_play_game play_0:(100)
Instructions: 218|7072 (-96.9174%) [-32.4404x]
L1 Hits: 273|7128 (-96.1700%) [-26.1099x]
LL Hits: 1|1 (No change)
RAM Hits: 10|9 (+11.1111%) [+1.11111x]
Total read+write: 284|7138 (-96.0213%) [-25.1338x]
Estimated Cycles: 628|7448 (-91.5682%) [-11.8599x]
play_game::bench_play_game_group::bench_play_game play_1:(1_000_000)
Instructions: 332|183930316 (-99.9998%) [ -554007x]
L1 Hits: 387|183930372 (-99.9998%) [ -475272x]
LL Hits: 1|1 (No change)
RAM Hits: 10|9 (+11.1111%) [+1.11111x]
Total read+write: 398|183930382 (-99.9998%) [ -462137x]
Estimated Cycles: 742|183930692 (-99.9996%) [ -247885x]
Gungraun result: Ok. 3 without regressions; 0 regressed; 0 filtered; 3 benchmarks finished in 0.45459s

哦,哇!我们的 100 基准测试下降了 11%,而我们的 1_000_000 基准测试下降了超过 200,000 倍!从 183,930,692 估计周期 降到 742 估计周期! 减少了 99.9996%

🐰 嘿,至少我们在性能 bug 进入生产环境之前捕获了它…哦,对了。算了…

在 CI 中捕获性能回归

由于我那个小小的性能错误,我们的游戏收到了大量的负面评论,这让高管们非常不满。 他们告诉我不要让这种情况再次发生,而当我询问如何做到时,他们只是告诉我不要再犯。 我该如何管理这个问题呢‽

幸运的是,我找到了这款叫做 Bencher 的超棒开源工具。 它有一个非常慷慨的免费层,因此我可以在我的个人项目中使用 Bencher Cloud。 而在工作中需要在我们的私有云内,我已经开始使用 Bencher Self-Hosted

Bencher有一个内建的适配器, 所以很容易集成到 CI 中。在遵循快速开始指南后, 我能够运行我的基准测试并用 Bencher 追踪它们。

$ bencher run --project game "cargo bench"
Finished `bench` profile [optimized + debuginfo] target(s) in 0.73s
Running benches/play_game.rs (target/release/deps/play_game-84c12f98b1991829)
play_game::bench_play_game_group::bench_play_game_100
Instructions: 23679|23679 (No change)
L1 Hits: 30675|30675 (No change)
LL Hits: 2|2 (No change)
RAM Hits: 19|19 (No change)
Total read+write: 30696|30696 (No change)
Estimated Cycles: 31350|31350 (No change)
play_game::bench_play_game_group::bench_play_game play_0:(100)
Instructions: 218|218 (No change)
L1 Hits: 273|273 (No change)
LL Hits: 1|1 (No change)
RAM Hits: 10|10 (No change)
Total read+write: 284|284 (No change)
Estimated Cycles: 628|628 (No change)
play_game::bench_play_game_group::bench_play_game play_1:(1_000_000)
Instructions: 332|332 (No change)
L1 Hits: 387|387 (No change)
LL Hits: 1|1 (No change)
RAM Hits: 10|10 (No change)
Total read+write: 398|398 (No change)
Estimated Cycles: 742|742 (No change)
Gungraun result: Ok. 3 without regressions; 0 regressed; 0 filtered; 3 benchmarks finished in 0.45370s
Bencher New Report:
...
View results:
- play_game::bench_play_game_group::bench_play_game play_0:(100) (Estimated Cycles): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c10f90a6-268a-4b31-b625-66f95eb4861f&measures=f03d9a6c-2b63-45c3-b34a-37149d1a7961&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_0:(100) (Instructions): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c10f90a6-268a-4b31-b625-66f95eb4861f&measures=17acf657-735b-4ece-ab32-ba857db5edce&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_0:(100) (L1 Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c10f90a6-268a-4b31-b625-66f95eb4861f&measures=009a129f-4476-4202-9e2b-cd7aed7110ac&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_0:(100) (LL Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c10f90a6-268a-4b31-b625-66f95eb4861f&measures=932a00d1-e064-4f18-81fb-aa94a5f6d5a0&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_0:(100) (RAM Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c10f90a6-268a-4b31-b625-66f95eb4861f&measures=c98672c7-8229-4e90-9773-482618b71dbf&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_0:(100) (Total read+write): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c10f90a6-268a-4b31-b625-66f95eb4861f&measures=0bd6ec91-2b29-47ea-801e-dc09338f3119&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_1:(1_000_000) (Estimated Cycles): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c0c16a00-5ad1-4787-92ac-a39eed8c5375&measures=f03d9a6c-2b63-45c3-b34a-37149d1a7961&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_1:(1_000_000) (Instructions): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c0c16a00-5ad1-4787-92ac-a39eed8c5375&measures=17acf657-735b-4ece-ab32-ba857db5edce&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_1:(1_000_000) (L1 Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c0c16a00-5ad1-4787-92ac-a39eed8c5375&measures=009a129f-4476-4202-9e2b-cd7aed7110ac&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_1:(1_000_000) (LL Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c0c16a00-5ad1-4787-92ac-a39eed8c5375&measures=932a00d1-e064-4f18-81fb-aa94a5f6d5a0&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_1:(1_000_000) (RAM Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c0c16a00-5ad1-4787-92ac-a39eed8c5375&measures=c98672c7-8229-4e90-9773-482618b71dbf&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game play_1:(1_000_000) (Total read+write): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=c0c16a00-5ad1-4787-92ac-a39eed8c5375&measures=0bd6ec91-2b29-47ea-801e-dc09338f3119&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game_100 (Estimated Cycles): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=4da8a40c-2282-487c-bac8-21218deba041&measures=f03d9a6c-2b63-45c3-b34a-37149d1a7961&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game_100 (Instructions): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=4da8a40c-2282-487c-bac8-21218deba041&measures=17acf657-735b-4ece-ab32-ba857db5edce&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game_100 (L1 Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=4da8a40c-2282-487c-bac8-21218deba041&measures=009a129f-4476-4202-9e2b-cd7aed7110ac&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game_100 (LL Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=4da8a40c-2282-487c-bac8-21218deba041&measures=932a00d1-e064-4f18-81fb-aa94a5f6d5a0&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game_100 (RAM Hits): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=4da8a40c-2282-487c-bac8-21218deba041&measures=c98672c7-8229-4e90-9773-482618b71dbf&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409
- play_game::bench_play_game_group::bench_play_game_100 (Total read+write): https://bencher.dev/console/projects/game/perf/game?branches=b53281dd-375a-4986-8074-17e9d488815e&heads=77345ed9-2e45-4d43-8186-f0f99b8120d1&testbeds=ef809413-f1ae-4889-bb91-d5e2e5769830&specs=%2C&benchmarks=4da8a40c-2282-487c-bac8-21218deba041&measures=0bd6ec91-2b29-47ea-801e-dc09338f3119&start_time=1773186232000&end_time=1775778233000&report=c703a61c-46a4-43bf-bbce-cb69d679b409

使用这个由一个友善的兔子给我的巧妙的时间旅行设备, 我能够回到过去,重演如果我们一直都在使用Bencher的情况下会发生什么。 你可以看到我们首次推出存在问题的FizzBuzzFibonacci实现的位置。 我马上在我的拉取请求评论中得到了CI的失败信息。 就在那天,我修复了性能问题,摆脱了那不必要的额外循环。 没有火灾。顾客都非常开心。

Bencher: 持续性能基准测试

Bencher是一套持续基准测试工具。 你是否曾经因为性能回归影响到了你的用户? Bencher可以防止这种情况的发生。 Bencher让你有能力在性能回归被合并 之前 就进行检测和预防。

  • 运行: 使用 完全相同 的裸机运行器和你喜爱的基准测试工具,在本地或CI中执行你的基准测试。bencher CLI在裸机上编排你的基准测试运行并存储其结果。
  • 追踪: 追踪你的基准测试结果的趋势。根据源分支、测试床和度量,使用Bencher web控制台来监视、查询和绘制结果图表。
  • 捕获: 使用 完全相同 的裸机硬件,在本地或CI中捕获性能回归。Bencher使用最先进的、可定制的分析技术在它们被合并之前就检测到性能回归。

基于防止功能回归的原因运行单元测试,我们也应该使用Bencher运行基准测试以防止性能回归。性能问题就是错误!

开始捕捉性能回归 — 免费试用Bencher Cloud

🤖 该文档由 AI 自动翻译。 它可能不准确并且可能包含错误。 如果您发现任何错误,请在 GitHub 上提出问题.