[インデックス 12126] ファイルの概要
このコミットは、Go言語のリンカに新しいセクションタイプ NOPTRBSS
(No-Pointer BSS) を導入し、コンパイラとランタイムを更新してこれを利用するようにするものです。これにより、ポインタを含まない大きな未初期化データ領域を効率的に管理し、ガベージコレクタの負担を軽減するとともに、特定のテストケース (test/nilptr.go
) で発生していた非常に大きなバイナリ生成の問題やARMビルドの問題を解決します。
コミット
commit 5bcad92f07317dc81122f72d40433f314e336b7c
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 21 22:08:42 2012 -0500
ld: add NOPTRBSS for large, pointer-free uninitialized data
cc: add #pragma textflag to set it
runtime: mark mheap to go into noptr-bss.
remove special case in garbage collector
Remove the ARM from.flag field created by CL 5687044.
The DUPOK flag was already in p->reg, so keep using that.
Otherwise test/nilptr.go creates a very large binary.
Should fix the arm build.
Diagnosed by minux.ma; replacement for CL 5690044.
R=golang-dev, minux.ma, r
CC=golang-dev
https://golang.org/cl/5686060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5bcad92f07317dc81122f72d40433f314e336b7c
元コミット内容
このコミットは、主に以下の3つの変更を含んでいます。
- リンカ (
ld
): ポインタを含まない大きな未初期化データのための新しいセクションNOPTRBSS
を追加します。 - コンパイラ (
cc
):#pragma dataflag
(コミットメッセージではtextflag
と誤記されていますが、コード変更はdataflag
を追加しています) を追加し、特定のデータがNOPTRBSS
セクションに配置されるようにマークできるようにします。 - ランタイム (
runtime
):mheap
(Goのメモリヒープ管理構造体) をNOPTRBSS
セクションに配置するようにマークし、ガベージコレクタがmheap
を特別扱いする必要がなくなるようにします。
また、このコミットは、以前の変更 (CL 5687044) で導入されたARMアーキテクチャの from.flag
フィールドを削除し、DUPOK
フラグを既存の p->reg
フィールドで処理するように戻しています。これは、test/nilptr.go
が非常に大きなバイナリを生成する問題と、ARMビルドの問題を解決するためです。
変更の背景
この変更の背景には、主に以下の2つの課題がありました。
- ガベージコレクションの効率化: Goのガベージコレクタは、ヒープ上のポインタを追跡して到達可能なオブジェクトを特定します。しかし、プログラムにはポインタを含まない大きなデータ構造(例えば、バイト配列や数値の配列など)が存在することがあります。これらのデータが通常のBSS (Block Started by Symbol) セクションに配置されていると、ガベージコレクタはそれらをスキャンする必要があり、無駄なオーバーヘッドが発生します。特に
mheap
のようなランタイムの重要なデータ構造は大きく、ポインタを含まない部分が多いため、これをガベージコレクションの対象から外すことで、GCの性能を向上させる必要がありました。 - バイナリサイズの最適化とARMビルドの修正:
test/nilptr.go
のような特定のテストケースが、非常に大きなバイナリを生成していました。これは、おそらくポインタを含まない大きなデータが効率的に扱われていなかったためと考えられます。また、ARMビルドに関する問題も報告されており (minux.ma による診断)、以前の変更 (CL 5687044) が原因で導入されたfrom.flag
フィールドの扱いが適切でなかったことが示唆されています。このコミットは、これらの問題を解決し、バイナリサイズを削減し、ARMビルドを修正することを目的としています。
前提知識の解説
1. リンカセクション (Linker Sections)
プログラムのバイナリファイルは、異なる種類のデータやコードを格納するために複数のセクションに分割されます。主要なセクションには以下のようなものがあります。
.text
: 実行可能な機械語コードが格納されます。.data
: 初期化されたグローバル変数や静的変数が格納されます。.rodata
: 読み取り専用のデータ(文字列リテラルなど)が格納されます。.bss
(Block Started by Symbol): 初期化されていないグローバル変数や静的変数が格納されます。これらの変数はプログラムの開始時にゼロで初期化されることが保証されます。バイナリファイル自体にはデータは含まれず、サイズ情報のみが格納され、実行時にメモリが割り当てられます。.noptrdata
: Go言語特有のセクションで、ポインタを含まない初期化済みデータが格納されます。ガベージコレクタはこのセクションをスキャンする必要がありません。
このコミットで導入される .noptrbss
は、.bss
の「ポインタを含まない」版です。つまり、ポインタを含まない大きな未初期化データがここに配置され、ガベージコレクタの対象外となります。
2. Goのガベージコレクタ (Garbage Collector)
Goのガベージコレクタは、到達可能性に基づいてメモリを管理します。プログラムが使用している(到達可能な)オブジェクトを特定し、それ以外の(到達不可能な)オブジェクトが占めるメモリを解放します。GCはヒープ上のポインタをたどってオブジェクトグラフを構築します。
ポインタを含まないデータ領域をGCがスキャンすることは無駄であり、性能に影響を与えます。そのため、Goでは noptrdata
のようなセクションを導入し、GCがスキャンすべきでない領域を明示的に区別しています。NOPTRBSS
の導入もこの思想に基づいています。
3. mheap
構造体
runtime.mheap
は、Goランタイムにおけるメモリヒープの管理構造体です。これは、Goプログラムが動的にメモリを割り当てる際に使用されるアリーナやスパンなどの情報を保持します。mheap
は非常に大きな構造体であり、その大部分はポインタを含まないデータ(例えば、アリーナのビットマップやスパンの配列など)で構成されています。
4. #pragma
ディレクティブ
#pragma
は、C言語やC++のプリプロセッサディレクティブで、コンパイラに特定の情報や指示を与えるために使用されます。Goのコンパイラも、内部的に同様のメカニズムを使用して、コード生成に関するヒントを受け取ることがあります。このコミットでは、#pragma dataflag
が導入され、特定の変数を特定のデータセクションに配置するようコンパイラに指示するために使用されます。
技術的詳細
このコミットの技術的詳細は、コンパイラ、リンカ、およびランタイムの連携によって実現されています。
-
NOPTRBSS
セクションの導入:- リンカ (
src/cmd/5l/asm.c
,src/cmd/6l/asm.c
,src/cmd/8l/asm.c
) に、新しいELFセクションタイプElfStrNoPtrBss
が追加されました。これにより、リンカは.noptrbss
という名前のセクションを認識し、処理できるようになります。 - リンカのシンボルテーブル (
src/cmd/ld/lib.h
,src/cmd/ld/symtab.c
) にSNOPTRBSS
という新しいシンボルタイプが追加され、このセクションに属するシンボルを識別できるようになりました。 - リンカのデータ処理ロジック (
src/cmd/ld/data.c
) が更新され、SNOPTRBSS
タイプのシンボルを適切に処理し、.noptrbss
セクションに配置するようになりました。特に、dodata
関数内で.bss
セクションの後に.noptrbss
セクションが配置されるように変更されています。 - リンカのアドレス割り当てロジック (
src/cmd/ld/data.c
) も更新され、noptrbss
セクションのアドレスと長さを定義するnoptrbss
およびenoptrbss
シンボルがエクスポートされるようになりました。
- リンカ (
-
#pragma dataflag
の追加:- Goコンパイラのフロントエンド (
src/cmd/cc/cc.h
,src/cmd/cc/dcl.c
,src/cmd/cc/dpchk.c
,src/cmd/cc/lexbody
,src/cmd/cc/macbody
) に、新しいdataflag
フィールドがSym
構造体に追加されました。 #pragma dataflag <value>
という新しいプラグマが追加されました。このプラグマは、続くグローバル変数宣言に対してdataflag
の値を設定します。この値は、リンカがその変数をどのセクションに配置するかを決定するためのヒントとして使用されます。src/cmd/cc/dcl.c
のloop
関数内で、dataflag
が設定されている場合にシンボルのdataflag
フィールドにその値がコピーされるようになりました。
- Goコンパイラのフロントエンド (
-
mheap
のNOPTRBSS
への配置:src/pkg/runtime/malloc.goc
に#pragma dataflag 16
が追加されました。これにより、runtime.mheap
変数がNOPTRBSS
セクションに配置されるようにコンパイラに指示されます。値16
は、NOPTR
フラグに対応する内部的な値です。src/pkg/runtime/mgc0.c
のガベージコレクタのマークフェーズが変更されました。以前はruntime.mheap
を特別扱いしてスキャンをスキップしていましたが、mheap
がNOPTRBSS
に配置されるようになったため、この特別扱いが不要になり、scan(data, ebss - data)
のように、ポインタを含まないBSSセクションの終わりまでをまとめてスキャンするようになりました。これにより、GCロジックが簡素化されます。
-
ARM
from.flag
の削除とDUPOK
の扱い:src/cmd/5a/lex.c
,src/cmd/5c/swt.c
,src/cmd/5g/gg.h
,src/cmd/5g/gobj.c
,src/cmd/5l/l.h
,src/libmach/5obj.c
など、ARMアーキテクチャ関連のファイルからfrom.flag
フィールドが削除されました。DUPOK
フラグは、p->reg
フィールドで引き続き処理されるように変更されました。これにより、ARMビルドの問題が解決されます。
これらの変更により、Goのバイナリはより効率的なメモリレイアウトを持つようになり、ガベージコレクタの性能が向上し、特定のケースでのバイナリサイズの肥大化が抑制されます。
コアとなるコードの変更箇所
このコミットは広範囲にわたる変更を含んでいますが、特に重要な変更箇所は以下の通りです。
src/cmd/ld/data.c
: リンカのデータセクション割り当てロジック。NOPTRBSS
セクションの追加と、SBSS
およびSNOPTRBSS
シンボルの処理が変更されています。dodata
関数内で、SNOPTRBSS
タイプのシンボルがSNOPTRDATA
に変換されるロジックが追加されています。.bss
セクションの後に.noptrbss
セクションが続くように、セクションの割り当て順序が変更されています。address
関数内で、.noptrbss
セクションのアドレスと長さを計算し、noptrbss
およびenoptrbss
シンボルを定義するようになりました。
src/cmd/cc/cc.h
: コンパイラのヘッダファイル。Sym
構造体にdataflag
フィールドが追加され、pragdataflag
関数が宣言されています。src/cmd/cc/dpchk.c
: コンパイラのプラグマ処理。pragdataflag
関数が実装され、#pragma dataflag
ディレクティブを処理するようになりました。src/pkg/runtime/malloc.goc
: ランタイムのメモリ割り当て関連ファイル。runtime.mheap
の宣言の前に#pragma dataflag 16
が追加され、mheap
がNOPTRBSS
セクションに配置されるように指示しています。src/pkg/runtime/mgc0.c
: ランタイムのガベージコレクタのコアロジック。mark
関数内のscan
呼び出しが変更され、mheap
の特別扱いが削除され、ebss
(BSSセクションの終わり) までをスキャンするようになりました。
コアとなるコードの解説
src/cmd/ld/data.c
の変更 (抜粋)
// dodata 関数内
for(s = datap; s != nil; s = s->next) {
- if(s->np > 0 && s->type == SBSS)
- s->type = SDATA;
+ if(s->np > 0) {
+ if(s->type == SBSS)
+ s->type = SDATA;
+ if(s->type == SNOPTRBSS)
+ s->type = SNOPTRDATA;
+ }
if(s->np > s->size)
diag("%s: initialize bounds (%lld < %d)",
s->name, (vlong)s->size, s->np);
}
// ...
// bss, then pointer-free bss
noptr = nil;
sect = addsection(&segdata, ".bss", 06);
sect->vaddr = datsize;
for(; ; s = s->next) {
if((s == nil || s->type >= SNOPTRBSS) && noptr == nil) {
// finish bss, start noptrbss
datsize = rnd(datsize, 8);
sect->len = datsize - sect->vaddr;
sect = addsection(&segdata, ".noptrbss", 06);
sect->vaddr = datsize;
noptr = sect;
}
if(s == nil) {
sect->len = datsize - sect->vaddr;
break;
}
if(s->type > SNOPTRBSS) {
cursym = s;
diag("unexpected symbol type %d", s->type);
}
// ... (シンボル処理)
}
この部分では、リンカがシンボルを処理し、適切なセクションに割り当てるロジックが示されています。
s->np > 0
(初期化データがある) かつs->type == SBSS
(BSSセクション) の場合、SDATA
(初期化済みデータセクション) に変更されます。- 同様に、
s->type == SNOPTRBSS
(ポインタなしBSSセクション) の場合、SNOPTRDATA
(ポインタなし初期化済みデータセクション) に変更されます。これは、未初期化データが実際に初期化される際に、ポインタなし初期化済みデータとして扱われることを意味します。 - セクションの割り当て順序が変更され、
.bss
セクションの後に.noptrbss
セクションが続くように明示的に制御されています。これにより、ポインタを含まない未初期化データが専用のセクションに分離されます。
src/cmd/cc/dpchk.c
の変更 (抜粋)
void
pragdataflag(void)
{
dataflag = getnsn();
while(getnsc() != '\n')
;
if(debug['f'])
print("%4d: dataflag %d\n", lineno, dataflag);
}
この関数は、#pragma dataflag
ディレクティブを処理します。getnsn()
は数値を取得し、それを dataflag
グローバル変数に設定します。これにより、コンパイラは後続の変数宣言に対してこの dataflag
の値を使用し、リンカにヒントを渡すことができます。
src/pkg/runtime/malloc.goc
の変更 (抜粋)
#pragma dataflag 16 /* mark mheap as 'no pointers', hiding from garbage collector */
MHeap runtime·mheap;
この行は、runtime.mheap
変数が宣言される直前に #pragma dataflag 16
を挿入しています。これにより、コンパイラは mheap
がポインタを含まないデータであることをリンカに伝え、リンカはこれを NOPTRBSS
セクションに配置します。コメントにあるように、これはガベージコレクタから mheap
を「隠す」効果があります。
src/pkg/runtime/mgc0.c
の変更 (抜粋)
// mark data+bss.
- // skip runtime·mheap itself, which has no interesting pointers
- // and is mostly zeroed and would not otherwise be paged in.
- scan(data, (byte*)&runtime·mheap - data);
- scan((byte*)(&runtime·mheap+1), end - (byte*)(&runtime·mheap+1));
+ scan(data, ebss - data);
この変更は、ガベージコレクタのマークフェーズにおける重要な最適化を示しています。以前は runtime.mheap
を特別にスキップしていましたが、mheap
が NOPTRBSS
セクションに移動したため、GCは data
セクションの開始から ebss
(BSSセクションの終わり、つまり NOPTRBSS
の終わり) までをまとめてスキャンするだけでよくなりました。これにより、GCのロジックが簡素化され、効率が向上します。
関連リンク
- Go Issue Tracker: このコミットに関連するGoのIssueやデザインドキュメントがある可能性があります。コミットメッセージに記載されているCL (Change List) 番号
5686060
をGoのコードレビューサイトで検索すると、より詳細な議論や背景情報が見つかるかもしれません。
参考にした情報源リンク
- Go言語のガベージコレクションに関するドキュメントやブログ記事
- ELFファイルフォーマットとリンカセクションに関する一般的な情報
- Goのコンパイラとリンカの内部構造に関する資料 (Goのソースコード自体も含む)
#pragma
ディレクティブに関するC/C++のドキュメント (Goのコンパイラにおける使用法を理解するため)- Goの
mheap
構造体に関するランタイムのソースコード解説