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

[インデックス 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コンパイラの構造と、データ初期化の基本的なメカニズムに関する知識が必要です。

  1. Goコンパイラ (cmd/gc / cmd/compile): Goの公式コンパイラは、ソースコードを機械語に変換する役割を担います。このコンパイラは、フロントエンド(構文解析、型チェックなど)とバックエンド(コード生成、最適化など)に分かれています。データ初期化に関する処理は、主にバックエンドのコード生成フェーズで行われます。

  2. リンカ (cmd/link / liblink): コンパイラによって生成されたオブジェクトファイル(.oファイル)は、リンカによって結合され、最終的な実行可能ファイルが生成されます。リンカは、異なるオブジェクトファイル間の参照を解決し、プログラムの各セクション(コード、データ、BSSなど)を適切に配置します。

  3. DATA命令: Goコンパイラが生成するアセンブリコードには、DATAという擬似命令(pseudo-instruction)が存在します。これは、特定のシンボル(変数など)のデータセクションに値を配置するための指示です。例えば、DATA $sym+offset, $valueのような形式で、リンカに対して「symというシンボルのoffset位置にvalueを配置せよ」と伝えます。 このDATA命令は、コンパイラが直接メモリを操作するのではなく、リンカにその作業を委ねるための抽象化レイヤーとして機能します。

  4. シンボルとデータセクション: プログラム内の変数や定数は、コンパイル時にシンボルとして扱われます。これらのシンボルが持つ初期値は、実行可能ファイルのデータセクションに格納されます。データセクションは、プログラムが起動する際にメモリにロードされ、変数の初期値として使用されます。

  5. リロケーション (Relocation): リロケーションとは、プログラムがメモリにロードされる際に、絶対アドレスが確定していないシンボル参照を解決するプロセスです。例えば、グローバル変数のアドレスは、コンパイル時には確定せず、リンカが最終的な配置を決定した後に初めて確定します。DATA命令がポインタや他のシンボルへの参照を含む場合、リンカはリロケーション情報を利用して、これらの参照を正しいメモリ位置に調整します。

このコミット以前は、整数定数であってもDATA命令を介してリンカに処理を委ねていました。この変更は、その間接的なステップを排除し、コンパイラが直接シンボルの値を設定することで、効率化を図るものです。

技術的詳細

このコミットの技術的な核心は、Goコンパイラがデータ初期化を処理する方法の変更にあります。具体的には、duintxxという関数(uintxxuint8, 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つのファイルが変更されています。

  1. src/cmd/5g/gobj.c (ARMアーキテクチャ向けコンパイラのオブジェクト生成コード)
  2. src/cmd/6g/gobj.c (x86-64アーキテクチャ向けコンパイラのオブジェクト生成コード)
  3. src/cmd/8g/gobj.c (x86アーキテクチャ向けコンパイラのオブジェクト生成コード)
  4. 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コンパイラの設計思想である「シンプルさと効率性」を追求したものであり、特にコンパイル時間の短縮という実用的なメリットをもたらしました。

関連リンク

参考にした情報源リンク