Criterionを使ってRustコードをベンチマークする方法


ベンチマーキングとは何ですか?

ベンチマーキングは、コードの実行速度(レイテンシー)や処理能力(スループット)を測定するという行為です。 この重要なステップは、ソフトウェア開発の中でよく見過ごされがちですが、高速でパフォーマンスに優れたコードを作成・維持するためには不可欠です。 ベンチマーキングは、開発者が自身のコードが様々な作業負荷や条件下でどの程度性能を発揮するかを理解するための必要な指標を提供します。 機能のリグレッションを防ぐために単体テストや統合テストを書くのと同じ理由で、パフォーマンスのリグレッションを防ぐためにベンチマークを書くべきです。 パフォーマンスのバグもバグです!

Rustとは何ですか?

Rustは、速度、信頼性、および生産性を重視するオープンソースのプログラミング言語です。ガベージコレクタを必要とせずにメモリ安全性を達成します。

以下のような場合には、Rustの使用を検討すべきです:

  • パフォーマンスが重要なローレベルプログラム
  • 複数の異なる言語で使用される共有ライブラリ
  • 複雑なコマンドラインインターフェース (CLI)
  • 多くの貢献者がいる長寿命のソフトウェアプロジェクト

Rustは開発者の生産性に強い重視を置いています。 Cargoは公式のパッケージマネージャで、以下のような多くのタスクを処理します:

  • プロジェクトの依存関係の管理
  • バイナリ、テスト、およびベンチマークのコンパイル
  • Linting
  • フォーマット

RustでFizzBuzzを書く

ベンチマークを書くためには、ベンチマークするソースコードが必要です。 まず、非常にシンプルなプログラム、 FizzBuzz を書いてみましょう。

FizzBuzzのルールは以下の通りです:

1から100までの整数を印刷するプログラムを書く:

  • 三の倍数の場合は、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 の両方で余り(除算後の余り)を計算します。
  • 2つの余りにパターンマッチを行います。 余りが 0 の場合、その数は指定された因数の倍数です。
  • 35 の両方で余りが 0 の場合は FizzBuzz を出力します。
  • 3 のみで余りが 0 の場合は Fizz を出力します。
  • 5 のみで余りが 0 の場合は Buzz を出力します。
  • それ以外の場合は、単に数字を出力します。
