Share
Embed
Transcript
13 slides
-
1. 登場した背景 - なぜtesting.B.Loop が必要だったのか 2. b.Loop() の実装 - 実際のコード
3. メリット - testing.B.Loop の利点 4. まとめ - おわりに 目次 2 -
従来のベンチマーク func BenchmarkTraditional(b *testing.B) { // セットアップコード data := prepareExpensiveData(10000)
// タイマーをリセットする必要がある b.ResetTimer() // b.Nを使ってループ for i := 0; i < b.N; i++ { result := processData(data) } } 登場した背景 3 -
従来のベンチマーク(b.N )の問題点 (issue #61515 より) b.N の誤用が多い 多くのベンチマークがb.N の使用を忘れている(rsc/badbench.md) イテレーションカウントではなく入力サイズとして使われることも
タイマーの管理 ResetTimer を適切な場所で呼ばないと、ベンチマーク結果が不正確になる。 コンパイラ最適化による影響 b.N のループ内でコンパイラが最適化(インライン展開)を行うと、測定結果が意図しない ものになる可能性がある。 登場した背景 4 -
コンパイラ最適化による影響? インライン展開とは インライン展開(インラインてんかい、英: inline expansion または 英: inlining ) とは、コンパイラによる最適化手法の1
つで、関数を呼び出す側に呼び出される関 数のコードを展開し、関数への制御転送をしないようにする手法。これにより関 数呼び出しに伴うオーバーヘッドを削減する。特に小さくて頻繁に呼ばれる関数 では効果的であり、呼び出し側にそのコードを展開することで定数畳み込みなど のさらなる最適化を施せる可能性が生じる。 (Wikipedia インライン展開 より引 用) 「関数を呼び出す側に関数のコードを展開することで、オーバーヘッドを削減する。 」こと らしい。 登場した背景 5 -
コンパイラ最適化による影響? インライン展開される条件 Go Wiki: Compiler And Runtime Optimizations より function
should be simple enough, the number of AST nodes must less than the budget (80); function doesn't contain complex things like closures, defer, recover, select, etc; function isn't prefixed by go:noinline; function isn't prefixed by go:uintptrescapes, since the escape information will be lost during inlining; function has body; etc. 登場した背景 6 -
func BenchmarkWithLoop(b *testing.B) { // セットアップコード data := prepareExpensiveData(10000) //
タイマーは自動的に管理される for b.Loop() { processData(data) } } Go1.24 以降で可能なベンチマークコード 7 -
func (b *B) Loop() bool { if b.loopN != 0
&& b.loopN < b.N { b.loopN++ return true } return b.loopSlowPath() } src/testing/benchmark.go b.Loop() の実装 8 -
func (b *B) loopSlowPath() bool { if b.loopN == 0
{ b.N = 1 b.loopN = 1 b.ResetTimer() // 初回の実行時のみタイマーをリセット return true } if b.benchTime.n > 0 { if b.N < b.benchTime.n { b.N = b.benchTime.n b.loopN++ return true } b.StopTimer() // ループ回数がb.Nを超えたらタイマーを停止 return false } return b.stopOrScaleBLoop() } 9 -
compile 側で for b.Loop() を検出して制御する 612043: cmd/compile: keep variables alive
in testing.B.Loop loops | https://go- review.googlesource.com/c/go/+/612043 どのようにしてインライン展開を防ぐのか? 10 -
func (s *inlClosureState) mark(n ir.Node) ir.Node { (--中略--) if isTestingBLoop(n)
{ // No inlining nor devirtualization performed on b.Loop body if base.Flag.LowerM > 1 { fmt.Printf("%v: skip inlining within testing.B.loop for %v\n", ir.Line(n), n) } // We still want to explore inlining opportunities in other parts of ForStmt. nFor, _ := n.(*ir.ForStmt) nForInit := nFor.Init() for i, x := range nForInit { if x != nil { nForInit[i] = s.mark(x) } } if nFor.Cond != nil { nFor.Cond = s.mark(nFor.Cond) } if nFor.Post != nil { nFor.Post = s.mark(nFor.Post) } return n } (--以下略--) src/cmd/compile/internal/inline/interleaved/interleaved.go どのようにしてインライン展開を防ぐのか? 11 -
1. b.N の誤用が減る 2. タイマーの自動管理 b.ResetTimer() の記述が不要になる ループ開始時にタイマーの自動リセット、終了時に自動停止 3. コンパイラ最適化の抑制
for b.Loop() の中ではインライン展開が抑制される testing.B.Loopのメリット 12 -
・Go 1.24では新しいベンチマーク方法としてtesting.B.Loopが導入 タイマーが自動的に管理( b.ResetTimer() の記述が完全に不要に) コンパイラ最適化から保護 ・シンプルにfor b.Loop() を使うのが良さそう 既存コードも
for i := 0; i < b.N; i++ → for b.Loop() まとめ 13