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

[インデックス 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つの変更を含んでいます。

  1. リンカ (ld): ポインタを含まない大きな未初期化データのための新しいセクション NOPTRBSS を追加します。
  2. コンパイラ (cc): #pragma dataflag (コミットメッセージでは textflag と誤記されていますが、コード変更は dataflag を追加しています) を追加し、特定のデータが NOPTRBSS セクションに配置されるようにマークできるようにします。
  3. ランタイム (runtime): mheap (Goのメモリヒープ管理構造体) を NOPTRBSS セクションに配置するようにマークし、ガベージコレクタが mheap を特別扱いする必要がなくなるようにします。

また、このコミットは、以前の変更 (CL 5687044) で導入されたARMアーキテクチャの from.flag フィールドを削除し、DUPOK フラグを既存の p->reg フィールドで処理するように戻しています。これは、test/nilptr.go が非常に大きなバイナリを生成する問題と、ARMビルドの問題を解決するためです。

変更の背景

この変更の背景には、主に以下の2つの課題がありました。

  1. ガベージコレクションの効率化: Goのガベージコレクタは、ヒープ上のポインタを追跡して到達可能なオブジェクトを特定します。しかし、プログラムにはポインタを含まない大きなデータ構造(例えば、バイト配列や数値の配列など)が存在することがあります。これらのデータが通常のBSS (Block Started by Symbol) セクションに配置されていると、ガベージコレクタはそれらをスキャンする必要があり、無駄なオーバーヘッドが発生します。特に mheap のようなランタイムの重要なデータ構造は大きく、ポインタを含まない部分が多いため、これをガベージコレクションの対象から外すことで、GCの性能を向上させる必要がありました。
  2. バイナリサイズの最適化と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 が導入され、特定の変数を特定のデータセクションに配置するようコンパイラに指示するために使用されます。

技術的詳細

このコミットの技術的詳細は、コンパイラ、リンカ、およびランタイムの連携によって実現されています。

  1. 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 シンボルがエクスポートされるようになりました。
  2. #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.cloop 関数内で、dataflag が設定されている場合にシンボルの dataflag フィールドにその値がコピーされるようになりました。
  3. mheapNOPTRBSS への配置:

    • src/pkg/runtime/malloc.goc#pragma dataflag 16 が追加されました。これにより、runtime.mheap 変数が NOPTRBSS セクションに配置されるようにコンパイラに指示されます。値 16 は、NOPTR フラグに対応する内部的な値です。
    • src/pkg/runtime/mgc0.c のガベージコレクタのマークフェーズが変更されました。以前は runtime.mheap を特別扱いしてスキャンをスキップしていましたが、mheapNOPTRBSS に配置されるようになったため、この特別扱いが不要になり、scan(data, ebss - data) のように、ポインタを含まないBSSセクションの終わりまでをまとめてスキャンするようになりました。これにより、GCロジックが簡素化されます。
  4. 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 が追加され、mheapNOPTRBSS セクションに配置されるように指示しています。
  • 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 を特別にスキップしていましたが、mheapNOPTRBSS セクションに移動したため、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 構造体に関するランタイムのソースコード解説