いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-547 (testing.B.Loop())(Go 1.24で追加, 新しいベンチマークのループ判定, benchmark, b.N)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。忘れないようにここにメモメモ。。。

Go 1.24で testing.B.Loop() が追加されました。

これまでは、testing.B.N を使用してループで回していたものが、今後は

for b.Loop() {
}

と書けるようになります。簡易に書けるようになっただけではなく以下のような効能もあります。

  • ベンチマーク関数が1回だけ実行されるようになる
  • b.Loop() のループ内は決して最適化されないようになる
  • b.Loop() の開始と終了時に自動的にタイマーの管理が行われるので b.ResetTimer() の呼び出しが不要になる

より正確にベンチマークを測定できるようになるので、使わない手は無いです。

サンプル

a_test.go

package main

import (
    "testing"
    "time"
)

func setup() {
    time.Sleep(1 * time.Second)
}

func slowFn() {
    time.Sleep(100 * time.Millisecond)
}

func Benchmark_OldStyleLoop_NoResetTimer(b *testing.B) {
    setup()

    for range b.N {
        slowFn()
    }

    b.Logf("N: %d", b.N)
}

func Benchmark_OldStyleLoop_WithResetTimer(b *testing.B) {
    setup()
    b.ResetTimer() // セットアップの時間を含めないようここでタイマーをリセット

    for range b.N {
        slowFn()
    }

    b.Logf("N: %d", b.N)
}

func Benchmark_NewStyleLoop(b *testing.B) {
    //
    // Go 1.24 にて、testing.B.Loop が追加された。
    // シグネチャは以下の様になっている。
    //
    //     func (b *B) Loop() bool
    //
    // 以前までは、testing.B.N を使用してループすることで
    // ベンチマークを計測していたが、今後は testing.B.Loop を
    // 使用してベンチマークすることが推奨されるようになる。
    //
    // testing.B.Loop を使用することで以下の恩恵がある。
    //
    //     - ベンチマーク関数が1回のみ実行されるようになる
    //         - セットアップやクリーンアップの回数が減少
    //     - b.Loop を利用しているループはコンパイラの最適化がかからないようになる
    //     - b.Loop の開始と終了時にタイマーが自動管理されるようになる
    //         - b.ResetTimer() の呼び出しが不要となる
    //         - セットアップコードがベンチマーク時間に含まれなくなる
    //
    // # REFERENCES
    //     - https://pkg.go.dev/testing@go1.24.0#hdr-b_N_style_benchmarks
    //     - https://pkg.go.dev/testing@go1.24.0#B.Loop
    //     - https://antonz.org/go-1-24/#benchmark-loop
    //     - https://www.bytesizego.com/blog/go-124-new-benchmark-function
    //

    // セットアップコードは1度だけ呼び出される
    setup()

    // b.Loop を利用する場合、内部でタイマーの自動管理が行われるので
    // b.ResetTimer() の呼び出しが不要となる
    //
    // - b.Loop の開始時に b.ResetTimer() される
    // - b.Loop が false を返したときに b.StopTimer() される
    for b.Loop() {
        //
        // for b.Loop() { ... } の中は決して最適化が行われない
        //
        slowFn()
    }

    b.Logf("N: %d", b.N)
}

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - go test -bench . -benchmem

shell

実行すると以下のように出力されます。

$ task
task: [default] go test -bench . -benchmem
goos: linux
goarch: amd64
pkg: github.com/devlights/try-golang/examples/testing/go124_benchmark_loop
cpu: AMD EPYC 7B13
Benchmark_OldStyleLoop_NoResetTimer-16                 1        1100764848 ns/op            3496 B/op         17 allocs/op
--- BENCH: Benchmark_OldStyleLoop_NoResetTimer-16
    a_test.go:23: N: 1
Benchmark_OldStyleLoop_WithResetTimer-16              10         100213149 ns/op             336 B/op          1 allocs/op
--- BENCH: Benchmark_OldStyleLoop_WithResetTimer-16
    a_test.go:34: N: 1
    a_test.go:34: N: 10
Benchmark_NewStyleLoop-16                             10         100190719 ns/op               0 B/op          0 allocs/op
--- BENCH: Benchmark_NewStyleLoop-16
    a_test.go:79: N: 10
PASS
ok      github.com/devlights/try-golang/examples/testing/go124_benchmark_loop   6.213s

ちゃんと一度だけの呼び出しになっていますね。

参考情報

go.dev

Go 1.24's New Benchmark Function; a better way to benchmark

Go 1.24 interactive tour

testing package - testing - Go Packages

testing package - testing - Go Packages

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。