[インデックス 16778] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) とランタイム (runtime
) におけるインメモリシンボルテーブルの新しいフォーマット導入に関するものです。これは、Go 1.2リリースに向けて、ガベージコレクタのメタデータ処理を改善し、ランタイムの効率性を高めるための基盤となる変更です。
コミット
- コミットハッシュ:
5d363c6357ebcacc8ba7c24420f7cfd2e530591a
- Author: Russ Cox rsc@golang.org
- Date: Tue Jul 16 09:41:38 2013 -0400
- Subject: cmd/ld, runtime: new in-memory symbol table format
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5d363c6357ebcacc8ba7c24420f7cfd2e530591a
元コミット内容
cmd/ld, runtime: new in-memory symbol table format
Design at http://golang.org/s/go12symtab.
This enables some cleanup of the garbage collector metadata
that will be done in future CLs.
This CL does not move the old symtab and pclntab back into
an unmapped section of the file. That's a bit tricky and will be
done separately.
Fixes #4020.
R=golang-dev, dave, cshapiro, iant, r
CC=golang-dev, nigeltao
https://golang.org/cl/11085043
変更の背景
Go言語の初期バージョンでは、プログラムの起動時にシンボルテーブルと pc->line
(pcln) テーブルのデコードにかなりの時間とメモリを費やしていました。これは、特に大規模なGoプログラムにおいて起動時間のボトルネックとなる可能性がありました。また、コンパイラからランタイムへ新しいメタデータを渡す際に、リンカを介した複数回のエンコード/デコードが必要となり、柔軟性に欠けるという問題も抱えていました。
このコミットは、これらの問題を解決するために、Go 1.2で導入された新しいインメモリシンボルテーブルフォーマットの基盤を構築します。主な目的は以下の通りです。
- 起動時間の改善: シンボルテーブルのデコード処理を不要にすることで、プログラムの起動を高速化します。
- メモリフットプリントの削減: ランタイムが使用するメモリ量を削減します。
- ガベージコレクタの効率化: ガベージコレクタがスタックをスキャンする際に必要となるメタデータ(スタックフレームサイズ、ポインタ情報など)を、より効率的に取得できるようにします。これにより、ガベージコレクションのパフォーマンスが向上します。
- メタデータ伝達の簡素化: コンパイラが生成するメタデータをランタイムが直接利用できる形式にすることで、開発の柔軟性を高めます。
前提知識の解説
- シンボルテーブル (Symbol Table): プログラム内の関数、変数、型などのシンボル(名前)と、それらがメモリ上のどこに配置されているか(アドレス)を対応付けるデータ構造です。デバッガやプロファイラがプログラムの実行状態を解析する際に利用されます。
pc->line
テーブル (PCLN Table): プログラムカウンタ (PC) の値(実行中の命令のアドレス)から、対応するソースコードのファイル名と行番号を特定するためのテーブルです。スタックトレースの生成やデバッグ情報に不可欠です。- ガベージコレクション (Garbage Collection - GC): プログラムが動的に確保したメモリ領域のうち、もはや使用されていない(参照されていない)ものを自動的に解放する仕組みです。GoのGCは、スタック上のポインタを正確に識別し、ヒープ上のオブジェクトをマーク&スイープすることで動作します。この際、関数ごとのスタックフレームのレイアウト(どこにポインタがあるか)に関するメタデータが必要となります。
- PC-Value テーブル (PC-Value Table):
pc->line
テーブルの概念を一般化したものです。これは、プログラムカウンタ (PC) の値(コード上の位置)と、それに対応する任意のint32
値をマッピングするテーブルです。例えば、以下のような情報を表現できます。pcsp
テーブル: PCからスタックポインタの調整量(スタックフレームサイズ)をマッピングします。スタックの巻き戻し(unwinding)に利用されます。pcfile
テーブル: PCからファイル番号をマッピングします。pcln
テーブル: PCから行番号をマッピングします。pcdata
テーブル: PCからガベージコレクションに必要なポインタ情報などのカスタムデータをマッピングします。
- 可変長整数 (Varint): データを効率的にエンコードするための手法の一つで、特に小さな数値を少ないバイト数で表現できます。Goのシンボルテーブルでは、値の差分(デルタ)を可変長整数でエンコードすることで、テーブル全体のサイズを削減しています。
- Zig-zag エンコーディング: 符号付き整数を可変長整数でエンコードする際に、負の数も効率的に表現できるようにする手法です。正の数と負の数を交互に配置することで、絶対値が小さい数値が常に少ないビット数で表現されるようにします。
技術的詳細
このコミットの核となるのは、Goランタイムが直接利用できる新しいインメモリシンボルテーブルフォーマットの導入です。これは、http://golang.org/s/go12symtab
で設計が詳細に説明されています。
pclntab
シンボル: 以前は複数のセクションに分散していたシンボル情報とpclnテーブルが、pclntab
という単一の連続したメモリ領域にまとめられます。これにより、デバッガがGoのシンボル情報をロードしやすくなります。struct Func
の再定義: ランタイムが各関数に関するメタデータ(エントリPC、関数名、引数サイズ、フレームサイズ、各種PC-Valueテーブルへのオフセットなど)を格納するためのstruct Func
が再定義されました。src/pkg/runtime/runtime.h
でstruct Func
の定義が変更され、name
,entry
,args
,locals
,frame
といったフィールドに加え、pcsp
,pcfile
,pcln
,npcdata
,nfuncdata
,ptrsoff
,ptrslen
といった新しいフィールドが追加されています。これらは、PC-Valueテーブルへのオフセットや、関数に関連するデータの数を示します。
- PC-Value テーブルの導入:
pcsp
(PC to stack pointer adjustment),pcfile
(PC to file number),pcln
(PC to line number) といったPC-Valueテーブルが導入され、これらは関数ごとに格納されます。これらのテーブルは、PCのデルタと値のデルタを可変長整数とZig-zagエンコーディングで効率的に表現します。 - 新しい擬似命令 (
FUNCDATA
,PCDATA
): コンパイラは、FUNCDATA
とPCDATA
という新しい擬似命令を使用して、カスタムの関数ごとのメタデータやPC-Valueテーブルを直接生成できるようになります。これにより、リンカを介さずにコンパイラからランタイムへ直接情報を渡すことが可能になり、柔軟性が向上します。AFUNCDATA
: 関数に関連するデータ(例: GCポインタマップ)を定義します。APCDATA
: PC-Valueテーブルの値を定義します。
- ストレージオーバーヘッドの削減: 新しいテーブル形式は、以前のPlan 9ベースのテーブルと比較して、実行イメージのサイズを約2MB削減するとされています(読み取り専用データが1MB、デコード済みデータが1MB削減)。これは、デバッガ固有の情報がランタイムから分離されたためです。ただし、実行可能ファイル自体のサイズは、デバッグ情報が別途格納されるため、1MB程度増加する可能性があります。
functab
とfiletab
の初期化: リンカは、functab
とfiletab
というシンボルを初期化します。functab
: すべてのGo関数のエントリポイントと、それに対応するFunc
構造体へのポインタのリストを格納します。filetab
: ソースファイル名とそれに対応するファイル番号のリストを格納します。
savehist
とgetline
: 履歴スタック(インクルードファイルのネストなど)を処理し、仮想的な行番号から実際のファイル名と行番号を解決するためのsavehist
とgetline
関数が導入されています。これにより、ランタイムがソースコードの行情報を正確に取得できるようになります。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
src/cmd/ld/lib.c
: リンカの主要なロジックが含まれており、新しいシンボルテーブルの構築とfunctab
、filetab
の生成ロジックが追加されています。特にfunctab()
関数が新設されています。src/cmd/ld/lib.h
: リンカのヘッダファイルで、新しいシンボルタイプ (SGOFUNC
,SFUNCTAB
,SFILEPATH
) や、functab
、savehist
、getline
などの新しい関数のプロトタイプが追加されています。src/cmd/ld/symtab.c
: リンカのシンボルテーブル関連の処理が含まれており、新しいシンボルタイプ (SGOFUNC
) の定義と処理が追加されています。src/pkg/runtime/runtime.h
: Goランタイムのヘッダファイルで、struct Func
の定義が新しいインメモリシンボルテーブルフォーマットに合わせて変更されています。src/pkg/runtime/symtab.c
: Goランタイムのシンボルテーブル解析ロジックが含まれており、新しいpclntab
フォーマットを読み取るためのruntime·symtabinit
やpcvalue
などの関数が実装されています。以前のPlan 9形式のシンボルテーブル解析ロジックは削除されています。src/pkg/runtime/extern.go
: Goのruntime
パッケージの外部インターフェースで、Func
構造体の定義が変更され、FuncForPC
,Name
,Entry
,FileLine
などのメソッドが新しいFunc
構造体に対応するように修正されています。src/cmd/5l/
,src/cmd/6l/
,src/cmd/8l/
(各アーキテクチャのリンカ):ALOCALS
,ATYPE
などの古い擬似命令が削除され、AFUNCDATA
,APCDATA
などの新しい擬似命令が追加されています。また、functab()
の呼び出しが追加されています。
src/cmd/ld/lib.c
の変更点 (抜粋)
// 新しい関数: addvarint
void
addvarint(Sym *s, uint32 val)
{
// 可変長整数をシンボルに書き込むロジック
}
// 新しい関数: funcpctab
void
funcpctab(Sym *dst, Sym *func, char *desc, int32 (*valfunc)(Sym*, int32, Prog*, int32, int32), int32 arg)
{
// PC-Valueテーブルを生成するロジック
// valfunc を使用して、PCごとの値を計算し、デルタエンコーディングで書き込む
}
// 新しい関数: pctofileline
static int32
pctofileline(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
// PCからファイル番号または行番号を計算するロジック
}
// 新しい関数: pctospadj
static int32
pctospadj(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
// PCからスタックポインタの調整量を計算するロジック
}
// 新しい関数: pctopcdata
static int32
pctopcdata(Sym *sym, int32 oldval, Prog *p, int32 phase, int32 arg)
{
// PCからPCDATAの値を計算するロジック
}
// 新しい関数: savehist
void
savehist(int32 line, int32 off)
{
// 履歴スタック(インクルードファイルのネストなど)を保存するロジック
}
// 新しい関数: gethist
Hist*
gethist(void)
{
// 現在の履歴スタックを取得するロジック
}
// 新しい関数: getline
void
getline(Hist *h, int32 line, int32 *f, int32 *l)
{
// 履歴スタックと仮想的な行番号から実際のファイル番号と行番号を解決するロジック
}
// 新しい関数: defgostring
Sym*
defgostring(char *text)
{
// Go文字列を定義するシンボルを返すロジック
}
// 新しい関数: addpctab
static int32
addpctab(Sym *f, int32 off, Sym *func, char *desc, int32 (*valfunc)(Sym*, int32, Prog*, int32, int32), int32 arg)
{
// PC-Valueテーブルをシンボルに追加し、そのオフセットを返すロジック
}
// 新しい関数: functab
void
functab(void)
{
// functab と filetab シンボルを初期化し、ランタイム関数とファイル名情報を格納するロジック
// 各関数について、そのメタデータ(名前、エントリポイント、引数/ローカルサイズ、各種PC-Valueテーブルへのオフセット)を
// go.func.<funcname> シンボルとして構築する
}
src/pkg/runtime/runtime.h
の変更点 (抜粋)
// layout of in-memory per-function information prepared by linker
// See http://golang.org/s/go12symtab.
struct Func
{
String *name; // function name
uintptr entry; // start pc
// TODO: Remove these fields.
int32 args; // in/out args size
int32 locals; // locals size
int32 frame; // legacy frame size; use pcsp if possible
int32 ptrsoff; // offset to pointer map
int32 ptrslen; // length of pointer map
int32 pcsp; // offset to pc-value table for stack pointer adjustment
int32 pcfile; // offset to pc-value table for file number
int32 pcln; // offset to pc-value table for line number
int32 npcdata; // number of pcdata tables
int32 nfuncdata; // number of funcdata tables
};
src/pkg/runtime/symtab.c
の変更点 (抜粋)
// 以前のシンボルテーブル解析ロジックは大幅に削除され、新しい形式に対応
// See http://golang.org/s/go12symtab for an overview.
// 新しい関数: runtime·symtabinit
void
runtime·symtabinit(void)
{
// 新しいfunctabとfiletabを初期化し、ランタイムが利用できるようにする
// functabがPC順にソートされているか検証する
}
// 新しい関数: readvarint
static uint32
readvarint(byte **pp)
{
// 可変長整数を読み取るロジック
}
// 新しい関数: funcdata
static uintptr
funcdata(Func *f, int32 i)
{
// Func構造体から指定されたfuncdataの値を返すロジック
}
// 新しい関数: pcvalue
static int32
pcvalue(Func *f, int32 off, uintptr targetpc)
{
// PC-Valueテーブルを解析し、指定されたPCに対応する値を返すロジック
// デルタエンコーディングとZig-zagエンコーディングをデコードする
}
コアとなるコードの解説
このコミットの核心は、Goの実行可能ファイル内に埋め込まれるシンボル情報とデバッグ情報の構造を根本的に変更した点にあります。
-
リンカ (
cmd/ld
) 側の変更:functab()
関数の導入: この関数は、すべてのGo関数に関する詳細なメタデータを含むgo.func.<funcname>
シンボルを構築します。このメタデータには、関数名、エントリポイント、引数とローカル変数のサイズ、そして最も重要なpcsp
、pcfile
、pcln
といったPC-Valueテーブルへのオフセットが含まれます。これらのPC-Valueテーブルは、funcpctab
関数によって生成され、PCのデルタと値のデルタを可変長整数とZig-zagエンコーディングで効率的に格納します。savehist()
とgetline()
: ソースファイルのインクルード階層を追跡し、仮想的な行番号から実際のファイル名と行番号を解決するためのメカニズムが導入されました。これは、コンパイラが生成する履歴情報(z
シンボル)をリンカが解釈し、ランタイムが利用できるようにするためのものです。- 新しいシンボルタイプ:
SGOFUNC
(Go関数情報)、SFUNCTAB
(関数テーブル)、SFILEPATH
(ファイルパス) といった新しいシンボルタイプが導入され、リンカがこれらの情報を適切に管理・出力できるようになりました。 - アーキテクチャ固有のリンカの更新:
5l
,6l
,8l
(386, AMD64, ARM) の各リンカで、古い擬似命令が新しいAFUNCDATA
やAPCDATA
に置き換えられ、functab()
の呼び出しが追加されました。これにより、コンパイラが生成する新しい形式のメタデータがリンカによって正しく処理されるようになります。
-
ランタイム (
runtime
) 側の変更:struct Func
の再設計: ランタイムが利用するstruct Func
は、リンカが生成する新しいメタデータフォーマットに直接対応するように変更されました。これにより、ランタイムはシンボルテーブルをデコードすることなく、直接メモリ上のFunc
構造体から必要な情報を取得できるようになります。特に、pcsp
,pcfile
,pcln
といったPC-Valueテーブルへのオフセットが直接格納されることで、スタックトレースやGCの効率が向上します。runtime·symtabinit()
: この関数は、起動時に新しいfunctab
とfiletab
を初期化し、ランタイムが関数情報やファイル情報を参照できるようにします。以前の複雑なシンボルテーブル解析ロジックは削除され、よりシンプルで高速な初期化が可能になりました。pcvalue()
関数: この関数は、指定されたPCとPC-Valueテーブルのオフセットから、対応する値を効率的に取得します。これは、デルタエンコーディングとZig-zagエンコーディングをデコードするロジックを含んでいます。これにより、ランタイムはPCからスタックフレームサイズ、ファイル番号、行番号などの情報を高速に取得できます。mgc0.c
の変更: ガベージコレクタのスタックフレームスキャンロジックが、新しいFunc
構造体とポインタマップの形式に対応するように更新されました。これにより、GCがスタック上のポインタをより正確かつ効率的に識別できるようになります。
これらの変更により、Goの実行可能ファイルは、ランタイムが直接利用できる最適化されたシンボル情報を持つようになり、起動時間の短縮、メモリ使用量の削減、そしてガベージコレクションのパフォーマンス向上に貢献しています。
関連リンク
- Go 1.2 Runtime Symbol Information Design Document: http://golang.org/s/go12symtab
参考にした情報源リンク
- GitHub Commit: https://github.com/golang/go/commit/5d363c6357ebcacc8ba7c24420f7cfd2e530591a
- Go 1.2 Runtime Symbol Information Design Document: http://golang.org/s/go12symtab
- Go言語のソースコード (特に
src/cmd/ld/
およびsrc/pkg/runtime/
ディレクトリ) - 可変長整数 (Varint) および Zig-zag エンコーディングに関する一般的な情報