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

[インデックス 18566] ファイルの概要

このコミットは、Go言語のリンカ (cmd/ld) において、生成されるバイナリサイズを削減することを目的としています。具体的には、ガベージコレクション (GC) の情報を持つ .gcargs および .gclocals シンボルをシンボルテーブルから削除し、それらを go.func データというメタシンボルに統合することで、バイナリの肥大化を抑制します。これにより、一般的なGoバイナリのサイズが約10%削減される効果があります。

コミット

commit 2541cc81978dc5e41e2e2db6345d8ca7a365ca8c
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 19 10:00:44 2014 -0500

    cmd/ld: drop gcargs, gclocals symbols from symbol table
    
    Update #6853
    
    Every function now has a gcargs and gclocals symbol
    holding associated garbage collection information.
    Put them all in the same meta-symbol as the go.func data
    and then drop individual entries from symbol table.
    
    Removing gcargs and gclocals reduces the size of a
    typical binary by 10%.
    
    LGTM=r
    R=r
    CC=golang-codereviews
    https://golang.org/cl/65870044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/2541cc81978dc5e41e2e2db6345d8ca7a365ca8c

元コミット内容

cmd/ld: drop gcargs, gclocals symbols from symbol table
    
Update #6853
    
Every function now has a gcargs and gclocals symbol
holding associated garbage collection information.
Put them all in the same meta-symbol as the go.func data
and then drop individual entries from symbol table.
    
Removing gcargs and gclocals reduces the size of a
typical binary by 10%.

変更の背景

この変更は、Go言語のバイナリサイズが肥大化しているという問題(Issue #6853)に対応するために行われました。Issue #6853 は「all: binaries too big and growing」と題されており、Goのリリース間でバイナリサイズが大幅に増加していることが指摘されていました。特に、symtab(シンボルテーブル)やpclntab(PC-lineテーブル)、リフレクト型、静的Go文字列データなどがバイナリサイズに寄与していると議論されていました。

Goの各関数は、ガベージコレクションに必要な情報(引数とローカル変数のポインタ情報)を保持するために、それぞれ .gcargs.gclocals というシンボルを持っていました。これらのシンボルが個別にシンボルテーブルにエントリとして存在することで、バイナリサイズが増加していました。このコミットは、これらの情報を go.func という既存のメタシンボルに統合し、個別のシンボルテーブルエントリを削除することで、バイナリサイズを削減することを目指しました。結果として、一般的なGoバイナリのサイズが10%削減されるという大きな効果が得られました。

前提知識の解説

Goリンカ (cmd/ld) の役割

Goリンカ (cmd/ld、または go tool link として呼び出されることが多い) は、Goのビルドプロセスにおいて非常に重要な役割を担っています。その主な機能は以下の通りです。

  1. コンパイル済みコードの結合: main パッケージとそのすべての依存関係のコンパイル済みGoアーカイブまたはオブジェクトファイルを読み込み、それらを単一の実行可能バイナリに結合します。
  2. シンボル解決と再配置: コンパイルされたパッケージからすべてのシンボル(関数、変数)を収集し、最終的なバイナリ内でのアドレスを計算し、それらの間のすべての参照を解決します。これは、コンパイラがパッケージを個別に処理し、他のパッケージの関数やデータの最終アドレスを知らないため不可欠です。
  3. デッドコードの削除: リンカは、使用されていないコードを識別して削除します。これにより、最終的なバイナリサイズが削減されます。この最適化は、リンカがすべてのパッケージをグローバルに把握しているため、リンク段階で実行されます。
  4. 実行可能形式の生成: ターゲットオペレーティングシステムの実行可能形式(例: Linux上のELF、Windows上のPE)に必要なヘッダを準備し、最終的な実行可能ファイルを生成します。
  5. ビルド情報の埋め込み: -X などのフラグを使用して、バージョン番号、ビルドタイムスタンプ、コミットハッシュなどのビルド時情報をバイナリに埋め込むことができます。
  6. バイナリサイズの制御: -s-w などのフラグを使用して、シンボルテーブルやデバッグ情報を削除し、生成されるバイナリのサイズを大幅に削減できます。

gcargs および gclocals シンボル

Goのガベージコレクタ (GC) は、スタック上のポインタを正確に識別するために、各関数のスタックフレームのレイアウトに関するメタデータを必要とします。このメタデータは、コンパイラによって生成され、gcargs および gclocals というシンボルとしてバイナリに埋め込まれます。

  • gcargs (Garbage Collector Arguments): 関数に渡される引数の中で、ポインタを含むものに関する情報を提供します。関数が呼び出されると、引数は通常スタックに配置されます。gcargs メタデータは、Goランタイムのガベージコレクタに対し、これらの引数のうちどれがポインタであるかを伝え、GCがそれらをトレースして、それらが指すオブジェクトがまだ「ライブ」(到達可能)であり、回収されるべきではないかを判断できるようにします。
  • gclocals (Garbage Collector Locals): 同様に、gclocals は、関数のスタックフレーム内のローカル変数でポインタを含むものに関する情報を提供します。関数が実行されると、ローカル変数のためにスタック上にスペースが割り当てられます。gclocals メタデータは、GCがこれらのローカル変数のうちどれがポインタであるかを識別するのに役立ち、ヒープに割り当てられたオブジェクトへの参照をスタック上で正しくスキャンできるようにします。

これらのシンボルは、コンパイラが生成するアセンブリ出力に FUNCDATA ディレクティブとして埋め込まれ、スタックフレームのメモリレイアウトを記述するテーブルやビットマップをバイナリに組み込みます。GCサイクル中、Goランタイムはこのメタデータを使用してスタックをスキャンし、ポインタを識別し、到達可能なすべてのオブジェクトを「ライブ」としてマークします。

技術的詳細

このコミット以前は、Goの各関数に対して生成される gcargs および gclocals シンボルは、リンカのシンボルテーブルに個別のエントリとして登録されていました。これは、バイナリのシンボルテーブルが肥大化する一因となっていました。

このコミットの変更は、src/cmd/ld/symtab.c 内の symtab 関数にロジックを追加することで実現されています。symtab 関数は、リンカがシンボルテーブルを構築する際に呼び出される関数です。

変更の核心は、gcargs および gclocals シンボルを、既存の go.func データというメタシンボルに「隠す」ことです。具体的には、これらのシンボルが SGOFUNC 型としてマークされ、hide フラグが 1 に設定され、outer フィールドが symgofuncgo.func シンボルへのポインタ)に設定されます。

  • s->type = SGOFUNC;: シンボルの型を SGOFUNC に設定します。これは、Goの関数関連のメタデータであることを示します。
  • s->hide = 1;: このフラグを設定することで、シンボルが通常のシンボルテーブルから「隠され」、外部ツール(例えば nm コマンド)から直接参照されなくなります。しかし、リンカ内部では引き続きその情報が利用可能です。
  • s->outer = symgofunc;: このシンボルが symgofuncgo.func シンボル)の「内部」にあることを示します。これにより、gcargsgclocals の情報が go.func という単一のメタシンボルに論理的にグループ化されます。

