[インデックス 18101] ファイルの概要
このコミットは、Goコンパイラのcmd/gc
(現在のcmd/compile
)において、整数定数で初期化されるデータに対するDATA
命令の生成をバイパスする変更を導入しています。これにより、コンパイル時間とメモリ使用量の削減を目指しています。
コミット
commit 4acb70d3772f5904095eb9641367625917cfa780
Author: Russ Cox <rsc@golang.org>
Date: Fri Dec 20 14:24:39 2013 -0500
cmd/gc: bypass DATA instruction for data initialized to integer constant
Eventually we will want to bypass DATA for everything,
but the relocations are not standardized well enough across
architectures to make that possible.
This did not help as much as I expected, but it is definitely better.
It shaves maybe 1-2% off all.bash depending on how much you
trust the timings of a single run:
Before: 241.139r 362.702u 112.967s
After: 234.339r 359.623u 111.045s
R=golang-codereviews, gobot, r, iant
CC=golang-codereviews
https://golang.org/cl/44650043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4acb70d3772f5904095eb9641367625917cfa780
元コミット内容
cmd/gc: bypass DATA instruction for data initialized to integer constant
Eventually we will want to bypass DATA for everything,
but the relocations are not standardized well enough across
architectures to make that possible.
This did not help as much as I expected, but it is definitely better.
It shaves maybe 1-2% off all.bash depending on how much you
trust the timings of a single run:
Before: 241.139r 362.702u 112.967s
After: 234.339r 359.623u 111.045s
R=golang-codereviews, gobot, r, iant
CC=golang-codereviews
https://golang.org/cl/44650043
変更の背景
Goコンパイラ(cmd/gc
、現在のcmd/compile
)は、プログラムのデータセクションを構築する際に、初期化されたデータのためにDATA
命令を生成していました。このDATA
命令は、リンカ(cmd/link
、以前はliblink
)によって解釈され、最終的な実行可能ファイルのデータセクションに配置されます。
しかし、このプロセスにはオーバーヘッドがありました。コンパイラがDATA
命令を生成し、それをリンカが後で解釈して処理するという二段階の処理は、コンパイル時間とメモリ使用量の増加につながります。特に、整数定数で初期化されるような単純なデータの場合、この間接的な処理は非効率的でした。
このコミットの目的は、このような単純なケースにおいてDATA
命令の生成をバイパスし、コンパイラが直接シンボルデータ(メモリ上のシンボルの値)を更新するように変更することです。これにより、リンカの負担を軽減し、コンパイルプロセス全体の効率を向上させることが期待されました。コミットメッセージにあるように、all.bash
(Goのビルドスクリプト)の実行時間が1-2%短縮されるという効果が見られました。将来的には、より複雑なデータ初期化についても同様の最適化を行うことが視野に入れられていましたが、アーキテクチャ間のリロケーション(再配置)の標準化が課題となっていました。
前提知識の解説
この変更を理解するためには、Goコンパイラの構造と、データ初期化の基本的なメカニズムに関する知識が必要です。
-
Goコンパイラ (
cmd/gc
/cmd/compile
): Goの公式コンパイラは、ソースコードを機械語に変換する役割を担います。このコンパイラは、フロントエンド(構文解析、型チェックなど)とバックエンド(コード生成、最適化など)に分かれています。データ初期化に関する処理は、主にバックエンドのコード生成フェーズで行われます。 -
リンカ (
cmd/link
/liblink
): コンパイラによって生成されたオブジェクトファイル(.o
ファイル)は、リンカによって結合され、最終的な実行可能ファイルが生成されます。リンカは、異なるオブジェクトファイル間の参照を解決し、プログラムの各セクション(コード、データ、BSSなど)を適切に配置します。 -
DATA
命令: Goコンパイラが生成するアセンブリコードには、DATA
という擬似命令(pseudo-instruction)が存在します。これは、特定のシンボル(変数など)のデータセクションに値を配置するための指示です。例えば、DATA $sym+offset, $value
のような形式で、リンカに対して「sym
というシンボルのoffset
位置にvalue
を配置せよ」と伝えます。 このDATA
命令は、コンパイラが直接メモリを操作するのではなく、リンカにその作業を委ねるための抽象化レイヤーとして機能します。 -
シンボルとデータセクション: プログラム内の変数や定数は、コンパイル時にシンボルとして扱われます。これらのシンボルが持つ初期値は、実行可能ファイルのデータセクションに格納されます。データセクションは、プログラムが起動する際にメモリにロードされ、変数の初期値として使用されます。
-
リロケーション (Relocation): リロケーションとは、プログラムがメモリにロードされる際に、絶対アドレスが確定していないシンボル参照を解決するプロセスです。例えば、グローバル変数のアドレスは、コンパイル時には確定せず、リンカが最終的な配置を決定した後に初めて確定します。
DATA
命令がポインタや他のシンボルへの参照を含む場合、リンカはリロケーション情報を利用して、これらの参照を正しいメモリ位置に調整します。
このコミット以前は、整数定数であってもDATA
命令を介してリンカに処理を委ねていました。この変更は、その間接的なステップを排除し、コンパイラが直接シンボルの値を設定することで、効率化を図るものです。
技術的詳細
このコミットの技術的な核心は、Goコンパイラがデータ初期化を処理する方法の変更にあります。具体的には、duintxx
という関数(uintxx
はuint8
, uint16
, uint32
, uint64
などの総称で、任意のサイズの符号なし整数を意味します)の動作が変更されました。
変更前は、duintxx
関数は、指定されたシンボルs
のオフセットoff
に、値v
(幅wid
)を書き込むためにDATA
命令を生成していました。このDATA
命令は、gins(ADATA, N, N)
という呼び出しによって生成され、Prog
構造体(アセンブリ命令を表す)としてリンカに渡されます。リンカは後でこのProg
を解釈し、実際のデータセクションに値を書き込みます。
変更後は、duintxx
関数はDATA
命令を生成する代わりに、setuintxx(ctxt, linksym(s), off, v, wid)
という新しい関数を呼び出すようになりました。このsetuintxx
関数は、リンカが後で解釈するProg
構造体を介さずに、直接シンボルs
のデータ(s->lsym->data
など)を更新します。
この直接的な更新の利点は以下の通りです。
- 中間表現の削減:
DATA
命令という中間表現を生成し、それをリンカが解釈するというステップが不要になります。これにより、コンパイラとリンカ間のデータ転送量と処理が削減されます。 - リンカの負担軽減: リンカは、
DATA
命令を解釈してデータセクションに値を書き込むという作業から解放されます。これにより、リンカの処理が高速化されます。 - メモリ使用量の削減:
DATA
命令を表すProg
構造体や関連するデータ構造をメモリに保持する必要がなくなるため、コンパイラとリンカのメモリ使用量が削減されます。
コミットメッセージにあるように、この最適化は特に整数定数に限定されています。これは、ポインタや他のシンボルへの参照を含むデータの場合、リロケーション処理が必要となり、その処理がアーキテクチャ間で十分に標準化されていないため、直接的なデータ更新が困難であるためです。リロケーションが必要なデータについては、引き続きDATA
命令を介してリンカに処理を委ねる必要があります。
この変更は、Goコンパイラのバックエンドにおけるコード生成の効率化の一例であり、コンパイル時間の短縮とリソース使用量の削減に貢献します。
コアとなるコードの変更箇所
このコミットでは、主に以下の4つのファイルが変更されています。
src/cmd/5g/gobj.c
(ARMアーキテクチャ向けコンパイラのオブジェクト生成コード)src/cmd/6g/gobj.c
(x86-64アーキテクチャ向けコンパイラのオブジェクト生成コード)src/cmd/8g/gobj.c
(x86アーキテクチャ向けコンパイラのオブジェクト生成コード)src/cmd/gc/obj.c
(コンパイラの共通オブジェクト生成コード)
具体的な変更内容は以下の通りです。
src/cmd/5g/gobj.c
,src/cmd/6g/gobj.c
,src/cmd/8g/gobj.c
から、duintxx
関数の実装が削除されました。これらのファイルでは、duintxx
関数がgins(ADATA, N, N)
を呼び出してDATA
命令を生成していました。src/cmd/gc/obj.c
に、新しいduintxx
関数の実装が追加されました。この新しい実装は、DATA
命令を生成する代わりに、setuintxx(ctxt, linksym(s), off, v, wid)
を呼び出しています。
変更前 (src/cmd/5g/gobj.c
の例):
int
duintxx(Sym *s, int off, uint64 v, int wid)
{
Prog *p;
off = rnd(off, wid);
p = gins(ADATA, N, N); // DATA命令を生成
p->from.type = D_OREG;
p->from.name = D_EXTERN;
p->from.sym = linksym(s);
p->from.offset = off;
p->reg = wid;
p->to.type = D_CONST;
p->to.name = D_NONE;
p->to.offset = v;
off += wid;
return off;
}
変更後 (src/cmd/gc/obj.c
の新しい実装):
int
duintxx(Sym *s, int off, uint64 v, int wid)
{
// Update symbol data directly instead of generating a
// DATA instruction that liblink will have to interpret later.
// This reduces compilation time and memory usage.
off = rnd(off, wid);
return setuintxx(ctxt, linksym(s), off, v, wid); // 直接シンボルデータを更新
}
コアとなるコードの解説
この変更の核心は、duintxx
関数の役割の変更と、setuintxx
関数の導入にあります。
変更前のduintxx
(各アーキテクチャのgobj.c
内):
この関数は、Goコンパイラのバックエンドの一部として、特定のアーキテクチャ(5gはARM、6gはx86-64、8gはx86)向けのアセンブリコードを生成する際に呼び出されていました。duintxx
の目的は、指定されたシンボルs
のデータ領域に、v
という整数値をwid
バイト幅で書き込むためのDATA
命令を生成することでした。
gins(ADATA, N, N)
は、ADATA
というオペコードを持つ新しいProg
(プログラム命令)構造体を作成します。このProg
構造体には、データの書き込み先(p->from
、シンボルs
とオフセットoff
)、書き込む値(p->to
、定数v
)、およびデータの幅(p->reg
)が設定されます。
これらのProg
構造体は、コンパイルの最終段階でリンカに渡され、リンカがこれらを解釈して実際の実行可能ファイルのデータセクションを構築していました。
変更後のduintxx
(共通のobj.c
内):
新しいduintxx
関数は、アーキテクチャ固有のgobj.c
ファイルから削除され、共通のobj.c
ファイルに移動されました。そして、その実装が根本的に変更されました。
新しいduintxx
は、gins(ADATA, N, N)
を呼び出す代わりに、setuintxx(ctxt, linksym(s), off, v, wid)
を呼び出します。
setuintxx
関数は、DATA
命令のような中間表現を生成するのではなく、直接シンボルs
に関連付けられたデータ領域にv
の値を書き込みます。
linksym(s)
は、コンパイラ内部のシンボル表現から、リンカが理解できるシンボル表現(LSym
)への変換を行います。ctxt
はコンテキスト情報、off
はシンボル内のオフセット、v
は書き込む値、wid
はデータの幅です。
この直接書き込みにより、リンカがDATA
命令を解釈して処理する手間が省かれ、コンパイルプロセスが効率化されます。
この変更は、Goコンパイラの設計思想である「シンプルさと効率性」を追求したものであり、特にコンパイル時間の短縮という実用的なメリットをもたらしました。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Goコンパイラのソースコード: https://github.com/golang/go/tree/master/src/cmd/compile
- Goリンカのソースコード: https://github.com/golang/go/tree/master/src/cmd/link
参考にした情報源リンク
- Goのコミットメッセージと差分: https://github.com/golang/go/commit/4acb70d3772f5904095eb9641367625917cfa780
- Goのコードレビューシステム (Gerrit): https://golang.org/cl/44650043 (コミットメッセージに記載されているCLリンク)
- Goコンパイラの内部構造に関する一般的な情報源 (例: Goのコンパイラに関するブログ記事やドキュメント)
- A Tour of the Go Compiler: https://go.dev/blog/compiler
- Go Compiler Internals (古い情報も含むが概念理解に役立つ): https://go.dev/doc/articles/go_compiler_internals.html
- アセンブリ言語とリンカの基本的な概念に関する情報源 (一般的なコンピュータサイエンスの知識)