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

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

このコミットは、Goコンパイラ (cmd/cc) とGoリンカ (cmd/ld) のソースコードに変更を加えています。具体的には、src/cmd/cc/pgen.csrc/cmd/ld/symtab.c の2つのファイルが修正されています。

コミット

cmd/cc: emit gc bitmaps in read-only memory

Cuts hello world by 70kB, because we don't write
those names into the symbol table.

Update #6853

LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/80370045

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

https://github.com/golang/go/commit/a26c01ad446d2853f0c6a7ddaacadb02efa00b7b

元コミット内容

commit a26c01ad446d2853f0c6a7ddaacadb02efa00b7b
Author: Russ Cox <rsc@golang.org>
Date:   Thu Apr 3 19:04:15 2014 -0400

    cmd/cc: emit gc bitmaps in read-only memory
    
    Cuts hello world by 70kB, because we don't write
    those names into the symbol table.
    
    Update #6853
    
    LGTM=khr
    R=khr
    CC=golang-codereviews
    https://golang.org/cl/80370045

変更の背景

このコミットの主な目的は、Goバイナリのサイズを削減することです。特に、hello world プログラムのような最小限のGoプログラムのバイナリサイズが約70KB削減されると述べられています。これは、Goのガベージコレクション (GC) に関連するビットマップ情報が、これまでシンボルテーブルに書き込まれていたものを、読み取り専用メモリ領域に配置するように変更されたためです。

Goのバイナリサイズは、特に組み込みシステムやコンテナ環境など、リソースが限られた環境でのデプロイにおいて重要な考慮事項です。バイナリサイズが大きいと、ディスク使用量が増加するだけでなく、ダウンロード時間、メモリ使用量、キャッシュ効率にも影響を与える可能性があります。

この変更は、GoのIssue #6853「all: binaries too big and growing」に関連しています。このIssueは、Goバイナリのサイズが肥大化しているという問題提起であり、このコミットはその問題に対する具体的な解決策の一つとして実装されました。GCビットマップを読み取り専用メモリに移動することで、シンボルテーブルの肥大化を防ぎ、結果としてバイナリサイズを効率的に削減しています。

前提知識の解説

Goのガベージコレクション (GC) とGCビットマップ

Goは、自動メモリ管理のためにガベージコレクション (GC) を採用しています。GCは、プログラムが不要になったメモリ領域を自動的に解放し、メモリリークを防ぎます。GoのGCは、並行性、低レイテンシを特徴とするマーク&スイープアルゴリズムをベースにしています。

GCが正しく動作するためには、メモリ上のどの位置にポインタが存在するかを正確に知る必要があります。この情報を提供するために使用されるのが「GCビットマップ」です。GCビットマップは、スタック、データセクション、BSSセクション、ヒープ上のオブジェクトなど、メモリの様々な領域におけるポインタのレイアウトを記述します。例えば、あるメモリブロックのGCビットマップが 101 であれば、最初のワードはポインタ、2番目のワードは非ポインタ、3番目のワードはポインタであることを示します。GCはこれらのビットマップを参照して、ポインタをたどり、到達可能なオブジェクトをマークします。

シンボルテーブル

シンボルテーブルは、コンパイラやリンカがプログラム内の識別子(変数名、関数名など)とその属性(型、アドレスなど)を管理するために使用するデータ構造です。コンパイルされたプログラムでは、シンボルテーブルはデバッグ情報や動的リンクのためにバイナリ内に埋め込まれることがあります。シンボルテーブルが大きくなると、バイナリサイズも増加します。

メモリセクション (特にSRODATA)

実行可能ファイルは、通常、異なる種類のデータを格納するために複数のメモリセクションに分割されます。一般的なセクションには以下のようなものがあります。

  • .text (または .code): 実行可能な機械語コードを格納します。
  • .data: 初期化されたグローバル変数や静的変数を格納します。プログラムの実行中に書き換え可能です。
  • .bss: 初期化されていないグローバル変数や静的変数を格納します。プログラム開始時にゼロで初期化されます。
  • .rodata (または SRODATA): 読み取り専用データを格納します。これには、文字列リテラル、定数、およびプログラムの実行中に変更されないその他のデータが含まれます。SRODATA は "Static Read-Only Data" の略で、Goバイナリにおける読み取り専用データセクションを指します。このセクションに配置されたデータは、実行時に変更されることがなく、オペレーティングシステムによって読み取り専用として保護されます。

