[インデックス 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