[インデックス 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構造体に関するランタイムのソースコード解説