[インデックス 13895] ファイルの概要
このコミットは、Go言語のamd64
アーキテクチャ向けコンパイラ(cmd/6g
)における浮動小数点レジスタの使用効率と最適化に関する改善を目的としています。具体的には、利用可能な16個の浮動小数点レジスタをすべて活用し、浮動小数点値の移動(float moves
)を最適化することで、生成されるコードのパフォーマンス向上を図っています。
コミット
commit 57ad05db1540fd949917708a7551866a2c9e2a47
Author: Russ Cox <rsc@golang.org>
Date: Fri Sep 21 13:39:09 2012 -0400
cmd/6g: use all 16 float registers, optimize float moves
Fixes #2446.
R=ken2
CC=golang-dev
https://golang.org/cl/6557044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/57ad05db1540fd949917708a7551866a2c9e2a47
元コミット内容
cmd/6g: use all 16 float registers, optimize float moves
Fixes #2446.
R=ken2
CC=golang-dev
https://golang.org/cl/6557044
変更の背景
この変更の背景には、Goコンパイラがamd64
アーキテクチャの浮動小数点レジスタを十分に活用できていなかったという問題があります。amd64
(x86-64)アーキテクチャには、SSE/SSE2命令セットによって提供される16個のXMMレジスタ(XMM0からXMM15)が存在し、これらは浮動小数点演算に利用されます。しかし、Goコンパイラの以前の実装では、これらのレジスタのうち一部(おそらくXMM0からXMM7までの8個)しか利用しておらず、残りのレジスタが未使用の状態でした。
レジスタはCPUがデータを高速に処理するために使用する非常に高速な記憶領域です。コンパイラが利用可能なレジスタを最大限に活用することは、メモリへのアクセスを減らし、CPUのパイプラインを効率的に利用することで、プログラムの実行速度を向上させる上で極めて重要です。特に浮動小数点演算は科学技術計算やグラフィックス処理などで頻繁に用いられるため、その性能向上は多くのアプリケーションに影響を与えます。
このコミットは、Go issue #2446を修正するものであり、この問題がコミュニティによって認識され、改善が求められていたことを示唆しています。コンパイラがより多くのレジスタを利用できるようになることで、レジスタスピル(レジスタの内容をメモリに退避させる処理)が減少し、結果として生成される機械語コードの効率が向上します。
前提知識の解説
1. cmd/6g
cmd/6g
は、Go言語のツールチェインの一部であり、amd64
(x86-64)アーキテクチャ向けのGoコンパイラを指します。Go言語のソースコードをこのアーキテクチャで実行可能なバイナリに変換する役割を担っています。Goのコンパイラは、特定のアーキテクチャごとにcmd/<arch>g
という命名規則で存在します(例: cmd/8g
は386
向け、cmd/5g
はARM
向けなど)。
2. 浮動小数点レジスタ (XMMレジスタ)
amd64
アーキテクチャでは、浮動小数点演算やSIMD(Single Instruction, Multiple Data)演算のために、XMMレジスタと呼ばれる特別なレジスタセットが提供されています。
- XMMレジスタ: 128ビット幅のレジスタで、XMM0からXMM15までの合計16個が存在します。これらは単精度浮動小数点数(float32)を4つ、または倍精度浮動小数点数(float64)を2つ格納できます。
- SSE/SSE2: Streaming SIMD Extensions (SSE) および SSE2 は、Intelによって導入された命令セット拡張であり、XMMレジスタを利用して浮動小数点演算やSIMD演算を高速化します。現代のほとんどの
amd64
プロセッサはこれらの命令セットをサポートしています。
3. レジスタ割り当て (Register Allocation)
コンパイラの重要なフェーズの一つに「レジスタ割り当て」があります。これは、プログラムの変数や中間結果をCPUのレジスタにどのように割り当てるかを決定するプロセスです。
- 目的: メモリへのアクセスはCPUレジスタへのアクセスよりもはるかに遅いため、可能な限り多くの値をレジスタに保持することで、プログラムの実行速度を向上させます。
- 課題: CPUのレジスタ数は限られているため、コンパイラはどの値をレジスタに保持し、どの値をメモリに退避させるか(レジスタスピル)を賢く決定する必要があります。利用可能なレジスタが多いほど、レジスタスピルを減らし、より効率的なコードを生成できます。
4. コピー伝播 (Copy Propagation)
コピー伝播は、コンパイラの最適化手法の一つです。これは、ある変数が別の変数のコピーである場合、そのコピーされた変数の使用箇所を元の変数で置き換えることで、冗長なデータ移動を排除しようとするものです。 例:
a = b
c = a + 1
コピー伝播を適用すると、c = b + 1
となり、a
の使用が不要になる場合があります。これにより、レジスタ間の移動命令などを削減し、コードを効率化できます。
技術的詳細
このコミットは、主に以下の3つの側面でamd64
コンパイラの浮動小数点処理を改善しています。
1. 浮動小数点レジスタの利用範囲拡大 (src/cmd/6g/gsubr.c
, src/cmd/6l/6.out.h
)
src/cmd/6l/6.out.h
: このファイルは、Goリンカ(cmd/6l
)が使用するアセンブラ命令やレジスタの定義を含んでいます。変更点として、D_X8
からD_X15
までの新しい浮動小数点レジスタが列挙型enum
に追加されています。これにより、コンパイラとリンカがXMM0からXMM15までの全16個のXMMレジスタを認識し、利用できるようになります。- 以前は
D_X0
からD_X7
までしか定義されていなかったため、8個のレジスタしか利用できませんでした。
- 以前は
src/cmd/6g/gsubr.c
: このファイルは、Goコンパイラのバックエンドにおける汎用サブルーチンを含んでいます。ginit()
関数とgclean()
関数内のレジスタ初期化およびクリーンアップループが、D_X7
までではなくD_X15
までを対象とするように変更されています。これは、コンパイラがレジスタ割り当てを行う際に、全16個の浮動小数点レジスタを考慮に入れることを意味します。regalloc()
関数(レジスタ割り当てを行う関数)も同様に、浮動小数点レジスタの検索範囲をD_X0
からD_X15
に拡張しています。これにより、コンパイラは利用可能なすべての浮動小数点レジスタの中から空いているものを見つけて割り当てることができるようになります。
2. 浮動小数点移動命令の最適化 (src/cmd/6g/peep.c
)
src/cmd/6g/peep.c
: このファイルは、Goコンパイラの「ピーフホール最適化(Peephole Optimization)」を担当しています。ピーフホール最適化は、生成されたアセンブリコードの小さな「窓(peephole)」を見て、より効率的な命令シーケンスに置き換える最適化手法です。subprop()
関数は、コピー伝播最適化の一部として機能します。この関数は、特定の命令パターンを検出し、冗長なデータ移動を排除しようとします。- 変更点として、
AMOVSS
(単精度浮動小数点移動)とAMOVSD
(倍精度浮動小数点移動)命令が、コピー伝播の対象として追加されています。これにより、浮動小数点値のレジスタ間移動が冗長である場合に、コンパイラがそれを検出し、最適化できるようになります。例えば、MOVSD XMM0, XMM1
の後にMOVSD XMM1, XMM0
のような冗長な移動がある場合、これらを削除したり、より効率的なシーケンスに置き換えたりすることが可能になります。 - また、デバッグ出力(
debug['P'] && debug['v']
)が追加されており、最適化プロセスの可視性が向上しています。これは、最適化がどのように適用されているかを開発者が追跡しやすくするためのものです。
3. レジスタ番号変換ロジックの修正 (src/cmd/6g/reg.c
)
src/cmd/6g/reg.c
: このファイルは、レジスタ関連のユーティリティ関数を含んでいます。BtoF()
関数は、おそらくビットマスクから浮動小数点レジスタのインデックスを計算する関数です。- 変更点として、ビットマスクの処理が
0xFF0000L
から0xFFFF0000L
に変更されています。これは、レジスタのインデックスを正しく抽出するために、より広いビット範囲を考慮する必要があることを示唆しています。具体的には、以前は8ビット(0xFF
)のシフトされた値を見ていたのに対し、新しい変更では16ビット(0xFFFF
)のシフトされた値を見るようになっています。これは、レジスタの識別子が拡張された(XMM0-XMM15)ことに対応するための修正と考えられます。
これらの変更は全体として、Goコンパイラがamd64
アーキテクチャの浮動小数点ハードウェアをより効果的に利用し、浮動小数点演算を含むプログラムのパフォーマンスを向上させることを目的としています。
コアとなるコードの変更箇所
src/cmd/6g/gsubr.c
--- a/src/cmd/6g/gsubr.c
+++ b/src/cmd/6g/gsubr.c
@@ -271,7 +271,7 @@ ginit(void)
reg[i] = 1;
for(i=D_AX; i<=D_R15; i++)
reg[i] = 0;
- for(i=D_X0; i<=D_X7; i++)
+ for(i=D_X0; i<=D_X15; i++)
reg[i] = 0;
for(i=0; i<nelem(resvd); i++)
@@ -289,7 +289,7 @@ gclean(void)
for(i=D_AX; i<=D_R15; i++)
if(reg[i])
yyerror("reg %R left allocated\\n", i);
- for(i=D_X0; i<=D_X7; i++)
+ for(i=D_X0; i<=D_X15; i++)
if(reg[i])
yyerror("reg %R left allocated\\n", i);
}
@@ -359,10 +359,10 @@ regalloc(Node *n, Type *t, Node *o)
case TFLOAT64:
if(o != N && o->op == OREGISTER) {
i = o->val.u.reg;
- if(i >= D_X0 && i <= D_X7)
+ if(i >= D_X0 && i <= D_X15)
goto out;
}
- for(i=D_X0; i<=D_X7; i++)
+ for(i=D_X0; i<=D_X15; i++)
if(reg[i] == 0)
goto out;
fatal("out of floating registers");
src/cmd/6g/peep.c
--- a/src/cmd/6g/peep.c
+++ b/src/cmd/6g/peep.c
@@ -627,19 +627,34 @@ subprop(Reg *r0)
Reg *r;
int t;
+ if(debug['P'] && debug['v'])
+ print("subprop %P\\n", r0->prog);
p = r0->prog;
v1 = &p->from;
- if(!regtyp(v1))
+ if(!regtyp(v1)) {
+ if(debug['P'] && debug['v'])
+ print("\tnot regtype %D; return 0\\n", v1);
return 0;
+ }
v2 = &p->to;
- if(!regtyp(v2))
+ if(!regtyp(v2)) {
+ if(debug['P'] && debug['v'])
+ print("\tnot regtype %D; return 0\\n", v2);
return 0;
+ }
for(r=uniqp(r0); r!=R; r=uniqp(r)) {
- if(uniqs(r) == R)
+ if(debug['P'] && debug['v'])
+ print("\t? %P\\n", r->prog);
+ if(uniqs(r) == R) {
+ if(debug['P'] && debug['v'])
+ print("\tno unique successor\\n");
break;
+ }
p = r->prog;
switch(p->as) {
case ACALL:
+ if(debug['P'] && debug['v'])
+ print("\tfound %P; return 0\\n", p);
return 0;
case AIMULL:
@@ -710,21 +725,33 @@ subprop(Reg *r0)
case AMOVSB:
case AMOVSL:
case AMOVSQ:
+ if(debug['P'] && debug['v'])
+ print("\tfound %P; return 0\\n", p);
return 0;
case AMOVL:
case AMOVQ:
+ case AMOVSS:
+ case AMOVSD:
if(p->to.type == v1->type)
goto gotit;
break;
}
if(copyau(&p->from, v2) ||
- copyau(&p->to, v2))
+ copyau(&p->to, v2)) {
+ if(debug['P'] && debug['v'])
+ print("\tcopyau %D failed\\n", v2);
break;
+ }
if(copysub(&p->from, v1, v2, 0) ||
- copysub(&p->to, v1, v2, 0))
+ copysub(&p->to, v1, v2, 0)) {
+ if(debug['P'] && debug['v'])
+ print("\tcopysub failed\\n");
break;
+ }
}
+ if(debug['P'] && debug['v'])
+ print("\tran off end; return 0\\n", p);
return 0;
gotit:
@@ -769,6 +796,8 @@ copyprop(Reg *r0)
Adr *v1, *v2;
Reg *r;
+ if(debug['P'] && debug['v'])
+ print("copyprop %P\\n", r0->prog);
p = r0->prog;
v1 = &p->from;
v2 = &p->to;
@@ -1180,13 +1209,22 @@ int
copyau(Adr *a, Adr *v)
{
- if(copyas(a, v))
+ if(copyas(a, v)) {
+ if(debug['P'] && debug['v'])
+ print("\tcopyau: copyas returned 1\\n");
return 1;
+ }
if(regtyp(v)) {
- if(a->type-D_INDIR == v->type)
+ if(a->type-D_INDIR == v->type) {
+ if(debug['P'] && debug['v'])
+ print("\tcopyau: found indir use - return 1\\n");
return 1;
- if(a->index == v->type)
+ }
+ if(a->index == v->type) {
+ if(debug['P'] && debug['v'])
+ print("\tcopyau: found index use - return 1\\n");
return 1;
+ }
}
return 0;
}
src/cmd/6g/reg.c
--- a/src/cmd/6g/reg.c
+++ b/src/cmd/6g/reg.c
@@ -1601,7 +1601,7 @@ int
BtoF(int32 b)
{
- b &= 0xFF0000L;
+ b &= 0xFFFF0000L;
if(b == 0)
return 0;
return bitno(b) - 16 + FREGMIN;
src/cmd/6l/6.out.h
--- a/src/cmd/6l/6.out.h
+++ b/src/cmd/6l/6.out.h
@@ -805,6 +805,14 @@ enum
D_X5,
D_X6,
D_X7,
+ D_X8,
+ D_X9,
+ D_X10,
+ D_X11,
+ D_X12,
+ D_X13,
+ D_X14,
+ D_X15,
D_CS = 68,
D_SS,
コアとなるコードの解説
src/cmd/6g/gsubr.c
の変更
- レジスタ範囲の拡張:
ginit()
とgclean()
関数内のループ、およびregalloc()
関数内のレジスタ検索ループが、D_X7
からD_X15
に拡張されています。これは、Goコンパイラが浮動小数点レジスタを管理する際に、XMM0からXMM15までの全16個のレジスタを対象とするようになったことを意味します。これにより、コンパイラはより多くのレジスタを自由に利用できるようになり、レジスタスピルの発生を抑え、コードの効率を向上させることが期待されます。
src/cmd/6g/peep.c
の変更
- 浮動小数点移動命令の最適化対象追加:
subprop()
関数において、AMOVSS
(単精度浮動小数点移動)とAMOVSD
(倍精度浮動小数点移動)命令が、コピー伝播最適化の対象として追加されました。これにより、コンパイラは浮動小数点値の冗長な移動を検出し、それらを削除または最適化することで、生成されるアセンブリコードのサイズと実行時間を削減できます。 - デバッグ出力の追加:
debug['P'] && debug['v']
という条件付きのprint
文が多数追加されています。これは、コンパイラの最適化フェーズ(特にコピー伝播)の動作を詳細にトレースするためのものです。開発者が最適化の適用状況やその効果をデバッグする際に役立ちます。
src/cmd/6g/reg.c
の変更
- ビットマスクの修正:
BtoF()
関数内のビットマスクが0xFF0000L
から0xFFFF0000L
に変更されました。これは、浮動小数点レジスタの識別子(インデックス)が拡張されたことに対応するための修正です。以前は8ビットの範囲でレジスタを識別していたのに対し、新しい変更では16ビットの範囲で識別することで、XMM0からXMM15までの全レジスタを正しく処理できるようになります。
src/cmd/6l/6.out.h
の変更
- 新しいレジスタ定義の追加:
enum
にD_X8
からD_X15
までの新しい浮動小数点レジスタの定義が追加されました。これは、Goのツールチェイン全体(コンパイラ、アセンブラ、リンカ)が、amd64
アーキテクチャの全16個のXMMレジスタを正式に認識し、利用するための基盤となります。
これらの変更は相互に関連しており、Goコンパイラがamd64
アーキテクチャの浮動小数点ハードウェア機能を最大限に活用し、より高性能なコードを生成するための重要なステップです。
関連リンク
- Go issue #2446: https://github.com/golang/go/issues/2446 (このコミットが修正したとされるGoのIssue)
- Go CL 6557044: https://golang.org/cl/6557044 (このコミットに対応するGerrit Code Reviewのチェンジリスト)
参考にした情報源リンク
- x86-64 (amd64) アーキテクチャのレジスタセットに関する情報 (例: Wikipedia, Intel/AMDの公式ドキュメント)
- コンパイラの最適化(レジスタ割り当て、コピー伝播、ピーフホール最適化)に関する一般的な情報 (例: コンパイラ設計の教科書、オンラインのコンピュータサイエンスリソース)
- Go言語のコンパイラ内部構造に関する情報 (Goのソースコード、Goのブログ記事、Goの設計ドキュメントなど)
- SSE/SSE2命令セットに関する情報 (Intelのプログラマーズマニュアルなど)