[インデックス 12055] ファイルの概要
このコミットは、Go言語のガベージコレクタ (GC) の効率を向上させるため、ポインタを含まないデータ(no-pointers data)を専用のセクションに分離する変更を導入しています。これにより、GCがスキャンする必要のあるメモリ領域が減少し、GCの実行時間短縮と誤検出の削減に貢献します。
コミット
Author: Russ Cox rsc@golang.org Date: Sun Feb 19 03:19:52 2012 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e3f8e915fadd17b7caffaae273eddd3528ac080
元コミット内容
gc, ld: tag data as no-pointers and allocate in separate section
The garbage collector can avoid scanning this section, with
reduces collection time as well as the number of false positives.
Helps a little bit with issue 909, but certainly does not solve it.
R=ken2
CC=golang-dev
https://golang.org/cl/5671099
変更の背景
Go言語のガベージコレクタは、プログラムが使用しなくなったメモリを自動的に解放する役割を担っています。GCの効率は、プログラムの実行性能に直結する重要な要素です。特に、GCがメモリをスキャンする際、ポインタ(他のメモリ領域への参照)が含まれているかどうかを判断する必要があります。ポインタが含まれていないことが確実なデータ領域をGCがスキャンすることは、無駄な処理であり、GC時間の増加や、誤ってポインタではない値をポインタと認識してしまう「誤検出(false positives)」の原因となります。
このコミットの背景には、Goのガベージコレクタの性能改善という明確な目的があります。コミットメッセージにもあるように、「issue 909」に関連する改善の一環として行われました。issue 909は、Goのガベージコレクタがポインタではない整数値をポインタと誤認識し、その結果、不要なメモリを保持し続けてしまう問題("false positives")を扱っています。この変更は、その問題を完全に解決するものではないものの、ポインタを含まないデータ領域をGCのスキャン対象から除外することで、GCの負担を軽減し、誤検出の可能性を減らすことを目指しています。
前提知識の解説
ガベージコレクション (GC)
ガベージコレクションは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されなくなった(到達不可能になった)ものを自動的に解放する仕組みです。これにより、プログラマは手動でのメモリ管理(malloc/freeなど)から解放され、メモリリークのリスクを低減できます。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGC処理を進めることで、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えるように設計されています。
ポインタとメモリレイアウト
コンピュータのメモリは、連続したバイト列としてアドレスが割り当てられています。プログラムは、変数やデータ構造をメモリ上に配置します。ポインタは、そのメモリ上の特定のアドレスを指し示す変数です。Go言語では、*T のように型にアスタリスクを付けることでポインタ型を宣言します。
プログラムが使用するメモリ領域は、その性質によっていくつかのセクションに分けられます。一般的なセクションには以下のようなものがあります。
.text(コードセクション): 実行可能な機械語コードが格納されます。.rodata(読み取り専用データセクション): 定数や文字列リテラルなど、実行中に変更されない読み取り専用のデータが格納されます。ポインタは含まれません。.data(初期化済みデータセクション): プログラムの開始時に初期値を持つグローバル変数や静的変数が格納されます。ポインタを含む可能性があります。.bss(未初期化データセクション): プログラムの開始時に0で初期化されるグローバル変数や静的変数が格納されます。ポインタを含む可能性があります。
Goのガベージコレクタは、メモリ上のポインタを追跡することで、どのオブジェクトがまだ使用されているかを判断します。そのため、ポインタが含まれる可能性のあるデータセクションはGCのスキャン対象となります。
NOPTR と RODATA
このコミットで導入される NOPTR (No Pointers) は、そのデータ領域にポインタが一切含まれていないことを示すフラグです。同様に、RODATA (Read-Only Data) は読み取り専用のデータであることを示します。
RODATA: 読み取り専用であるため、実行中に内容が変更されることはありません。通常、ポインタも含まれません。NOPTR: 読み取り専用であるかどうかにかかわらず、ポインタが含まれていないことを保証します。これにより、GCはこれらの領域をスキャンする必要がなくなります。
これらのフラグは、コンパイラ (g コマンド群、例: 5g, 6g, 8g はそれぞれARM, AMD64, x86アーキテクチャ向けのGoコンパイラ) とリンカ (ld コマンド) の間で、データの特性を伝えるために使用されます。
技術的詳細
このコミットの主要な目的は、Goのガベージコレクタがポインタを含まないデータ領域を効率的にスキップできるようにすることです。これを実現するために、以下の技術的な変更が導入されています。
-
新しいデータセクション
SNOPTRDATAの導入: リンカ (src/cmd/ld/lib.hで定義されるenumにSNOPTRDATAが追加) は、ポインタを含まないデータ専用の新しいセクション.noptrdataを作成します。これにより、従来の.dataセクションからポインタを含まないデータを分離できます。 -
コンパイラによる
NOPTRフラグの付与: Goコンパイラ (src/cmd/5g/gsubr.c,src/cmd/6g/gsubr.c,src/cmd/8g/gsubr.c) は、グローバル変数や静的データがポインタを含まない型である場合 (!haspointers(nam->type))、そのデータにNOPTRフラグを付与するようになります。これは、ggloblnod関数内でp->from.scale |= NOPTR;という形で実装されています。 -
リンカによる
SNOPTRDATAセクションへの配置: リンカ (src/cmd/ld/data.c,src/cmd/5l/obj.c,src/cmd/6l/obj.c,src/cmd/8l/obj.c) は、シンボルにNOPTRフラグが設定されている場合、そのデータを新しいSNOPTRDATAセクション(最終的には.noptrdataELF/Mach-Oセクション)に配置するように変更されます。addstring関数では、文字列リテラルがデフォルトでSNOPTRDATAに配置されるようになります (s->type = SNOPTRDATA;)。文字列はポインタを含まないため、これは理にかなっています。dodata関数では、シンボルを処理するループが変更され、SNOPTRDATAタイプのシンボルが優先的に処理され、.noptrdataセクションに集約されます。その後、残りのデータが従来の.dataセクションに配置されます。address関数では、.noptrdataセクションのアドレス範囲 (noptrdataとenoptrdata) が定義され、Goランタイムがこの情報を利用できるようにします。
-
GCによるスキャン対象の最適化: この変更により、Goランタイムのガベージコレクタは、
.noptrdataセクションに配置されたデータがポインタを含まないことを認識し、その領域をスキャン対象から除外できるようになります。これにより、GCの実行時間が短縮され、ポインタの誤検出が減少します。
この変更は、Goのコンパイラ、リンカ、およびランタイムの連携によって実現される、低レベルかつ重要な最適化です。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、以下のファイルに集中しています。
-
src/cmd/5g/gsubr.c,src/cmd/6g/gsubr.c,src/cmd/8g/gsubr.c(Goコンパイラ):ggloblnod関数において、グローバル変数や静的データがポインタを含まない場合 (!haspointers(nam->type))、NOPTRフラグをp->from.scaleに追加する行が追加されました。if(nam->type != T && !haspointers(nam->type)) p->from.scale |= NOPTR;
-
src/cmd/5l/5.out.h,src/cmd/6l/6.out.h,src/cmd/8l/8.out.h(リンカのヘッダファイル):NOPTRマクロが定義されました。#define RODATA (1<<3) #define NOPTR (1<<4) // 新規追加
-
src/cmd/5l/obj.c,src/cmd/6l/obj.c,src/cmd/8l/obj.c(リンカのオブジェクト処理):loop関数内で、シンボルのタイプがSNOPTRDATAでないことを確認する条件が追加されました。これにより、SNOPTRDATAタイプのシンボルが再定義チェックの対象外となります。if(s->type != SBSS && s->type != SNOPTRDATA && !s->dupok) {p->from.scaleにNOPTRフラグが設定されている場合、シンボルのタイプをSNOPTRDATAに設定するロジックが追加されました。else if(p->from.scale & NOPTR) s->type = SNOPTRDATA;
-
src/cmd/ld/data.c(リンカのデータセクション処理):addstring関数で、文字列シンボルのデフォルトタイプがSDATAからSNOPTRDATAに変更されました。if(s->type == 0) s->type = SNOPTRDATA; // 変更dodata関数内で、データセクションの処理ロジックが大幅に変更されました。.noptrdataセクションが新しく追加され、SNOPTRDATAタイプのシンボルがこのセクションに配置されるようになりました。- 従来の
.dataセクションの前に.noptrdataセクションが配置されるように、セクションの生成とシンボルの配置順序が調整されました。
address関数内で、.noptrdataセクションの仮想アドレス (noptrとenoptrdata) が定義され、xdefineを通じてGoランタイムに公開されるようになりました。xdefine("noptrdata", SBSS, noptr->vaddr); xdefine("enoptrdata", SBSS, noptr->vaddr + noptr->len);
-
src/cmd/ld/lib.h(リンカのライブラリヘッダ):SNOPTRDATAがenumSymKindに追加されました。SNOPTRDATA, // 新規追加 SDATA,
-
src/cmd/ld/symtab.c(リンカのシンボルテーブル処理):symtab関数内で、noptrdataとenoptrdataシンボルが定義され、リンカによって生成されるシンボルテーブルにこれらのアドレスが記録されるようになりました。xdefine("noptrdata", SBSS, 0); xdefine("enoptrdata", SBSS, 0);
コアとなるコードの解説
この変更の核となるのは、Goのコンパイラとリンカが連携して、ポインタを含まないデータを識別し、それを専用のメモリセクションに配置する仕組みです。
-
コンパイラ (
gsubr.c):ggloblnod関数は、グローバル変数や静的データのシンボルを処理する際に呼び出されます。ここで、haspointers(nam->type)という関数を使って、そのデータの型がポインタを含んでいるかどうかをチェックします。もしポインタを含まない型であれば、シンボル情報の一部であるp->from.scaleにNOPTRフラグを立てます。このフラグは、リンカに対する「このデータはポインタを含まない」という指示になります。 -
リンカ (
obj.c,data.c,lib.h,symtab.c):lib.h:SNOPTRDATAという新しいシンボルタイプが定義されます。これは、ポインタを含まないデータが属するセクションを示すためのものです。obj.c: リンカは、コンパイラから渡されたシンボル情報(p->from.scaleにNOPTRフラグが立っているか)を読み取ります。もしNOPTRフラグが立っていれば、そのシンボルの内部的なタイプをSNOPTRDATAに設定します。data.c: このファイルは、リンカが最終的な実行可能ファイルのデータセクションを構築するロジックを含んでいます。addstring関数では、文字列リテラルがデフォルトでSNOPTRDATAタイプとして扱われるようになります。文字列は通常ポインタを含まないため、これは妥当な変更です。dodata関数が最も重要な変更箇所です。ここでは、シンボルをイテレートし、SNOPTRDATAタイプのシンボルを優先的に集めて、新しい.noptrdataセクションを構築します。その後、残りの(ポインタを含む可能性のある)データが従来の.dataセクションに配置されます。これにより、物理的にポインタを含まないデータとそうでないデータがメモリ上で分離されます。address関数では、リンカが生成する実行可能ファイル内の.noptrdataセクションの開始アドレスと終了アドレスを特定し、それらをnoptrdataおよびenoptrdataというシンボルとして定義します。これらのシンボルは、Goランタイムが実行時に.noptrdataセクションの範囲を知るために使用されます。
symtab.c:noptrdataとenoptrdataシンボルがリンカのシンボルテーブルに登録され、デバッグ情報やランタイムからのアクセスが可能になります。
この一連の変更により、Goのガベージコレクタは、実行時に noptrdata と enoptrdata シンボルの値を見て、その範囲のメモリ領域はポインタを含まないためスキャンする必要がないと判断できるようになります。これにより、GCの効率が向上し、特に大きな静的データや文字列リテラルが多いプログラムにおいて、GCのオーバーヘッドが削減されます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/4e3f8e915fadd17b7caffaae273eddd3528ac080
- Go Code Review: https://golang.org/cl/5671099
- Go Issue 909: https://github.com/golang/go/issues/909
参考にした情報源リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事 (一般的なGo GCの仕組み理解のため)
- ELFファイルフォーマットやメモリセクションに関する一般的な情報 (
.text,.data,.rodata,.bssの理解のため) - Goのコンパイラとリンカの内部構造に関する資料 (Goのツールチェインがどのように動作するかを理解するため)
- Goのランタイムとメモリ管理に関する資料 (GCがどのようにメモリをスキャンするかを理解するため)
- Goのソースコード (特に
src/cmd/gc,src/cmd/ld,src/runtimeディレクトリ) - GoのIssueトラッカー (特に
issue 909の詳細を理解するため)
[インデックス 12055] ファイルの概要
このコミットは、Go言語のガベージコレクタ (GC) の効率を向上させるため、ポインタを含まないデータ(no-pointers data)を専用のメモリセクションに分離する変更を導入しています。これにより、GCがスキャンする必要のあるメモリ領域が減少し、GCの実行時間短縮と誤検出の削減に貢献します。
コミット
Author: Russ Cox rsc@golang.org Date: Sun Feb 19 03:19:52 2012 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e3f8e915fadd17b7caffaae273eddd3528ac080
元コミット内容
gc, ld: tag data as no-pointers and allocate in separate section
The garbage collector can avoid scanning this section, with
reduces collection time as well as the number of false positives.
Helps a little bit with issue 909, but certainly does not solve it.
R=ken2
CC=golang-dev
https://golang.org/cl/5671099
変更の背景
Go言語のガベージコレクタは、プログラムが使用しなくなったメモリを自動的に解放する役割を担っており、その効率はプログラムの実行性能に直結する重要な要素です。GCは、メモリ上のポインタを追跡することで、どのオブジェクトがまだ使用されているかを判断します。しかし、ポインタを含まないデータ領域(例えば、純粋な数値の配列や文字列リテラルなど)までGCがスキャンすることは、無駄な処理であり、GC時間の増加や、誤ってポインタではない値をポインタと認識してしまう「誤検出(false positives)」の原因となります。
このコミットの背景には、Goのガベージコレクタの性能改善という明確な目的があります。コミットメッセージにもあるように、「issue 909」に関連する改善の一環として行われました。issue 909は、Goのガベージコレクタがポインタではない整数値をポインタと誤認識し、その結果、不要なメモリを保持し続けてしまう問題("false positives")を扱っています。この変更は、その問題を完全に解決するものではないものの、ポインタを含まないデータ領域をGCのスキャン対象から除外することで、GCの負担を軽減し、誤検出の可能性を減らすことを目指しています。これにより、GCの実行時間が短縮され、全体的なアプリケーションのパフォーマンスが向上することが期待されます。
前提知識の解説
ガベージコレクション (GC)
ガベージコレクションは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されなくなった(到達不可能になった)ものを自動的に解放する仕組みです。これにより、プログラマは手動でのメモリ管理(malloc/freeなど)から解放され、メモリリークのリスクを低減できます。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGC処理を進めることで、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えるように設計されています。
GoのGCは、"Tracing Garbage Collection"の一種であり、"roots"(グローバル変数やゴルーチンのスタックなど)から開始し、すべてのポインタをたどって他のオブジェクトを推移的にマークすることで「生きている」オブジェクトを識別します。これらのポインタをたどることで到達できないメモリは、ガベージと見なされ、回収されます。
ポインタとメモリレイアウト
コンピュータのメモリは、連続したバイト列としてアドレスが割り当てられています。プログラムは、変数やデータ構造をメモリ上に配置します。ポインタは、そのメモリ上の特定のアドレスを指し示す変数です。Go言語では、*T のように型にアスタリスクを付けることでポインタ型を宣言します。
プログラムが使用するメモリ領域は、その性質によっていくつかのセクションに分けられます。一般的なセクションには以下のようなものがあります。
.text(コードセクション): 実行可能な機械語コードが格納されます。.rodata(読み取り専用データセクション): 定数や文字列リテラルなど、実行中に変更されない読み取り専用のデータが格納されます。通常、ポインタは含まれません。.data(初期化済みデータセクション): プログラムの開始時に初期値を持つグローバル変数や静的変数が格納されます。ポインタを含む可能性があります。.bss(未初期化データセクション): プログラムの開始時に0で初期化されるグローバル変数や静的変数が格納されます。ポインタを含む可能性があります。
Goのガベージコレクタは、メモリ上のポインタを追跡することで、どのオブジェクトがまだ使用されているかを判断します。そのため、ポインタが含まれる可能性のあるデータセクションはGCのスキャン対象となります。
NOPTR と RODATA
このコミットで導入される NOPTR (No Pointers) は、そのデータ領域にポインタが一切含まれていないことを示すフラグです。同様に、RODATA (Read-Only Data) は読み取り専用のデータであることを示します。
RODATA: 読み取り専用であるため、実行中に内容が変更されることはありません。通常、ポインタも含まれません。NOPTR: 読み取り専用であるかどうかにかかわらず、ポインタが含まれていないことを保証します。これにより、GCはこれらの領域をスキャンする必要がなくなります。
これらのフラグは、コンパイラ (g コマンド群、例: 5g, 6g, 8g はそれぞれARM, AMD64, x86アーキテクチャ向けのGoコンパイラ) とリンカ (ld コマンド) の間で、データの特性を伝えるために使用されます。
技術的詳細
このコミットの主要な目的は、Goのガベージコレクタがポインタを含まないデータ領域を効率的にスキップできるようにすることです。これを実現するために、以下の技術的な変更がGoのツールチェイン(コンパイラとリンカ)に導入されています。
-
新しいデータセクション
SNOPTRDATAの導入: リンカの内部表現において、ポインタを含まないデータ専用の新しいシンボルタイプSNOPTRDATAが定義されます (src/cmd/ld/lib.hに追加)。これにより、従来のSDATA(通常のデータ) とは異なるカテゴリとして、ポインタを含まないデータを識別できるようになります。最終的に、このタイプのデータは実行可能ファイル内の.noptrdataという専用のセクションに配置されます。 -
コンパイラによる
NOPTRフラグの付与: Goコンパイラ (src/cmd/5g/gsubr.c,src/cmd/6g/gsubr.c,src/cmd/8g/gsubr.c) は、グローバル変数や静的データがポインタを含まない型である場合 (!haspointers(nam->type))、そのデータにNOPTRフラグを付与するようになります。このフラグは、コンパイラがリンカに渡すシンボル情報の一部として埋め込まれます。具体的には、ggloblnod関数内で、シンボルのfrom.scaleフィールドにNOPTRビットが設定されます。 -
リンカによる
SNOPTRDATAセクションへの配置: リンカ (src/cmd/5l/obj.c,src/cmd/6l/obj.c,src/cmd/8l/obj.c,src/cmd/ld/data.c) は、コンパイラから受け取ったシンボル情報に基づいて、データを適切なセクションに配置します。- シンボルに
NOPTRフラグが設定されている場合、リンカはそのシンボルの内部タイプをSNOPTRDATAに設定します。 src/cmd/ld/data.cのaddstring関数では、文字列リテラルがデフォルトでSNOPTRDATAに配置されるようになります。文字列はポインタを含まないため、これはGC効率化に貢献します。dodata関数では、シンボルを処理するロジックが変更され、SNOPTRDATAタイプのシンボルが優先的に処理され、.noptrdataセクションに集約されます。その後、残りの(ポインタを含む可能性のある)データが従来の.dataセクションに配置されます。これにより、実行可能ファイル内のメモリレイアウトが最適化され、ポインタを含まないデータが連続した領域にまとめられます。address関数では、リンカが最終的な実行可能ファイル内の.noptrdataセクションの開始アドレスと終了アドレスを特定し、それらをnoptrdataおよびenoptrdataというシンボルとして定義します。これらのシンボルは、Goランタイムが実行時に.noptrdataセクションの範囲を知るために使用されます。
- シンボルに
-
GCによるスキャン対象の最適化: この変更により、Goランタイムのガベージコレクタは、実行時に
noptrdataおよびenoptrdataシンボルの値(つまり、.noptrdataセクションのメモリ範囲)を参照できるようになります。GCは、この範囲のメモリ領域がポインタを含まないことを認識するため、その領域をスキャン対象から除外できます。これにより、GCの実行時間が短縮され、ポインタの誤検出が減少します。特に、大量の静的データや文字列リテラルを持つアプリケーションにおいて、GCのオーバーヘッドが大幅に削減される効果が期待されます。
この一連の変更は、Goのコンパイラ、リンカ、およびランタイムの密接な連携によって実現される、低レベルかつ重要な最適化であり、Goプログラムの全体的な性能向上に寄与します。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、Goのコンパイラとリンカの以下のファイルに集中しています。
-
src/cmd/5g/gsubr.c,src/cmd/6g/gsubr.c,src/cmd/8g/gsubr.c(Goコンパイラ):ggloblnod関数において、グローバル変数や静的データがポインタを含まない型である場合 (!haspointers(nam->type))、そのシンボル情報の一部であるp->from.scaleにNOPTRフラグを追加する行が挿入されました。if(nam->type != T && !haspointers(nam->type)) p->from.scale |= NOPTR;
-
src/cmd/5l/5.out.h,src/cmd/6l/6.out.h,src/cmd/8l/8.out.h(リンカのヘッダファイル):- リンカが使用するフラグとして、
NOPTRマクロが新しく定義されました。#define RODATA (1<<3) #define NOPTR (1<<4) // 新規追加
- リンカが使用するフラグとして、
-
src/cmd/5l/obj.c,src/cmd/6l/obj.c,src/cmd/8l/obj.c(リンカのオブジェクト処理):loop関数内で、シンボルの再定義チェックにおいて、SNOPTRDATAタイプのシンボルが除外されるように条件が変更されました。if(s->type != SBSS && s->type != SNOPTRDATA && !s->dupok) {p->from.scaleにNOPTRフラグが設定されている場合、シンボルのタイプをSNOPTRDATAに設定するロジックが追加されました。else if(p->from.scale & NOPTR) s->type = SNOPTRDATA;
-
src/cmd/ld/data.c(リンカのデータセクション処理):addstring関数で、文字列シンボルのデフォルトタイプがSDATAからSNOPTRDATAに変更されました。if(s->type == 0) s->type = SNOPTRDATA; // 変更dodata関数内で、データセクションの構築ロジックが大幅に変更されました。.noptrdataセクションが新しく追加され、SNOPTRDATAタイプのシンボルがこのセクションに配置されるようになりました。- 従来の
.dataセクションの前に.noptrdataセクションが配置されるように、セクションの生成とシンボルの配置順序が調整されました。
address関数内で、.noptrdataセクションの仮想アドレス (noptrとenoptrdata) が定義され、xdefineを通じてGoランタイムに公開されるようになりました。xdefine("noptrdata", SBSS, noptr->vaddr); xdefine("enoptrdata", SBSS, noptr->vaddr + noptr->len);
-
src/cmd/ld/lib.h(リンカのライブラリヘッダ):- リンカが使用するシンボルタイプを定義する
enumにSNOPTRDATAが追加されました。SNOPTRDATA, // 新規追加 SDATA,
- リンカが使用するシンボルタイプを定義する
-
src/cmd/ld/symtab.c(リンカのシンボルテーブル処理):symtab関数内で、noptrdataとenoptrdataシンボルが定義され、リンカによって生成されるシンボルテーブルにこれらのアドレスが記録されるようになりました。xdefine("noptrdata", SBSS, 0); xdefine("enoptrdata", SBSS, 0);
コアとなるコードの解説
この変更の核となるのは、Goのコンパイラとリンカが連携して、ポインタを含まないデータを識別し、それを専用のメモリセクションに配置する仕組みです。
-
コンパイラ (
gsubr.cのggloblnod関数):ggloblnod関数は、Goのソースコードからグローバル変数や静的データが検出された際に呼び出され、それらのシンボル情報を構築します。この関数内で、haspointers(nam->type)というヘルパー関数が使用され、対象のデータの型がポインタを含んでいるかどうかをチェックします。もし、そのデータがポインタを一切含まない型であると判断された場合(例: 整数、浮動小数点数、文字列、ポインタを含まない構造体など)、リンカに渡すシンボル情報の一部であるp->from.scaleフィールドにNOPTRフラグ(ビットマスク)を立てます。このフラグは、リンカに対する「このデータはポインタを含まないため、GCのスキャン対象から除外できる」という明確な指示となります。 -
リンカ (
obj.c,data.c,lib.h,symtab.c):lib.h: リンカの内部でデータを分類するために使用されるenumに、新しくSNOPTRDATAというシンボルタイプが追加されます。これは、ポインタを含まないデータが属するセクションを示すためのものです。obj.c: リンカは、コンパイラから渡されたシンボル情報(特にp->from.scaleにNOPTRフラグが立っているか)を読み取ります。もしNOPTRフラグが立っていれば、そのシンボルの内部的なタイプをSNOPTRDATAに設定します。これにより、リンカは後続の処理でこのデータを特別に扱うことができます。data.c: このファイルは、リンカが最終的な実行可能ファイルのデータセクションを構築する際の中心的なロジックを含んでいます。addstring関数では、文字列リテラルがデフォルトでSNOPTRDATAタイプとして扱われるように変更されます。文字列は通常、それ自体がポインタを含むことはなく、その内容もポインタではないため、この変更はGCの効率化に直接貢献します。dodata関数がこのコミットの最も重要な変更箇所です。この関数は、すべてのデータシンボルをイテレートし、それらを適切なメモリセクションに配置します。変更後、dodataはまずSNOPTRDATAタイプのシンボルを優先的に集め、新しい.noptrdataセクションを構築します。このセクションは、実行可能ファイル内でポインタを含まないデータが連続して配置される領域となります。その後、残りの(ポインタを含む可能性のある)データが従来の.dataセクションに配置されます。これにより、物理的にポインタを含まないデータとそうでないデータがメモリ上で分離され、GCがスキャンすべき領域を明確に区別できるようになります。address関数では、リンカが生成する実行可能ファイル内の.noptrdataセクションの開始アドレスと終了アドレスを特定し、それらをnoptrdataおよびenoptrdataというシンボルとして定義します。これらのシンボルは、Goランタイムが実行時に.noptrdataセクションの正確な範囲を知るために使用されます。
symtab.c:noptrdataとenoptrdataシンボルがリンカのシンボルテーブルに登録され、デバッグ情報やランタイムからのアクセスが可能になります。
この一連の変更により、Goのガベージコレクタは、実行時に noptrdata と enoptrdata シンボルの値を見て、その範囲のメモリ領域はポインタを含まないためスキャンする必要がないと判断できるようになります。これにより、GCの効率が向上し、特に大きな静的データや文字列リテラルが多いプログラムにおいて、GCのオーバーヘッドが削減されます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/4e3f8e915fadd17b7caffaae273eddd3528ac080
- Go Code Review: https://golang.org/cl/5671099
- Go Issue 909: https://github.com/golang/go/issues/909
参考にした情報源リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事
- ELFファイルフォーマットやメモリセクションに関する一般的な情報
- Goのコンパイラとリンカの内部構造に関する資料
- Goのランタイムとメモリ管理に関する資料
- Goのソースコード (特に
src/cmd/gc,src/cmd/ld,src/runtimeディレクトリ) - GoのIssueトラッカー (特に
issue 909の詳細) - Web検索: "Go garbage collector no-pointers data section"