GCビットマップのような静的で変更されないデータは、読み取り専用メモリセクションに配置するのが適切です。これにより、データの整合性が保証され、誤った書き込みや悪意のある改ざんから保護されます。また、読み取り専用データは、複数のプロセス間で共有されることが多く、メモリ効率の向上にも寄与します。

Goのコンパイラ (cmd/cc) とリンカ (cmd/ld)

Goのビルドプロセスでは、複数のツールが連携して動作します。

  • cmd/cc (GoのCコンパイラ): Goのツールチェインの一部として、Goランタイムや標準ライブラリの一部など、C言語で書かれたコードをコンパイルするために使用されます。このコミットでは、pgen.c というファイルが変更されており、これはGoのCコンパイラが生成するコードに関連する部分です。
  • cmd/ld (Goのリンカ): コンパイラによって生成されたオブジェクトファイルやライブラリを結合し、最終的な実行可能バイナリを生成する役割を担います。リンカは、シンボル解決、再配置、そしてGCビットマップのようなランタイムに必要なメタデータの埋め込みを行います。symtab.c は、リンカがシンボルテーブルを処理する部分に関連するソースファイルです。

技術的詳細

このコミットの核心は、GoのGCビットマップがシンボルテーブルに書き込まれるのではなく、読み取り専用メモリ領域 (SRODATA) に直接配置されるように変更された点にあります。

以前のGoのビルドプロセスでは、GCビットマップに関連する情報(特にスタックフレームの引数やローカル変数に関するポインタ情報)が、シンボルテーブルの一部として扱われていました。これらの情報は、.gcargs·.gclocals· といった特定の名前を持つシンボルとしてシンボルテーブルに登録されていました。シンボルテーブルは、デバッグや動的リンクのためにバイナリ内に含まれるため、これらのGC関連情報がシンボルとして存在することで、バイナリサイズが増加していました。

このコミットでは、以下の2つの主要な変更によってこの問題を解決しています。

  1. cmd/cc におけるGCビットマップの型変更: src/cmd/cc/pgen.c では、関数データ (AFUNCDATA) を生成する際に、そのシンボルの型を SRODATA に明示的に設定するようになりました。これにより、GCビットマップがシンボルテーブルの一部としてではなく、読み取り専用データセクションに直接配置されるようになります。SRODATA に配置されることで、これらのデータは実行時に変更されることがなく、またシンボルテーブルからその名前が削除されるため、バイナリサイズが削減されます。
  2. cmd/ld におけるシンボル名マッチングの変更: src/cmd/ld/symtab.c では、リンカが特定のシンボル名を識別するロジックが変更されました。以前は strstr を使用して .gcargs·.gclocals· といった部分文字列を検索していましたが、新しいコードでは strncmp を使用して gcargs.gclocals. といったプレフィックスを検索するように変更されています。これは、GCビットマップがシンボルテーブルから削除された後も、リンカがこれらの関連データを正しく識別し、処理できるようにするための調整です。特に、· (中点) はGoの内部的なシンボル名に使われる文字であり、この変更はシンボル名の正規化や内部表現の変更に対応している可能性があります。

これらの変更により、GCビットマップはバイナリの読み取り専用データセクションに効率的に格納され、シンボルテーブルの肥大化を防ぎます。結果として、特に小さなGoプログラムにおいて顕著なバイナリサイズの削減が実現されました。

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

--- a/src/cmd/cc/pgen.c
+++ b/src/cmd/cc/pgen.c
@@ -51,6 +51,7 @@ makefuncdatasym(char *namefmt, int64 funcdatakind)
 	nod.sym = sym;
 	nod.class = CSTATIC;
 	gins(AFUNCDATA, nodconst(funcdatakind), &nod);