import GameTree from "../game-tree.mdx";
import HelloWorld from "../hello-world-rust-code.mdx";
import FizzBuzzOutput from "../fizz-buzz-output.mdx";
import GameLockTree from "../game-lock-tree.mdx";
### ステップバイステップで進める
このステップバイステップのチュートリアルを進めるには、まず[Rustをインストール](https://rustup.rs/)する必要があります。
> 🐰 このポストのソースコードは[GitHub上で利用可能](https://github.com/bencherdev/bencher/tree/main/examples/rust/bench)です。
Rustがインストールされたら、ターミナルウィンドウを開き、次のコマンドを入力します:`cargo init game`
その後、新たに作成された`game`ディレクトリに移動します。
<GameTree />
`src`という名前のディレクトリがあり、その中に`main.rs`というファイルが存在しているはずです:
<HelloWorld />
先ほどのFizzBuzzの実装をその内容と置き換えてから、`cargo run`を実行します。
出力結果は次のようになるはずです:
<FizzBuzzOutput />
> 🐰 ブーム! コーディングインタビューを突破していますね!
新たな`Cargo.lock`ファイルが生成されているはずです:
<GameLockTree />
これ以上進む前に、マイクロベンチマークとマクロベンチマークの違いについて話すことが重要です。

マイクロベンチマークとマクロベンチマーク

ソフトウェアベンチマークには、マイクロベンチマークとマクロベンチマークの2つの主要なカテゴリーがあります。 マイクロベンチマークは、ユニットテストと同様のレベルで動作します。 例えば、単一の数値に対してFizzBuzz、またはFizzBuzzを決定する関数のベンチマークはマイクロベンチマークになります。 一方、マクロベンチマークは、統合テストと同様のレベルで動作します。 例えば、1から100までの全ゲームをプレイするFizzBuzzの関数のベンチマークは、マクロベンチマークになります。

一般的に、可能な限り低い抽象レベルでテストすることが最善です。 ベンチマークの場合、これにより保守性が向上し、測定値のノイズを減らすことに役立ちます。 しかし、エンドツーエンドテストがシステム全体が予想通りに組み合わさるかのサニチェックに非常に役立つように、 マクロベンチマークがあると、ソフトウェアを通る重要なパスが性能を維持するために非常に役立ちます。

Rustにおけるベンチマーキング

Rustでのベンチマーキングには、3つの主要なオプションがあります。 それらは、libtest benchCriterion、そして Iaiです。

libtestはRustの組み込みユニットテストとベンチマーキングフレームワークです。 Rust標準ライブラリの一部ながらも、libtest benchはまだ不安定と見なされており、 nightlyコンパイラリリースでのみ利用可能です。 安定版のRustコンパイラ上で動作させるには、 別のベンチマーキングハーネス が必要です。 ただし、どちらも積極的に開発が行われているわけではありません。

Rustエコシステム内で最も積極的にメンテナンスされているベンチマーキングハーネスはCriterionです。 Criterionは安定版およびnightlyのRustコンパイラリリースの両方で動作し、 そして Rustコミュニティ内で事実上の標準となっています。 また、libtest benchと比較すると、Criterionははるかに機能が豊富です。

Criterionとは異なる実験的な選択肢としてIaiがあります。これはCriterionと同じクリエーターからのものです。 しかし、これは壁時計の時間の代わりに命令数を使用します: CPU命令、L1アクセス、L2アクセス、そしてRAMアクセス。 これにより、これらのメトリクスが実行間でほぼ同一に保たれるべきであるため、一発のベンチマーキングが可能になります。

全てがBencherによってサポートされています。それならばなぜCriterionを選ぶのでしょうか? Criterionは、Rustコミュニティにおける事実上の標準ベンチマーキングハーネスです。 私は、コードのレイテンシをベンチマーキングするためにCriterionを使用することをお勧めします。 つまり、Criterionは壁時計時間の測定に適しています。

FizzBuzzのリファクタリング

FizzBuzzアプリケーションをテストするためには、ロジックとプログラムのmain関数を切り離す必要があります。 ベンチマークハーネスは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クレートからplay_gameをインポートします。
  • main: プログラムのメインエントリーポイントで、1から100までの数字を反復処理し、各数字に対してplay_gameを呼び出し、printtrueに設定します。

FizzBuzzのベンチマーク

コードをベンチマークするために、benches ディレクトリを作成し、ベンチマークを含むファイル play_game.rs を追加する必要があります:

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

play_game.rs に次のコードを追加します:

use criterion::{criterion_group, criterion_main, Criterion};
use game::play_game;
fn bench_play_game(c: &mut Criterion) {
c.bench_function("bench_play_game", |b| {
b.iter(|| {
std::hint::black_box(for i in 1..=100 {
play_game(i, false)
});
});
});
}
criterion_group!(
benches,
bench_play_game,
);
criterion_main!(benches);
  • Criterion ベンチマークランナーをインポートします。
  • 我々の game クレートから play_game 関数をインポートします。
  • Criterion の mutable reference を受け取る bench_play_game という名前の関数を作成します。
  • Criterion インスタンス(c)を使用して、 bench_play_game という名前のベンチマークを作成します。
  • ベンチマークランナー(b)を使用して、マクロベンチマークを何度も実行します。
  • コンパイラが我々のコードを最適化しないように、“black box” 内でマクロベンチマークを実行します。
  • 1 から 100 まで繰り返します。
  • 各数字に対して、play_game を呼び出します。printfalse に設定します。

次に、ベンチマークを実行するための game クレートを設定する必要があります。

あなたの Cargo.toml ファイルの 最後 に、以下を追加してください:

[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "play_game"
harness = false
  • criterion: パフォーマンステストのためにのみ使用するので、開発依存関係として criterion を追加します。
  • bench: play_game をベンチマークとして登録し、harnessfalse に設定します。なぜなら、我々は Criterion をベンチマークハーネスとして使用するからです。

さあ、コードをベンチマークする準備が整いました。cargo bench を実行します:

$ cargo bench
Compiling playground v0.0.1 (/home/bencher)
Finished bench [optimized] target(s) in 4.79s
Running unittests src/main.rs (target/release/deps/game-68f58c96f4025bd4)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-043972c4132076a9)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-e0857103eb02eb56)
bench_play_game time: [3.0020 µs 3.0781 µs 3.1730 µs]
Found 12 outliers among 100 measurements (12.00%)
2 (2.00%) high mild
10 (10.00%) high severe

🐰 レタスターニップビート!私たちは最初のベンチマークメトリクスを得ました!

ついに…ちょっと待って、私たちのユーザーは新機能を求めています!

RustでFizzBuzzFibonacciを書く

私たちの主要業績指標(KPI)が下降しているため、製品マネージャー(PM)は新機能の追加を希望しています。多くのブレインストーミングとユーザーインタビューの結果、単なるFizzBuzzだけでは足りないと判断されました。今日の子供たちは新しいゲーム、FizzBuzzFibonacciを求めています。

FizzBuzzFibonacciの規則は以下の通りです:

1から100までの整数を印刷するプログラムを書く:

  • 3の倍数にはFizzを印刷
  • 5の倍数には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の間で全ての数値に対して反復します。
  • フィボナッチ数列を0および1から開始し、それぞれをpreviouscurrentの両方の数値とし初期化します。
  • current の数値が現在の i の反復より少ない間反復します。
  • previouscurrent の数値を加えて next の数値を取得します。
  • previous の数値を current の数値にアップデートします。
  • current の数値を next の数値にアップデートします。
  • 一度 current が与えられた数値 n と等しいかそれ以上になったら、ループを終了します。
  • current の数値が与えられた数値 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_buzzfizz_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
Compiling playground v0.0.1 (/home/bencher)
Finished bench [optimized] target(s) in 4.79s
Running unittests src/main.rs (target/release/deps/game-68f58c96f4025bd4)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-043972c4132076a9)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-e0857103eb02eb56)
bench_play_game time: [20.067 µs 20.107 µs 20.149 µs]
change: [+557.22% +568.69% +577.93%] (p = 0.00 < 0.05)
Performance has regressed.
Found 6 outliers among 100 measurements (6.00%)
4 (4.00%) high mild
2 (2.00%) high severe

