Goには標準でベンチマークを取る機能 がある。 次のように go test のオプションとして実行できる。
go test -bench .
また、標準には入ってないが複数のベンチ結果を比較してくれる benchstat というツールもある。 go installですぐに導入でき、2つのベンチ結果のファイルを引数で渡すだけなので直感的に使えてとてもいい。
$ go install golang.org/x/perf/cmd/benchstat@latest $ go test -bench . -count 10 > old.txt $ go test -bench . -count 10 > new.txt $ benchstat old.txt new.txt name old time/op new time/op delta Test-8 11.0ms ± 2% 10.9ms ± 2% ~ (p=0.436 n=10+10)
Pull Request提出時に自動的にベンチを取りたい
新しい実装を入れるたびに手元で古い実装とのベンチマーク結果を比較するのは面倒。 そこで、Pull Request提出時に自動的にベンチマーク結果を比較してくれると便利そうだと考えて、試してみた。
CIでベンチマーク結果の比較をする
CI上で次のような流れで benchstat を実行し、結果をPull Requestのコメントに書くとよさそう。
- Pull RequestのベースとHEADブランチでそれぞれベンチマークを実行
- 2つのベンチ結果をbenchstatで比較
- 比較結果をPull Requestのコメントに書き込む
これを最低限実現するGitHub ActionsのYAMLは次のようになる。 (この例ではGitHub Actionsだけど、他のCIでも似たようなことは実現できるはず)。
on: pull_request: paths: - '**.go' jobs: benchstat: runs-on: ubuntu-20.04 permissions: pull-requests: write contents: read steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - uses: actions/setup-go@v2 with: go-version: '^1.16' - name: Run benchstat id: benchstat env: BENCH_CMD: go test -bench . -count 10 run: | go install golang.org/x/perf/cmd/benchstat@latest git checkout origin/${GITHUB_BASE_REF} ${{ env.BENCH_CMD }} > old.txt git checkout origin/${GITHUB_HEAD_REF} ${{ env.BENCH_CMD }} > new.txt benchstat old.txt new.txt > benchstat.txt - uses: actions/github-script@v4 with: script: | const fs = require('fs').promises; const result = await fs.readFile("benchstat.txt"); github.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: "```\n" + result + "```", })
基本的に流れをそのままYAMLにしただけだが、いくつか注意点があった。
Goのファイルが変更されたときのみ実行する
たとえばドキュメントの変更のみなのにGoのベンチマークを取っても意味がないので、paths:
フィルターでGoのファイルに変更があった場合のみにする。
actions/checkout
のときは fetch-depth: 0
を指定する
デフォルトのactions/checkout の設定だと、PRを出したブランチしか取得してくれないので、fetch-depth: 0
を指定して他のブランチも取得しておく。
これでPRのベースブランチとの比較ができるようになる。
PRにコメントをつけるには actions/github-script
が便利
PRにコメントを付ける実装はactions/github-scriptを使った。 これを使うとnode.jsでGitHub APIを直接叩くようなコードが記述できるので、curlなどでコマンドを組み立てるよりも読みやすくて便利だった。 Node.jsのコードが書けるのでベンチ結果のファイルを読み込むのも fs パッケージを使ってできたりする。
同じPRでpushする度にベンチマークが実行される
タイトル通り、一度Pull Requestを出した後に何度も追加でpushするとその度に結果がコメントされるので 場合によってはうるさいかもしれない…。
PR上で /bench
など特定のコメントを送信したときだけベンチマークを実行するとか、に改造したらもっと良い体験になるかも。