🧪

【Go】Goのテストに入門してみた! ~ベンチマーク編~

に公開

はじめに

前回は「カバレッジ」を見ていきました。
今回は「ベンチマーク」に入門していきます!

前回の記事はこちら!
https://zenn.dev/tmyhrn/articles/93c20bcd2c3442

この記事でわかること

  • ベンチマークとは何か
  • Goのベンチマークテストの書き方
  • ベンチマークテスト結果の見方

ベンチマークとは?

ベンチマークとは、プログラムの処理性能(速度やメモリ使用量)を数値的に測定する方法です。
Goでは、testingパッケージ を使って、関数の実行時間やメモリ消費量、アロケーションの頻度を自動で計測できます。

ベンチマークを使うことで、

  • 「どの実装がより速いのか?」
  • 「どれだけメモリを使っているのか?」
  • 「処理効率は改善されているか?」

といった実用的な指標が得られ、パフォーマンスチューニングの根拠になります。

ベンチマークをとるための実装

今回は、以前の記事でもやった「文字列結合」を例にベンチマークを取ります。

main_test.go
package main

import (
    "fmt"
    "strings"
    "testing"
)

// 10万個の文字列を作成する
const N = 100000

// "Hello"という文字列が指定した分だけ配列に格納される
func generateStrings() []string {
    s := make([]string, N)
    b.ResetTimer()
    for i := 0; i < N; i++ {
        s[i] = "Hello"
    }
    return s
}

// +演算子を用いた文字列結合
func BenchmarkStringConcatPlus(b *testing.B) {
    s := generateStrings()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        result := ""
        for _, v := range s {
            result += v + " "
        }
        _ = result
    }
}

// fmt.Sprintfを用いた文字列結合
func BenchmarkStringConcatSprintf(b *testing.B) {
    s := generateStrings()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        result := ""
        for _, v := range s {
            result = fmt.Sprintf("%s %s", result, v)
        }
        _ = result
    }
}

// stringsパッケージのJoin関数を用いた文字列結合
func BenchmarkStringConcatJoin(b *testing.B) {
    s := generateStrings()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = strings.Join(s, " ")
    }
}

// stringsパッケージのBuilderを用いた文字列結合
func BenchmarkStringConcatBuilder(b *testing.B) {
    s := generateStrings()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        for _, v := range s {
            sb.WriteString(v)
            sb.WriteString(" ")
        }
        _ = sb.String()
    }
}

1. 関数名をBenchmarkからはじめる

ベンチマークをとるときは、必ず関数名を Bechmark からはじめるようにします。

2. 引数にはb *testing.Bを渡す

ベンチマーク関数の引数には、b *testing.B を渡します。

3. ループ回数にはb.Nを使う

ベンチマーク関数のループ回数には、b.N を 使います。
Goのベンチマークは、一定の精度を保つために、処理時間に応じて b.N を動的に調整します。

  • 処理が早い場合 → 試行回数を多めにして安定した計測を行う
  • 処理が遅い場合 → 少ない回数でも十分なデータが取れるので、試行回数を少なくする

4. b.ResetTimer()を使う

ベンチマーク関数の中で初期化処理を行っていた場合、それはベンチマークの対象外なので、b.ResetTimer() を使ってリセットを行います。

ベンチマークをとってみよう

以下のコマンドでベンチマークをとることができます。

ターミナル
# go test -benchmem -bench .
## -benchmem:ベンチマーク時のメモリ割り当て情報を出力
## -bench . :実行するベンチマーク関数を指定(.は全指定を表す)

goos: darwin
goarch: arm64
pkg: go-test-practice
cpu: Apple M1
BenchmarkStringConcatPlus-8                    1        4679751291 ns/op        30394878864 B/op          100174 allocs/op
BenchmarkStringConcatSprintf-8                 1        4964243791 ns/op        60439051232 B/op          397670 allocs/op
BenchmarkStringConcatJoin-8                 1533            755007 ns/op          607255 B/op          1 allocs/op
BenchmarkStringConcatBuilder-8              1398            856217 ns/op         3243900 B/op         31 allocs/op
PASS
ok      go-test-practice        13.496s

1. goos

使用しているOSを指します。
この例では、Darwin(Appleが開発するOSで、macOSの基礎部分)が該当します。

2. goarch

使用しているアーキテクチャを指します。
この例では、Apple Siliconのarm64が該当します。

3. pkg

テストを行ったパッケージ名を指します。
ここではgo-test-practiceが該当します。

4. cpu

使用しているCPU情報を指します。
この例では Apple M1が該当します。

5. ベンチマーク結果

項目 意味
BenchmarkStringConcatPlus-8 ベンチマーク関数の名前。末尾の-8並列性(GOMAXPROCSの値) を表します。この例では8スレッド並列で計測されたことを示しています。
1 実行されたベンチマークの回数(b.N の値)。ここでは1回しか実行されなかったため、非常に重い処理であることがわかります。
4679751291 ns/op 1回の実行にかかった平均時間(ナノ秒)。この例では約4.67 秒です。
30394878864 B/op 1回の操作あたりのメモリ使用量(バイト単位)。この例では 約30GB のメモリが使われたことを示しており、かなり非効率な処理と言えます。
100174 allocs/op 1回の操作あたりに発生したアロケーション回数。ここでは 10万回以上のアロケーションが発生しています。

つまり、以下の結果が効率的な処理をしていると言うことができます。

  • 1回の実行にかかった平均時間が短い
    • 高速で処理が完了しているので、CPU効率が良い
  • 1回の操作あたりのメモリ使用量が小さい
    • メモリ使用量が少なく、キャッシュに収まりやすくなり、GCの負荷も減る
  • アロケーション回数が少ない
    • メモリ確保の頻度が少ないのでGCが発生しにくくなり、処理が安定する

まとめ

今回は「ベンチマーク」について学びました。

ベンチマークとり、それぞれの処理時間・メモリ使用量・アロケーション数を比較することで、効率的な実装が何かを数値で判断できます。
パフォーマンスが重要な処理において、ベンチマークは「なんとなく速そう」ではなく、「実際に速い」かどうかを証明する有効な手段です。

次回は「テーブル駆動ベンチマークテスト」に入門します!

参考

https://pkg.go.dev/testing#hdr-Benchmarks
https://blog.cloudsmith.co.jp/2024/03/444/
https://zenn.dev/canary_techblog/articles/2c11335be49f84
https://ja.wikipedia.org/wiki/Darwin_(オペレーティングシステム)

Discussion