おお、すごい!Criterionは、私たちのFizzBuzzとFizzBuzzFibonacciゲームのパフォーマンスの違いを+568.69%と教えてくれました。 あなたの数字は私のものと少し違うでしょう。 しかし、2つのゲームの違いはおそらく5xの範囲内でしょう。 これは私にとっては良い感じです!特に、私たちのゲームに_Fibonacci_という高尚な響きのフィーチャーを追加することによるものです。 子供たちはそれを大好きになるでしょう!

RustにてFizzBuzzFibonacciを展開

我々のゲームは大ヒットです!子供たちは確かにFizzBuzzFibonacciを遊ぶのが大好きです。 それほどに、経営陣から続編を求める声が聞こえてきました。 しかし、これは現代の世界、我々は一度きりの購入ではなく、年間定期収入(ARR)が必要です! 我々のゲームの新たなビジョンは、それがオープンエンドであり、1から100(包含)の間に生息するのではなく、新たなフロンティアへと向かうことです。

Open World FizzBuzzFibonacciのルールは次のとおりです:

以下のように、任意の 正の整数を受け取って印刷するプログラムを書きます:

  • 3の倍数の場合、Fizzを出力する
  • 5の倍数の場合、Buzzを出力する
  • 3と5の両方の倍数の場合、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がパスします。我々の上司たちは大喜びです。 それでは、出荷しましょう! 🚀

終わり


SpongeBob SquarePants Three Weeks Later
This is Fine meme

🐰 … あなたのキャリアの終わりかもしれない?


冗談じゃない!全てが炎上しています!🔥

最初は全てうまく行っているように見えました。 しかし、土曜日の午前2時07分に僕のページャーが鳴った:

📟 あなたのゲームが炎上しています!🔥

ベッドから飛び起き、何が起こっているのかを理解しようとしました。 ログを検索しようと試みましたが、何もかもがクラッシュし続けていて困難でした。 最終的に、問題を見つけました。その子供たち! 彼らは私たちのゲームが大好きで、最大百万までプレイしていました! ひらめきの一瞬で、新たに2つのベンチマークを追加しました:

fn bench_play_game_100(c: &mut Criterion) {
c.bench_function("bench_play_game_100", |b| {
b.iter(|| std::hint::black_box(play_game(100, false)));
});
}
fn bench_play_game_1_000_000(c: &mut Criterion) {
c.bench_function("bench_play_game_1_000_000", |b| {
b.iter(|| std::hint::black_box(play_game(1_000_000, false)));
});
}
  • ゲームを数字一百(100)でプレイするためのマイクロベンチマーク bench_play_game_100
  • ゲームを数字一百万(1_000_000)でプレイするためのマイクロベンチマーク bench_play_game_1_000_000

