[インデックス 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
セクション(最終的には.noptrdata
ELF/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
がenum
SymKind
に追加されました。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"