【Go】 machoは「マッチョ」ではない
本記事は、Go 4 Advent Calendar 2020の25日目の記事です。
macho package を眺めたことはあるでしょうか。こちらはmachoなので呼び方を表題でピンとこない方は要注意かもしれません。
背景: Go 1.15 on macOSでperformance regressionというIssue
同僚の@budougumi0617さんが『私がGoのソースコードを読むときのTips』にて、Goコードリーディング会を継続して開催していることを紹介しておりました。
それに対して、弊社ではBASEメンバーと合同で定期的にGoコードリーディングパーティを実施しています。
ひとりでは詰まってしまうようなこともみんなで見ていると解決の糸口がみつかったり、他のメンバーのエディタ捌きを盗むことができます。
12月25日本日、@dice_zuさんをゲストに迎えて testing パッケージ内のキャッシュの挙動について読んでいたりしたのですが、雑談の中で 「vimのslackで昔@itchynyさんがGo 1.15になった際にmacOSでちょっと遅くなった気がするという話をされてすごいと思った」っていう話題が上がり、「このIssueですか〜」というissueをとりあげた。
はて、このIssueはどうやって解決しているのかな?とよんでいったのが始まりです。
248719: [release-branch.go1.15] cmd/link: link dynamic library automatically
表題のIssueです。
このIssueの差分は次のようなif文です。
if lib != "" && ctxt.HeadType == objabi.Hdarwin {
machoadddynlib(lib, ctxt.LinkMode)
}
現在のバージョンのコードではちょっと変わっていて if-else での処理となっています。
if local == "_" && remote == "_" {
// allow #pragma dynimport _ _ "foo.so"
// to force a link of foo.so.
havedynamic = 1
if ctxt.HeadType == objabi.Hdarwin {
machoadddynlib(lib, ctxt.LinkMode)
} else {
dynlib = append(dynlib, lib)
}
continue
}
内容としては、objabi.Hdarwin
の場合、つまりMacOSに該当するようなオペレーションシステムの場合はmachoadddynlib()
関数を呼び、そうではない場合はdynamic importを行なうというものです。
当該Issueでの指摘は、
Remove the unconditional imports in the runtime. Now,
Security.framework and CoreFoundation.framework are only linked
when the x509 package is imported (or otherwise specified).
とある通り、不要なimportを取り除くという趣旨ですので、Security.frameworkやCoreFoundation.frameworkは取り除くという振る舞いを実装したと推測されます。
これは、同僚の@glassemonkeyさんよりSwiftではよくCoreFoundation使うよねという情報提供があり、それに該当する奴らなのねっていうことが会の中では話に上がりました。
このPRのsrc/runtime/sys_darwin.go
でもその旨が理解できるコメントの削除が確認できます。
machoadddynlib
ここで出てくるmacho
とはなんぞやという話題です。結論から言うと「マッチョ」ではないです(そらそうよ)。
これは、「Mach-O(マーク・オー)」です。macOS標準のバイナルファイルフォーマットとして採用されているファイルフォーマットです。
今回のIssueはMacOSに関するruntimeについてですので、「Mach-O」の関連性はしっかり点と点で繋がります。
パッケージとしては、debug/machoという強そうなパッケージがあります。
machoは「マーク・オー」
マーク・オーだよ
余談: testingパッケージとgocachetest
冒頭でTestingパッケージについても読んだ話を書きました。@budougumi0617さんがこんな記事ありますよね〜と『Go ランタイムのデバッグをサポートする環境変数 by @mattnさん』をシェアしてくださりました。
gocachetest
という設定はキャッシュされたテスト結果を再利用するかどうかの詳細を表示するオプションです。これが実際内部ではどういう扱いについてちょっとだけ読みました(これ自体はサイドメニュー的な内容ですが)。
まず、gochachetest
を有効にすると、以下のコードでDebugTest
がtrueになります。
func initEnv() {
verify = false
debugHash = false
debug := strings.Split(os.Getenv("GODEBUG"), ",")
for _, f := range debug {
if f == "gocacheverify=1" {
verify = true
}
if f == "gocachehash=1" {
debugHash = true
}
if f == "gocachetest=1" {
DebugTest = true
}
}
}
以降、テストを実行する際に、DebugTest
をみてlogを出す分岐のコードが散りばめられています。
func (c *runCache) tryCacheWithID(b *work.Builder, a *work.Action, id string) bool {
if len(pkgArgs) == 0 {
// Caching does not apply to "go test",
// only to "go test foo" (including "go test .").
if cache.DebugTest {
fmt.Fprintf(os.Stderr, "testcache: caching disabled in local directory mode\n")
}
c.disableCache = true
return false
}
それではGopherのみなさんメリークリスマス!
Discussion