GungraunでRustコードをベンチマークする方法

Everett Pompeii

Everett Pompeii


ベンチマークとは?

ベンチマークとは、コードの性能をテストして、その処理速度(レイテンシ)や処理量(スループット)を確認することを指します。 この、ソフトウェア開発において見落とされがちなステップは、高速で高性能なコードを作成および維持するために重要です。 ベンチマークは開発者がコードがさまざまな作業負荷や条件下でどれだけうまく動作するかを理解するために必要な指標を提供します。 機能のリグレッションを防ぐためにユニットテストや統合テストを書くのと同様に、 パフォーマンスのリグレッションを防ぐためにもベンチマークを書くべきです。 パフォーマンスのバグもバグです!

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 を出力します。
  • それ以外の場合は、単に数字を出力します。

ステップバイステップで進める

このステップバイステップのチュートリアルを進めるには、まず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

これ以上進む前に、マイクロベンチマークとマクロベンチマークの違いについて話すことが重要です。

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

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

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

Rustにおけるベンチマーク

Rustでのベンチマークにおいて人気のあるオプションは、libtest benchCriterion、そしてIaiです。

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

Rustエコシステム内で最も人気のあるベンチマークハーネスはCriterionです。 これは安定版とnightly版の両方のRustコンパイラリリースで動作し、Rustコミュニティ内で事実上の標準として定着しています。 Criterionはlibtest benchと比べてはるかに多機能です。

Criterionの実験的な代替としてIaiがありますが、これはCriterionの作成者によって開発されたものです。 しかし、壁時計時間の代わりに命令カウントを使用します:CPU命令、L1アクセス、L2アクセス、RAMアクセスです。 これにより、これらのメトリクスはラン間でほぼ同一であるべきなので、シングルショットベンチマークが可能になります。

4つすべてがBencherでサポートされています。では、なぜGungraun(Iai-Callgrindの改名された後継)を選ぶのでしょうか? Gungraunは実時間の代わりに命令カウントを使用します。 これにより、継続的ベンチマーキング、つまりCIでのベンチマーキングに最適です。 特に共有ランナーを使用している場合、継続的ベンチマーキングにはGungraunの使用をお勧めします。 Gungraunは積極的にメンテナンスされており、包括的なオンラインドキュメントがあり、 長期プロジェクトにとって信頼できる選択肢です。 Gungraunは、あなたが本当に気にかけていることのプロキシのみを測定することを理解することが重要です。 1,000命令から2,000命令に増えると、アプリケーションのレイテンシは2倍になりますか? そうかもしれないし、そうでないかもしれません。 このため、命令カウントベースのベンチマークと並行して、実時間ベースのベンチマークも実行することが有用な場合があります。

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_gamelib.rsで作成したgameクレートからplay_gameをインポートします。
  • main:プログラムのメインエントリーポイントで、1から100まで(含む)の数字を反復し、各数字に対してprinttrueに設定してplay_gameを呼び出します。

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クレートからplay_game関数をインポートします。
  • #[library_benchmark]属性を使用して、bench_play_gameというライブラリベンチマーク関数を作成します。
  • 1から100までループし、printfalseに設定してplay_gameを呼び出します。
  • bench_play_gameベンチマークを含むbench_play_game_groupというライブラリベンチマークグループを作成します。
  • main!マクロを使用してベンチマークグループを実行します。

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

Cargo.tomlファイルの_末尾_に以下を追加します:

[dev-dependencies]
gungraun = "0.18.0"
[[bench]]
name = "play_game"
harness = false
[profile.bench]
debug = true
  • gungraun:パフォーマンステストにのみ使用するため、gungraunを開発依存関係として追加します。
  • benchplay_gameをベンチマークとして登録し、Gungraunをベンチマーキングハーネスとして使用するためharnessfalseに設定します。
  • 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の規則は以下の通りです:

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
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ゲームの推定サイクルの差を教えてくれます。 あなたの数値は私のものと少し異なるでしょう。 しかし、2つのゲームの差はおそらく10-15xの範囲です。 それは良さそうです!特に_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つのベンチマークを追加しました:

ここが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!に追加します。

素晴らしい!1つのベンチマーク関数で、複数のテストケース!

実行したところ、以下の結果を得ました:

$ 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ベンチマークは1回だけ実行されます。しかし待ってください、最初のベンチマーク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,0007,448,000推定サイクルであるべきなのに、183,930,692推定サイクルです 🤯 フィボナッチ数列のコードは機能的に正しいのに、どこかにパフォーマンスバグがあるに違いありません。

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
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%の削減です!

🐰 ねえ、少なくとも本番環境に到達する前にこのパフォーマンスバグを見つけました…ああ、そうでした。気にしないで…

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

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

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