$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

Goでは-race付きでテストするとビルドキャッシュが完全に別になる

gotesplitAdd -race to list when it is specified for test optionsというPullRequestを投げたのだが、この背景を書いておこうと思う。

まずGoでは-raceオプションについて、以下のような挙動を起こす。

  • -raceフラグをつけるとruntime/raceがおそらく一番依存の深いところに追加されてコンパイルされるっぽい?
  • そのため、go testgo test -raceはビルドキャッシュが全く異なる。つまりgo testのビルドキャッシュはgo test -raceのビルドに全く使われないし、その逆も同じである

gotesplitの以前のバージョンでは、-raceオプションを付けても、テスト分割のためのテスト一覧を取得するgo test -listコマンドのオプションには-raceをつけていなかった。このためgotesplitを使って-race付きのテストをしようとすると、テスト一覧を取得する時に一度-raceなしのフルコンパイルが走り、実際のテスト実行で-raceありのフルコンパイルが走っていた。フルコンパイルが2回走っていたので、想定以上の実行時間がかかってしまっていた。

上記のPullRequestによって、-raceがついていてもコンパイルは一度で済むようになったため高速化した。

挙動の調査ログ

https://github.com/shibayu36/go-playground のレポジトリを使って挙動を調査したログを貼っておく。

まずgo clean -cacheを使って完全にビルドキャッシュを飛ばした時、ビルドキャッシュのディレクトリのサイズはほぼ空になる。

$ go clean -cache
$ du -sh /Users/shibayu36/Library/Caches/go-build
 12K    /Users/shibayu36/Library/Caches/go-build

その後、-raceなしで./playgroundのテストを実行してみる。この時166packageのコンパイルが走り、ビルドキャッシュが91Mになる様子が観察できる。

# -x付きで実際にコンパイルされるpackageを出す
$ go test -count 1 -x ./playground 2>log.txt
# コンパイルされたpackage数を出すoneliner
$ grep "/compile " log.txt | awk -F'-p ' '{split($2, a, " "); print a[1]}' | wc -l
     166
# ビルドキャッシュのサイズ
$ du -sh /Users/shibayu36/Library/Caches/go-build
 91M    /Users/shibayu36/Library/Caches/go-build

もちろんもう一度同じコマンドを実行しても、ビルドキャッシュが利用されてコンパイルは一切行わない。

$ go test -count 1 -x ./playground 2>log-retest.txt
$ grep "/compile " log-retest.txt | awk -F'-p ' '{split($2, a, " "); print a[1]}' | wc -l
       0
$ du -sh /Users/shibayu36/Library/Caches/go-build
 91M    /Users/shibayu36/Library/Caches/go-build

このビルドキャッシュが存在する状態で-race付きでテストすると、167packageのコンパイルが走ることがわかる。167packageというのは、ビルドキャッシュなしの状態で-raceなしのコンパイルしたpackage数+1の数字である。またビルドキャッシュのサイズが2倍以上に膨らみ、188Mになっている。

$ go test -race -count 1 -x ./playground 2>log-race.txt
$ grep "/compile " log-race.txt | awk -F'-p ' '{split($2, a, " "); print a[1]}' | wc -l
     167
$ du -sh /Users/shibayu36/Library/Caches/go-build
188M    /Users/shibayu36/Library/Caches/go-build

さらにもう一度同じコマンドを実行しても、-race用のビルドキャッシュが利用されてコンパイルは行われないように見える。

$ go test -race -count 1 -x ./playground 2>log-race-retest.txt
$ grep "/compile " log-race-retest.txt | awk -F'-p ' '{split($2, a, " "); print a[1]}' | wc -l
       0
$ du -sh /Users/shibayu36/Library/Caches/go-build
188M    /Users/shibayu36/Library/Caches/go-build

さらに-race付きだと何のpackageが1つ増えているかを調査するために次のコマンドを実行する。するとruntime/raceが追加でコンパイルされていることが分かる。

diff -u <(grep "/compile " log.txt | awk -F'-p ' '{split($2, a, " "); print a[1]}' | sort) <(grep "/compile " log-race.txt | awk -F'-p ' '{split($2, a, " "); print a[1]}' | sort)
--- /dev/fd/11  2023-09-26 09:43:17
+++ /dev/fd/12  2023-09-26 09:43:17
@@ -133,6 +133,7 @@
 runtime/internal/math
 runtime/internal/sys
 runtime/pprof
+runtime/race
 runtime/trace
 sort
 strconv

ということでraceオプションの有無で、完全にビルドキャッシュが分かれている挙動を確認できました。