Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 が実行され、パフォーマンスのオーバーヘッドが発生していた点です。

このコミットでは、この問題を解決するために以下の変更が導入されました。

  1. グローバル変数の導入: var cgoLibGccFile string という新しいグローバル変数が build.go ファイルの先頭付近に導入されました。この変数は、libgcc のパスを一度だけ取得してキャッシュするために使用されます。
  2. キャッシュロジックの追加: cgo メソッド内で b.libgcc(p) を呼び出す前に、cgoLibGccFile が空文字列であるかどうかをチェックする条件分岐が追加されました。
    • cgoLibGccFile が空の場合(つまり、まだ libgcc のパスが取得されていない場合)、b.libgcc(p) を呼び出して libgcc のパスを取得し、その結果を cgoLibGccFile に格納します。
    • cgoLibGccFile が空でない場合(つまり、既に libgcc のパスがキャッシュされている場合)、b.libgcc(p) の呼び出しをスキップし、キャッシュされた cgoLibGccFile の値を使用します。
  3. 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 {

コアとなるコードの解説

  1. var cgoLibGccFile string の追加:

    • これは、libgcc のパスをキャッシュするためのグローバル変数です。Goのビルドプロセス全体で一度だけ初期化され、その後の cgo メソッドの呼び出しで再利用されます。
  2. if cgoLibGccFile == "" ブロック:

    • この条件分岐がキャッシュの核心部分です。
    • cgoLibGccFile がまだ設定されていない(空文字列である)場合、つまり libgcc のパスがまだ取得されていない場合にのみ、以下の処理が実行されます。
      • cgoLibGccFile, err = b.libgcc(p): b.libgcc(p) メソッドが呼び出され、libgcc のパスが取得されます。このメソッドが gcc -print-libgcc-file-name を実行する部分です。取得されたパスは cgoLibGccFile に格納されます。
      • エラーハンドリング: b.libgcc(p) の呼び出しでエラーが発生した場合、そのエラーが返されます。
    • cgoLibGccFile が既に設定されている場合、このブロックはスキップされ、既存のキャッシュされた値が使用されます。
  3. staticLibs の初期化:

    • Windows環境とそれ以外の環境で、リンクする静的ライブラリのリスト staticLibs が構築されます。
    • 変更前は libgcc 変数(b.libgcc(p) の結果)が直接使用されていましたが、変更後は cgoLibGccFile 変数を使用するように修正されています。これにより、常にキャッシュされた libgcc のパスがリンクコマンドに渡されるようになります。

この変更により、b.libgcc(p) の呼び出し(そしてそれに伴う gcc -print-libgcc-file-name の実行)は、Goのビルドプロセス中に一度だけ行われることが保証され、ビルドパフォーマンスが向上します。

関連リンク

参考にした情報源リンク

  • 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ページなど)