私がそれを実行したとき、これを得ました:

$ cargo bench
Compiling playground v0.0.1 (/home/bencher)
Finished bench [optimized] target(s) in 4.79s
Running unittests src/main.rs (target/release/deps/game-68f58c96f4025bd4)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-043972c4132076a9)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-e0857103eb02eb56)
bench_play_game time: [20.024 µs 20.058 µs 20.096 µs]
change: [-0.0801% +0.1431% +0.3734%] (p = 0.21 > 0.05)
No change in performance detected.
Found 17 outliers among 100 measurements (17.00%)
9 (9.00%) high mild
8 (8.00%) high severe
bench_play_game_100 time: [403.00 ns 403.57 ns 404.27 ns]
Found 13 outliers among 100 measurements (13.00%)
6 (6.00%) high mild
7 (7.00%) high severe

それを待って…それを待って…

bench_play_game_1_000_000
time: [9.5865 ms 9.5968 ms 9.6087 ms]
Found 16 outliers among 100 measurements (16.00%)
8 (8.00%) high mild
8 (8.00%) high severe

何!403.57 ns x 1,000403,570 ns になるべきで、9,596,800 ns9.5968 ms x 1_000_000 ns/1 ms)にはならないはずです 🤯 私がフィボナッチ数列コードを機能的に正しく実装したにもかかわらず、どこかにパフォーマンスのバグがあるはずです。

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 {} ループを完全に取り除き、 与えられた数値(n)と current の値を単に比較するだけで良いのです🤦

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 の数で初期化します。
  • 与えられた数 n よりも current 数が小さい間、繰り返します。
  • previouscurrentの数を足して nextの数を得ます。
  • previousの数を currentの数に更新します。
  • currentの数を nextの数に更新します。
  • currentが与えられた数 nよりも大きくなれば、ループを退出します。
  • currentの数が与えられた数 nと等しいかどうかを確認し、その結果を返します。

それでは、ベンチマークを再実行し、どうなったか見てみましょう:

$ cargo bench
Compiling playground v0.0.1 (/home/bencher)
Finished bench [optimized] target(s) in 4.79s
Running unittests src/main.rs (target/release/deps/game-68f58c96f4025bd4)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-043972c4132076a9)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-e0857103eb02eb56)
bench_play_game time: [3.1201 µs 3.1772 µs 3.2536 µs]
change: [-84.469% -84.286% -84.016%] (p = 0.00 < 0.05)
Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
1 (1.00%) high mild
4 (4.00%) high severe
bench_play_game_100 time: [24.460 ns 24.555 ns 24.650 ns]
change: [-93.976% -93.950% -93.927%] (p = 0.00 < 0.05)
Performance has improved.
bench_play_game_1_000_000
time: [30.260 ns 30.403 ns 30.564 ns]
change: [-100.000% -100.000% -100.000%] (p = 0.00 < 0.05)
Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) high mild
3 (3.00%) high severe

おお、ワ オ!私たちの bench_play_game ベンチマークは、元のFizzBuzzのときのものと同じくらいに戻ってきました。 そのスコアがもともとどれくらいだったか覚えていられれば良かったのですが、もう3週間も経っています。 私のターミナルの履歴はそこまで遡ることができません。 また、Criterionは最新の結果についてしか比較しません。 しかし、それは近いと思います!

bench_play_game_100 ベンチマークはほぼ10倍下がりました。 -93.950%。 そして bench_play_game_1_000_000 ベンチマークは10,000倍以上下がりました! 9,596,800 ns から 30.403 ns に! 私たちはさえもCriterionの変化のメーターを最大まで振り切りました、それは -100.000% までしか表示しません!

🐰 ねえ、少なくとも私たちはこのパフォーマンスバグを本番環境に進出する前に捕まえた…ああ、そうだった…

CIでパフォーマンスの後退を捕捉する

私のちょっとしたパフォーマンスのバグが原因で我々のゲームが大量の否定的なレビューを受けたことに、エクゼクティブたちは不満を持っていました。 彼らは再びそれを起こさないようにと言い、どうすれば良いのか尋ねると、ただ再びやらないようにと言われるだけでした。 どうすればそれを管理することができるのでしょうか‽

