[インデックス 14327] ファイルの概要
このコミットは、Go言語のビルドコマンド (cmd/go
) におけるCgo関連の処理を最適化するものです。具体的には、gcc -print-libgcc-file-name
コマンドの呼び出しを一度だけにし、その結果をキャッシュすることで、ビルド時間の短縮を図っています。
コミット
commit 3e80f9ce7bd102ff5b8ddb1c02bb1a5af9602452
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Wed Nov 7 05:09:54 2012 +0800
cmd/go: invoke gcc -print-libgcc-file-name only once
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6741051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3e80f9ce7bd102ff5b8ddb1c02bb1a5af9602452
元コミット内容
cmd/go: invoke gcc -print-libgcc-file-name only once
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6741051
変更の背景
Go言語のCgo機能は、GoコードからC言語のコードを呼び出すことを可能にします。この際、Cgoは内部的にCコンパイラ(通常はGCC)を使用してCコードをコンパイルし、Goのランタイムとリンクします。このプロセスにおいて、Cコンパイラが提供する標準ライブラリの一つである libgcc
のパスを特定する必要がありました。
以前の実装では、libgcc
のパスを特定するために gcc -print-libgcc-file-name
コマンドが複数回呼び出される可能性がありました。このコマンドは、GCCが使用する libgcc
のフルパスを出力するもので、その実行には一定のオーバーヘッドが伴います。特に、Cgoを使用するパッケージが複数ある場合や、ビルドプロセス中にこの情報が繰り返し必要とされる場合、このオーバーヘッドが累積し、ビルド時間の増加につながっていました。
このコミットの目的は、gcc -print-libgcc-file-name
の呼び出しを一度に制限し、その結果をキャッシュすることで、Cgoを使用するGoプログラムのビルドパフォーマンスを向上させることにあります。
前提知識の解説
- Cgo: Go言語の機能の一つで、GoプログラムからC言語の関数を呼び出したり、C言語のデータ型を扱ったりすることを可能にします。Cgoを使用すると、既存のCライブラリをGoプロジェクトに統合したり、Goでは実装が難しい低レベルな処理をCで記述したりできます。Cgoは、Goのビルドプロセス中にCコンパイラ(GCCなど)を呼び出してCコードをコンパイルし、Goのオブジェクトファイルとリンクします。
- GCC (GNU Compiler Collection): C、C++、Goなど、多くのプログラミング言語をサポートするコンパイラ群です。GoのCgo機能は、通常、バックエンドとしてGCCを利用します。
libgcc
: GCCが提供するランタイムサポートライブラリです。整数演算のオーバーフローチェック、浮動小数点演算のソフトウェアエミュレーション、例外処理など、コンパイラが生成するコードが依存する低レベルなヘルパー関数が含まれています。C/C++プログラムがコンパイルされる際に、暗黙的にリンクされることがあります。gcc -print-libgcc-file-name
: このGCCコマンドラインオプションは、libgcc
ライブラリのフルパスを出力します。ビルドシステムやスクリプトが、特定の環境におけるlibgcc
の場所を動的に特定するために使用されます。- ビルドプロセスにおけるパフォーマンス最適化: ソフトウェアのビルドは、特に大規模なプロジェクトでは時間がかかることがあります。ビルド時間を短縮するための一般的な手法には、不要な再コンパイルの回避、並列処理の活用、そして外部コマンドの呼び出し回数を減らすためのキャッシュなどがあります。このコミットは、最後の「外部コマンドの呼び出し回数を減らすためのキャッシュ」に該当します。
技術的詳細
このコミットは、src/cmd/go/build.go
ファイル内の builder
構造体の cgo
メソッドに焦点を当てています。cgo
メソッドは、Cgoパッケージのビルドを担当し、Cgoが生成したGoファイルとCオブジェクトファイルを処理します。
変更前は、cgo
メソッド内で b.libgcc(p)
が呼び出され、その結果が libgcc
変数に格納されていました。この b.libgcc(p)
メソッドは、内部的に gcc -print-libgcc-file-name
コマンドを実行して libgcc
のパスを取得していました。問題は、この cgo
メソッドがビルドプロセス中に複数回呼び出される可能性があり、そのたびに gcc -print-libgcc-file-name
が実行され、パフォーマンスのオーバーヘッドが発生していた点です。
このコミットでは、この問題を解決するために以下の変更が導入されました。
- グローバル変数の導入:
var cgoLibGccFile string
という新しいグローバル変数がbuild.go
ファイルの先頭付近に導入されました。この変数は、libgcc
のパスを一度だけ取得してキャッシュするために使用されます。 - キャッシュロジックの追加:
cgo
メソッド内でb.libgcc(p)
を呼び出す前に、cgoLibGccFile
が空文字列であるかどうかをチェックする条件分岐が追加されました。cgoLibGccFile
が空の場合(つまり、まだlibgcc
のパスが取得されていない場合)、b.libgcc(p)
を呼び出してlibgcc
のパスを取得し、その結果をcgoLibGccFile
に格納します。cgoLibGccFile
が空でない場合(つまり、既にlibgcc
のパスがキャッシュされている場合)、b.libgcc(p)
の呼び出しをスキップし、キャッシュされたcgoLibGccFile
の値を使用します。
libgcc
の使用箇所の更新:cgo
メソッド内でlibgcc
変数を使用していた箇所が、新しくキャッシュされたcgoLibGccFile
変数を使用するように変更されました。これにより、常にキャッシュされた値が利用されるようになります。
この変更により、gcc -print-libgcc-file-name
コマンドはGoのビルドプロセス全体で最大でも一度しか実行されなくなり、特にCgoを多用する大規模なプロジェクトにおいて、ビルド時間の短縮に貢献します。
コアとなるコードの変更箇所
src/cmd/go/build.go
ファイルの以下の部分が変更されました。
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1541,6 +1541,8 @@ func envList(key string) []string {
var cgoRe = regexp.MustCompile(`[/\\\\:]`)
+var cgoLibGccFile string
+
func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo, outObj []string, err error) {
if goos != toolGOOS {
return nil, nil, errors.New("cannot use cgo when compiling for a different operating system")
@@ -1630,16 +1632,19 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo,
bareLDFLAGS = append(bareLDFLAGS, f)
}
}
- libgcc, err := b.libgcc(p)
- if err != nil {
- return nil, nil, err
+ if cgoLibGccFile == "" {
+ var err error
+ cgoLibGccFile, err = b.libgcc(p)
+ if err != nil {
+ return nil, nil, err
+ }
}
var staticLibs []string
if goos == "windows" {
// libmingw32 and libmingwex might also use libgcc, so libgcc must come last
- staticLibs = []string{"-lmingwex", "-lmingw32", libgcc}
+ staticLibs = []string{"-lmingwex", "-lmingw32", cgoLibGccFile}
} else {
- staticLibs = []string{libgcc}
+ staticLibs = []string{cgoLibGccFile}
}
for _, cfile := range cfiles {
コアとなるコードの解説
-
var cgoLibGccFile string
の追加:- これは、
libgcc
のパスをキャッシュするためのグローバル変数です。Goのビルドプロセス全体で一度だけ初期化され、その後のcgo
メソッドの呼び出しで再利用されます。
- これは、
-
if cgoLibGccFile == ""
ブロック:- この条件分岐がキャッシュの核心部分です。
cgoLibGccFile
がまだ設定されていない(空文字列である)場合、つまりlibgcc
のパスがまだ取得されていない場合にのみ、以下の処理が実行されます。cgoLibGccFile, err = b.libgcc(p)
:b.libgcc(p)
メソッドが呼び出され、libgcc
のパスが取得されます。このメソッドがgcc -print-libgcc-file-name
を実行する部分です。取得されたパスはcgoLibGccFile
に格納されます。- エラーハンドリング:
b.libgcc(p)
の呼び出しでエラーが発生した場合、そのエラーが返されます。
cgoLibGccFile
が既に設定されている場合、このブロックはスキップされ、既存のキャッシュされた値が使用されます。
-
staticLibs
の初期化:- Windows環境とそれ以外の環境で、リンクする静的ライブラリのリスト
staticLibs
が構築されます。 - 変更前は
libgcc
変数(b.libgcc(p)
の結果)が直接使用されていましたが、変更後はcgoLibGccFile
変数を使用するように修正されています。これにより、常にキャッシュされたlibgcc
のパスがリンクコマンドに渡されるようになります。
- Windows環境とそれ以外の環境で、リンクする静的ライブラリのリスト
この変更により、b.libgcc(p)
の呼び出し(そしてそれに伴う gcc -print-libgcc-file-name
の実行)は、Goのビルドプロセス中に一度だけ行われることが保証され、ビルドパフォーマンスが向上します。
関連リンク
- Go言語のCgoに関する公式ドキュメント: https://go.dev/cmd/cgo/
- GCCのドキュメント(
--print-libgcc-file-name
オプションについて): https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html
参考にした情報源リンク
- Goのコミット履歴: https://github.com/golang/go/commits/master
- Goのコードレビューシステム (Gerrit): https://go.dev/cl/6741051 (元のコードレビューページ)
gcc -print-libgcc-file-name
の機能に関する一般的な情報源 (例: Stack Overflow, GCCの公式ドキュメントなど)- Goのビルドプロセスに関する一般的な情報源 (例: Goの公式ブログ、技術記事など)
libgcc
の役割に関する一般的な情報源 (例: GCCの公式ドキュメント、Linuxのmanページなど)