+	linksym(sym)->type = SRODATA;
 	return sym;
 }
 
--- a/src/cmd/ld/symtab.c
+++ b/src/cmd/ld/symtab.c
@@ -425,7 +425,7 @@ symtab(void)
 			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) {
+		if(strncmp(s->name, "gcargs.", 7) == 0 || strncmp(s->name, "gclocals.", 9) == 0 || strncmp(s->name, "gclocals·", 10) == 0) {
 			s->type = SGOFUNC;
 			s->hide = 1;
 			s->outer = symgofunc;

コアとなるコードの解説

src/cmd/cc/pgen.c の変更

 	nod.sym = sym;
 	nod.class = CSTATIC;
 	gins(AFUNCDATA, nodconst(funcdatakind), &nod);
+	linksym(sym)->type = SRODATA;
 	return sym;
  • makefuncdatasym 関数は、関数に関連するデータ(funcdatakind で指定される種類)のためのシンボルを作成します。このデータは、GoランタイムがGCやスタックトレースなどの目的で使用するメタデータです。
  • gins(AFUNCDATA, ...) は、AFUNCDATA (Function Data) 命令を生成し、コンパイルされたコードに特定の関数データを埋め込みます。
  • 追加された行 linksym(sym)->type = SRODATA; がこのコミットの重要な変更点です。
    • linksym(sym): シンボル sym に対応するリンカシンボル構造体へのポインタを取得します。
    • ->type = SRODATA;: このリンカシンボルの型を SRODATA (Static Read-Only Data) に設定します。これにより、このシンボルが参照するデータ(GCビットマップなど)が、最終的なバイナリの読み取り専用データセクションに配置されるようになります。以前は、これらのデータはデフォルトで書き込み可能なデータセクションやシンボルテーブルに配置されていた可能性があります。SRODATA に配置することで、バイナリサイズが削減され、データの整合性が向上します。

src/cmd/ld/symtab.c の変更

-		if(strstr(s->name, ".gcargs·") != nil || strstr(s->name, ".gclocals·") != nil || strncmp(s->name, "gcargs·", 8) == 0 || strncmp(s->name, "gclocals·", 10) == 0) {
+		if(strncmp(s->name, "gcargs.", 7) == 0 || strncmp(s->name, "gclocals.", 9) == 0 || strncmp(s->name, "gclocals·", 10) == 0) {
 			s->type = SGOFUNC;
 			s->hide = 1;
 			s->outer = symgofunc;
  • このコードブロックは、リンカが特定のシンボル名を識別し、それらに特別な処理(この場合は SGOFUNC 型の設定と非表示化)を適用する部分です。
  • 変更前は、strstr を使用してシンボル名に ".gcargs·"".gclocals·" といった部分文字列が含まれているかをチェックしていました。また、strncmp"gcargs·""gclocals·" といったプレフィックスもチェックしていました。
  • 変更後、strstr による部分文字列検索が削除され、代わりに strncmp を使用して "gcargs.""gclocals." といったプレフィックスを厳密にチェックするようになりました。
    • strncmp(s->name, "gcargs.", 7) == 0: シンボル名が "gcargs." で始まるかをチェックします。
    • strncmp(s->name, "gclocals.", 9) == 0: シンボル名が "gclocals." で始まるかをチェックします。
    • strncmp(s->name, "gclocals·", 10) == 0: 既存の "gclocals·" のチェックは残されています。
  • この変更は、cmd/cc でGCビットマップが SRODATA に移動されたことに伴う調整です。GCビットマップがシンボルテーブルから削除されるか、その名前の形式が変更された場合でも、リンカが引き続きこれらの関連データを正しく識別し、処理できるようにするためのものです。特に、シンボル名の命名規則が変更された可能性や、シンボルテーブルに登録されなくなったデータに対するリンカの内部的な参照方法の変更に対応していると考えられます。

関連リンク

参考にした情報源リンク