幸運なことに、Bencherという素晴らしいオープンソースツールを見つけました。 超大盤振る舞いの無料枠があるので、私の個人的なプロジェクトではBencherクラウドをただ使うことができます。 そして、仕事では全てが私たちのプライベートクラウド内にある必要があるので、Bencher Self-Hostedを使い始めました。

Bencherには組み込みのアダプターがあり、 そのためCIに簡単に統合することができます。クイックスタートガイドをフォローした後、 私は私のベンチマークを実行し、それらをBencherで追跡することができます。

$ bencher run --project game "cargo bench"
Finished bench [optimized] target(s) in 0.07s
Running unittests src/lib.rs (target/release/deps/game-13f4bad779fbfde4)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/release/deps/game-043972c4132076a9)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running benches/play_game.rs (target/release/deps/play_game-e0857103eb02eb56)
Gnuplot not found, using plotters backend
bench_play_game time: [3.0713 µs 3.0902 µs 3.1132 µs]
Found 16 outliers among 100 measurements (16.00%)
3 (3.00%) high mild
13 (13.00%) high severe
bench_play_game_100 time: [23.938 ns 23.970 ns 24.009 ns]
Found 15 outliers among 100 measurements (15.00%)
5 (5.00%) high mild
10 (10.00%) high severe
bench_play_game_1_000_000
time: [30.004 ns 30.127 ns 30.279 ns]
Found 5 outliers among 100 measurements (5.00%)
1 (1.00%) high mild
4 (4.00%) high severe
Bencher New Report:
...
View results:
- bench_play_game (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=077449e5-5b45-4c00-bdfb-3a277413180d&start_time=1697224006000&end_time=1699816009000&upper_boundary=true
- bench_play_game_100 (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=96508869-4fa2-44ac-8e60-b635b83a17b7&start_time=1697224006000&end_time=1699816009000&upper_boundary=true
- bench_play_game_1_000_000 (Latency): https://bencher.dev/console/projects/game/perf?measures=52507e04-ffd9-4021-b141-7d4b9f1e9194&branches=3a27b3ce-225c-4076-af7c-75adbc34ef9a&testbeds=bc05ed88-74c1-430d-b96a-5394fdd18bb0&benchmarks=ff014217-4570-42ea-8813-6ed0284500a4&start_time=1697224006000&end_time=1699816009000&upper_boundary=true

この素敵なウサギが私にくれた便利なタイムマシンを使って、私たちがずっとBencherを使っていたらどうなっていたかを時間を遡って再現しました。 最初にバギーなFizzBuzzFibonacciの実装をプッシュしたところを見ることができます。 私のプルリクエストに対するコメントとしてCIで直ちに失敗が出ました。 その同じ日に、私はその無駄な、余分なループを取り除くことでパフォーマンスのバグを修正しました。 火事はありません。ただ幸せなユーザーたちだけです。

Bencher: 連続ベンチマーキング

🐰 Bencher

Bencherは、連続ベンチマーキングツールのスイートです。 パフォーマンスの後退があなたのユーザーに影響を与えたことはありますか? Bencherなら、それが起こるのを防げた可能性があります。 Bencherは、パフォーマンスの低下を_productionに到達する_前に検出し、防止することを可能にします。

  • 実行: お気に入りのベンチマーキングツールを使用してベンチマークをローカルまたはCIで実行します。 bencher CLIは単にあなたの既存のベンチマークハーネスをラップし、その結果を保存します。
  • 追跡: ベンチマークの結果を時間と共に追跡します。ソースブランチ、テストベッド、測定基準に基づいてBencherのWebコンソールを使用して結果を監視、クエリ、グラフ化します。
  • キャッチ: CIでパフォーマンスの後退をキャッチします。Bencherは最先端のカスタマイズ可能な分析を使用して、パフォーマンスの後退がProductionに到達する前にそれを検出します。

機能の後退を防ぐためにユニットテストがCIで実行されるのと同じ理由で、Bencherを使用してCIでベンチマークを実行してパフォーマンスの後退を防ぐべきです。パフォーマンスのバグはバグです!

CIでパフォーマンスの回帰を捉えるのを開始してください - Bencher Cloudを無料で試す

🤖 このドキュメントは OpenAI GPT-4 によって自動的に生成されました。 正確ではない可能性があり、間違いが含まれている可能性があります。 エラーを見つけた場合は、GitHub で問題を開いてください。.