この変更により、個々の gcargs および gclocals シンボルがリンカの最終的なシンボルテーブルから削除されるため、バイナリサイズが削減されます。しかし、GCが必要とする情報は go.func メタシンボルを通じて引き続き利用可能であるため、ランタイムの動作には影響を与えません。

コアとなるコードの変更箇所

変更は src/cmd/ld/symtab.c ファイルの symtab 関数内で行われています。

--- a/src/cmd/ld/symtab.c
+++ b/src/cmd/ld/symtab.c
@@ -409,5 +409,10 @@ symtab(void)
 		if(s->type == STEXT && s->outer == S) {
 			s->type = SGOFUNC;
 			s->hide = 1;
 			s->outer = symgofunc;
 		}
+		if(strstr(s->name, ".gcargs·") != nil || strstr(s->name, ".gclocals·") != nil || strncmp(s->name, "gcargs·", 8) == 0 || strncmp(s->name, "gclocals·", 10) == 0) {
+			s->type = SGOFUNC;
+			s->hide = 1;
+			s->outer = symgofunc;
+		}
 	}
 }

コアとなるコードの解説

追加されたコードブロックは以下の通りです。

		if(strstr(s->name, ".gcargs·") != nil || strstr(s->name, ".gclocals·") != nil || strncmp(s->name, "gcargs·", 8) == 0 || strncmp(s->name, "gclocals·", 10) == 0) {
			s->type = SGOFUNC;
			s->hide = 1;
			s->outer = symgofunc;
		}

このC言語のコードスニペットは、リンカがシンボルテーブルを処理するループ内で実行されます。

  1. strstr(s->name, ".gcargs·") != nil || strstr(s->name, ".gclocals·") != nil:
    • これは、現在のシンボル s の名前 (s->name) に文字列 .gcargs· または .gclocals· が含まれているかどうかをチェックします。Goのコンパイラは、これらのGC関連のシンボル名に特定のプレフィックスやサフィックスを付けることが一般的です。· はGoの内部シンボル名で使われる特殊文字です。
  2. strncmp(s->name, "gcargs·", 8) == 0 || strncmp(s->name, "gclocals·", 10) == 0:
    • これは、シンボル名が gcargs· または gclocals· で始まるかどうかをチェックします。strncmp は指定されたバイト数だけ文字列を比較します。
    • これらの条件のいずれかが真である場合、つまり現在のシンボルがGCの引数またはローカル変数に関する情報を持つシンボルであると識別された場合、以下の処理が実行されます。
  3. s->type = SGOFUNC;:
    • シンボルの型を SGOFUNC に変更します。これは、このシンボルがGoの関数に関連するメタデータであることを示します。
  4. s->hide = 1;:
    • シンボルを「隠し」ます。これにより、このシンボルは通常のシンボルテーブルのリストには表示されなくなりますが、リンカ内部ではその情報が保持され、必要に応じてアクセスできます。これはバイナリサイズ削減の主要なメカニズムです。
  5. s->outer = symgofunc;:
    • このシンボルが symgofuncgo.func シンボル)の「内部」にあることを示します。これにより、論理的にこれらのGC関連情報が go.func という単一のメタシンボルにまとめられ、個別のエントリが不要になります。

この変更により、リンカは gcargs および gclocals シンボルを特殊な方法で扱い、最終的なバイナリのシンボルテーブルからそれらを削除することで、バイナリサイズを効率的に削減します。

関連リンク

参考にした情報源リンク