[インデックス 13949] ファイルの概要
このコミットは、src/cmd/5c/txt.c ファイルに2行の追加と4行の削除を行い、合計で2行の変更を加えています。
コミット
commit f2fadfefaf8b050a401296faa9c9b060828c3d42
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Sep 25 14:42:44 2012 +0800
cmd/5c: fix dataflag annotation
file old_size new_size base@c1ce95068533
bin/go 14717392 6287824 5918236
this huge size difference is due to GC data for runtime.mheap
(NOPTR dataflag is not obeyed).
R=rsc, dave
CC=golang-dev
https://golang.org/cl/6547051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f2fadfefaf8b050a401296faa9c9b060828c3d42
元コミット内容
cmd/5c: fix dataflag annotation
file old_size new_size base@c1ce95068533
bin/go 14717392 6287824 5918236
this huge size difference is due to GC data for runtime.mheap
(NOPTR dataflag is not obeyed).
R=rsc, dave
CC=golang-dev
https://golang.org/cl/6547051
変更の背景
このコミットは、Goコンパイラの一部であるcmd/5cにおけるデータフラグの注釈に関するバグを修正することを目的としています。具体的には、bin/goバイナリのサイズが非常に大きい(約14.7MBから約6.3MBへ減少)という問題に対処しています。このサイズの差は、runtime.mheapのためのGC(ガベージコレクション)データが原因であり、NOPTRデータフラグが正しく適用されていないために発生していました。
NOPTRデータフラグは、特定のメモリ領域にポインタが含まれていないことを示すために使用されます。ガベージコレクタは、ポインタを含まないことが保証されているメモリ領域をスキャンする必要がないため、このフラグはGCの効率を向上させ、バイナリサイズを削減するのに役立ちます。しかし、このバグにより、runtime.mheapに関連するデータがNOPTRとして認識されず、ガベージコレクタが不要なスキャンを実行し、その結果としてバイナリに余分なGCデータが含まれてしまっていました。
この修正は、NOPTRデータフラグが正しく解釈され、runtime.mheapのようなポインタを含まないデータ領域がGCの対象から適切に除外されるようにすることで、バイナリサイズの肥大化を防ぎ、Goプログラムの効率を向上させます。
前提知識の解説
cmd/5c コンパイラ
cmd/5cは、Goツールチェーンの一部であり、ARMアーキテクチャ向けのCコンパイラです。Goツールチェーンは、Plan 9ツールチェーンの子孫であり、5c、6c、8cといったコンパイラは、それぞれARM、AMD64、386アーキテクチャ向けのCファイルをコンパイルするために使用されます。これらのツールは通常、Goソースコードのsrc/cmd/サブディレクトリに配置されています。cmd/5cは、Cgo(GoパッケージがCコードを呼び出すことを可能にする機能)を使用する際に、Cファイルをコンパイルするために利用されます。
dataflag annotation (NOPTR)
NOPTRは、Goのランタイムおよびリンカのコンテキストで使用される概念で、特にNOPTRBSS(No-Pointer BSS)に関連しています。NOPTRBSSは、ポインタを含まないことが保証されている未初期化データのためのメモリ領域を指します。これは、Goのガベージコレクタの効率を向上させるための内部的な最適化です。このメモリセグメントにはポインタが含まれていないことが既知であるため、ガベージコレクタはそれをスキャンする必要がなく、ガベージコレクションのオーバーヘッドを削減します。これは、大規模なポインタを含まない未初期化データをより効果的に管理するのに役立ちます。
NOPTRは、Go開発者がコードに直接追加するような「データフラグ注釈」ではありません。むしろ、Goツールチェーンが低レベルで特定のデータセグメントを処理する方法の特性です。
runtime.mheap とガベージコレクション
Goのランタイムにおいて、mheapはメモリ割り当てとガベージコレクションシステムの重要なコンポーネントです。これは「mallocヒープ」を表し、動的に割り当てられたGoオブジェクトが主に存在する領域です。
mheapは、8192バイトのページ単位でメモリを管理します。Goがオブジェクトのためにメモリを割り当てる必要がある場合、以下の階層に従います。
- まず、プロセッサごとのキャッシュ(
mcache)から割り当てを試みます。 mcacheにスペースがない場合、中央リスト(mcentral)から「スパン」(mspan)を要求します。mcentralが空の場合、mheapからページの連続したブロックを取得します。mheap自体が空であるか、十分な大きなページブロックがない場合、オペレーティングシステムからさらにメモリを要求します。
mheapはメモリをmspansに整理します。mspansは連続したページのブロックであり、ガベージコレクションされるヒープオブジェクトのためのmSpanInUseや、手動で管理されるメモリ(ゴルーチンのスタックなど)のためのmSpanManualなど、さまざまな状態を持つことができます。
Goのガベージコレクタ(GC)は、主にmheapによって管理されるメモリ上で動作します。GCは、並行で非世代的なトライカラーマークアンドスイープアルゴリズムを使用して、プログラムから到達不能になったオブジェクトを識別し、メモリを再利用します。
このコミットの文脈では、runtime.mheapに関連するデータがNOPTRとして正しく扱われないことで、ガベージコレクタが不要なスキャンを実行し、その結果としてバイナリサイズが肥大化するという問題が発生していました。
技術的詳細
このコミットの技術的な核心は、cmd/5cコンパイラがATEXT(テキストセグメント)とAGLOBL(グローバルデータセグメント)のデータフラグをどのように処理するかを修正することにあります。
以前のコードでは、ATEXTとAGLOBLの処理が別々に記述されていました。ATEXTの場合のみtextflagが適用され、AGLOBLの場合はp->reg = 0が設定されていました。この分離が問題を引き起こしていました。
NOPTRデータフラグは、そのメモリ領域にポインタが含まれていないことをガベージコレクタに伝えるために重要です。runtime.mheapのような特定のグローバルデータは、ポインタを含まないように設計されている場合があります。しかし、AGLOBLがATEXTとは異なる方法で処理されていたため、NOPTRの意図が正しく伝わらず、ガベージコレクタがこれらの領域を不必要にスキャンし、その結果としてバイナリに余分なGCメタデータが含まれてしまっていました。
修正後のコードでは、ATEXTとAGLOBLの両方が同じif条件if(a == ATEXT || a == AGLOBL)の下で処理されるようになりました。これにより、textflagの適用が両方のタイプに一貫して行われるようになります。特に、AGLOBLがこの新しい統合されたロジックの下に入ることで、NOPTRのようなデータフラグが正しく解釈され、ガベージコレクタがポインタを含まないグローバルデータ領域を不必要にスキャンするのを防ぐことができます。
この変更により、bin/goバイナリから不要なGCデータが削除され、ファイルサイズが大幅に削減されました。これは、コンパイラがメモリ領域の特性(ポインタの有無)をより正確にガベージコレクタに伝えるようになった結果です。
コアとなるコードの変更箇所
diff --git a/src/cmd/5c/txt.c b/src/cmd/5c/txt.c
index 7738de1c30..b8675fe60f 100644
--- a/src/cmd/5c/txt.c
+++ b/src/cmd/5c/txt.c
@@ -1176,17 +1176,15 @@ patch(Prog *op, int32 pc)\n void
gpseudo(int a, Sym *s, Node *n)\n {\n-\n \tnextpc();
\tp->as = a;
\tp->from.type = D_OREG;
\tp->from.sym = s;
\tp->from.name = D_EXTERN;
-\tif(a == ATEXT) {\n+\tif(a == ATEXT || a == AGLOBL) {\
\t\tp->reg = textflag;
\t\ttextflag = 0;
-\t} else if(a == AGLOBL)\n-\t\tp->reg = 0;\
+\t}\
\tif(s->class == CSTATIC)\
\t\tp->from.name = D_STATIC;
\tnaddr(n, &p->to);
コアとなるコードの解説
変更はsrc/cmd/5c/txt.cファイルのgpseudo関数内で行われています。この関数は、擬似命令(pseudo-instructions)を生成する役割を担っています。
元のコードでは、ATEXT(テキストセグメント、つまり実行可能コード)とAGLOBL(グローバルデータセグメント)の処理が以下のように分かれていました。
if(a == ATEXT) {
p->reg = textflag;
textflag = 0;
} else if(a == AGLOBL)
p->reg = 0;
このロジックでは、ATEXTの場合にのみtextflagが設定され、AGLOBLの場合は単にp->reg = 0が設定されていました。textflagは、Goのコンパイラとリンカが特定のセグメントの特性(例えば、ポインタが含まれているかどうか)を識別するために使用する内部的なフラグです。
修正後のコードは以下のようになります。
if(a == ATEXT || a == AGLOBL) {
p->reg = textflag;
textflag = 0;
}
この変更により、ATEXTとAGLOBLの両方が同じifブロック内で処理されるようになりました。これは、AGLOBLもATEXTと同様にtextflagの影響を受けることを意味します。
具体的には、NOPTRデータフラグが正しく機能するためには、AGLOBLセグメントもtextflagを通じてその特性が適切に伝達される必要がありました。以前のコードでは、AGLOBLがtextflagのロジックから外れていたため、NOPTRとしてマークされるべきデータが正しく認識されず、ガベージコレクタがその領域をスキャンしてしまい、結果としてバイナリに不要なGCメタデータが追加されていました。
この修正によって、AGLOBLセグメントもtextflagの制御下に置かれることで、NOPTRのような重要なデータフラグが正しく適用され、ガベージコレクタがポインタを含まないグローバルデータ領域を不必要にスキャンすることがなくなりました。これにより、bin/goバイナリのサイズが大幅に削減されたのです。
関連リンク
- Go Gerrit Change-Id: https://golang.org/cl/6547051