[インデックス 14621] ファイルの概要
このコミットは、GoコンパイラのARMアーキテクチャ向けバックエンドであるcmd/5g
において、符号反転(否定)演算子OMINUS
のコード生成を最適化するものです。具体的には、一時レジスタの使用を避け、より効率的なARMアセンブリ命令を生成することで、パフォーマンスの向上とコードサイズの削減を図っています。
コミット
commit 5bdf40dccab1fec0660c4374be9046d82a1a004f
Author: Dave Cheney <dave@cheney.net>
Date: Wed Dec 12 19:25:22 2012 +1100
cmd/5g: avoid temporary during OMINUS
Saves one MOVW and one register during the fast div/mod introduced in CL 6819123.
linux/arm (armv5)
benchmark old ns/op new ns/op delta
BenchmarkInt64Mod1 12 12 +7.50%
BenchmarkUint16Mod2 7 7 +0.28%
BenchmarkUint16Mod4 7 7 -0.28%
BenchmarkUint64Mod1 15 11 -23.72%
BenchmarkInt8Neg 8 7 -17.66%
BenchmarkInt16Neg 8 7 -17.66%
BenchmarkInt32Neg 5 5 -9.04%
BenchmarkUint8Neg 7 6 -14.35%
BenchmarkUint16Neg 8 7 -17.66%
BenchmarkUint32Neg 5 5 -9.04%
R=rsc
CC=golang-dev
https://golang.org/cl/6842045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5bdf40dccab1fec0660c4374be9046d82a1a004f
元コミット内容
cmd/5g: avoid temporary during OMINUS
Saves one MOVW and one register during the fast div/mod introduced in CL 6819123.
linux/arm (armv5)
benchmark old ns/op new ns/op delta
BenchmarkInt64Mod1 12 12 +7.50%
BenchmarkUint16Mod2 7 7 +0.28%
BenchmarkUint16Mod4 7 7 -0.28%
BenchmarkUint64Mod1 15 11 -23.72%
BenchmarkInt8Neg 8 7 -17.66%
BenchmarkInt16Neg 8 7 -17.66%
BenchmarkInt32Neg 5 5 -9.04%
BenchmarkUint8Neg 7 6 -14.35%
BenchmarkUint16Neg 8 7 -17.66%
BenchmarkUint32Neg 5 5 -9.04%
R=rsc
CC=golang-dev
https://golang.org/cl/6842045
変更の背景
この変更は、以前のコミット(CL 6819123)で導入された高速な除算/剰余演算(div/mod
)の最適化に続くものです。CL 6819123は、Goコンパイラが生成する除算および剰余演算のコードを改善し、より効率的なアセンブリ命令を使用するようにしたと考えられます。
本コミットの目的は、その最適化の恩恵をさらに広げ、特に符号反転(否定)演算OMINUS
において、一時レジスタの使用を削減することです。従来のコード生成では、0 - X
のような否定演算を行う際に、まず0
を一時レジスタに移動し、その後で減算を行うという手順を踏んでいました。これにより、余分なMOVW
命令とレジスタの割り当てが発生していました。
このコミットは、ARMアーキテクチャのRSB
(Reverse Subtract)命令を活用することで、この非効率性を解消します。RSB
命令はoperand2 - operand1
という形式の減算を直接実行できるため、0 - X
のような否定演算を単一の命令で効率的に処理できます。これにより、生成されるアセンブリコードがよりコンパクトになり、実行速度も向上します。
ベンチマーク結果は、特にNeg
(否定)操作において顕著なパフォーマンス改善(最大で約17%)を示しており、この最適化が効果的であったことを裏付けています。
前提知識の解説
- Goコンパイラ (
cmd/5g
): Go言語のソースコードを機械語に変換するプログラムです。cmd/5g
は、Goコンパイラのバックエンドの一つで、ARMアーキテクチャ(特にARMv5)向けのコード生成を担当しています。Goコンパイラは、フロントエンドでGoのコードを抽象構文木(AST)に変換し、その後、各アーキテクチャのバックエンドがASTを元にアセンブリコードを生成します。 OMINUS
: Goコンパイラの内部で使われる演算子の一つで、数値の符号を反転させる(例:-x
)操作を表します。MOVW
(Move Word): ARMアセンブリ命令の一つで、32ビットの値をレジスタ間で移動させる命令です。- レジスタ (Register): CPU内部にある高速な記憶領域で、演算の対象となるデータを一時的に保持するために使われます。レジスタの使用効率は、プログラムの実行速度に大きく影響します。
- 除算/剰余演算 (
div/mod
): 数値の除算と剰余を求める演算です。これらの演算は一般的にCPUにとってコストの高い操作であり、最適化の対象となることが多いです。 CL 6819123
: これはGoプロジェクトの内部的な変更リスト(Change List)のIDです。Goプロジェクトでは、Gerritというコードレビューシステムが使われており、各変更はCLとして管理されます。このCLは、本コミットの前に除算/剰余演算の高速化が行われたことを示唆しています。RSB
(Reverse Subtract): ARMアセンブリ命令の一つで、「逆減算」を意味します。通常の減算命令がRd = Rn - Rm
であるのに対し、RSB
はRd = Rm - Rn
のようにオペランドの順序が逆になります。これにより、0 - X
のような否定演算をRSB Rd, Rn, #0
(Rd = 0 - Rn
)のように単一の命令で効率的に表現できます。Node
: GoコンパイラのAST(抽象構文木)における各要素を表すデータ構造です。ソースコードの構造がツリー状に表現されます。Prog
: Goコンパイラのバックエンドで、生成されるアセンブリ命令を表すデータ構造です。regalloc
/regfree
: レジスタの割り当てと解放を行うコンパイラ内部の関数です。gmove
: データをある場所から別の場所へ移動させるアセンブリ命令を生成する関数です。gins
: アセンブリ命令を生成する汎用的な関数です。optoas
: Goコンパイラの内部演算子(例:OMINUS
,OSUB
)を、対応するアセンブリ命令(例:ARSB
,ASUBD
)に変換する関数です。
技術的詳細
このコミットの主要な技術的変更点は、OMINUS
演算のコード生成ロジックを、一時レジスタとMOVW
命令を介した0
からの減算から、ARMのRSB
命令を直接利用する形に切り替えたことです。
従来のcgen.c
におけるOMINUS
の処理は以下のようでした。
nl
(否定されるオペランド)の値をn1
というレジスタにロード。0
を定数としてn3
というノードに設定。n2
という一時レジスタを割り当て。n3
(0
)の値をn2
に移動(gmove(&n3, &n2)
)。n2
(0
)からn1
(オペランド)を減算(gins(optoas(OSUB, nl->type), &n1, &n2)
)。- 結果を
res
に移動。 n1
とn2
のレジスタを解放。
このプロセスでは、0
を一時レジスタに移動するためのMOVW
命令と、その一時レジスタの割り当て・解放が必要でした。
新しいコードでは、cgen.c
からn3
ノードと、gmove(&n3, &n2)
、そしてn2
のレジスタ解放が削除されました。代わりに、gins(optoas(OMINUS, nl->type), &n2, &n1)
という形式で命令が生成されます。ここでn2
は0
の定数ノード、n1
は元のオペランドです。
この変更を可能にしているのが、gsubr.c
におけるoptoas
関数の拡張です。gsubr.c
の変更により、OMINUS
演算子と様々な整数型(TINT8
, TUINT8
, TINT16
, TUINT16
, TINT32
, TUINT32
, TPTR32
)の組み合わせに対して、アセンブリ命令としてARSB
(実質的にはARMのRSB
命令)が返されるようになりました。
これにより、コンパイラは0 - X
という論理的な操作を、RSB
命令一つで実現できるようになり、中間的なMOVW
命令と一時レジスタのオーバーヘッドがなくなりました。結果として、生成されるコードはより効率的で高速になります。
コアとなるコードの変更箇所
src/cmd/5g/cgen.c
--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -15,7 +15,7 @@ void
cgen(Node *n, Node *res)
{
Node *nl, *nr, *r;
- Node n1, n2, n3, f0, f1;
+ Node n1, n2, f0, f1;
int a, w, rg;
Prog *p1, *p2, *p3;
Addr addr;
@@ -240,13 +240,10 @@ cgen(Node *n, Node *res)
case OMINUS:
regalloc(&n1, nl->type, N);
cgen(nl, &n1);
- nodconst(&n3, nl->type, 0);
- regalloc(&n2, nl->type, res);
- gmove(&n3, &n2);
- gins(optoas(OSUB, nl->type), &n1, &n2);
- gmove(&n2, res);
+ nodconst(&n2, nl->type, 0);
+ gins(optoas(OMINUS, nl->type), &n2, &n1);
+ gmove(&n1, res);
regfree(&n1);
- regfree(&n2);
goto ret;
// symmetric binary
src/cmd/5g/gsubr.c
--- a/src/cmd/5g/gsubr.c
+++ b/src/cmd/5g/gsubr.c
@@ -1611,6 +1611,16 @@ optoas(int op, Type *t)
a = ASUBD;
break;
+ case CASE(OMINUS, TINT8):
+ case CASE(OMINUS, TUINT8):
+ case CASE(OMINUS, TINT16):
+ case CASE(OMINUS, TUINT16):
+ case CASE(OMINUS, TINT32):
+ case CASE(OMINUS, TUINT32):
+ case CASE(OMINUS, TPTR32):
+ a = ARSB;
+ break;
+
case CASE(OAND, TINT8):
case CASE(OAND, TUINT8):
case CASE(OAND, TINT16):
コアとなるコードの解説
src/cmd/5g/cgen.c
の変更
Node n1, n2, n3, f0, f1;
からNode n1, n2, f0, f1;
へ変更:n3
という一時的なノード変数が不要になったため削除されました。これは、0
を表現するために別途ノードを割り当てる必要がなくなったことを示唆しています。OMINUS
ケース内の変更:nodconst(&n3, nl->type, 0);
の削除:0
を定数ノードn3
として作成する行が削除されました。regalloc(&n2, nl->type, res);
の削除:n2
という一時レジスタの割り当てが不要になったため削除されました。gmove(&n3, &n2);
の削除:0
を一時レジスタn2
に移動する命令生成が不要になったため削除されました。gins(optoas(OSUB, nl->type), &n1, &n2);
からgins(optoas(OMINUS, nl->type), &n2, &n1);
へ変更:- 演算子が
OSUB
からOMINUS
に変わりました。これは、optoas
関数がOMINUS
に対して直接RSB
命令を返すようになったためです。 - オペランドの順序が
(&n1, &n2)
から(&n2, &n1)
に変わりました。これはRSB
命令のセマンティクス(operand2 - operand1
)に合わせたものです。ここでn2
は0
の定数ノード、n1
は元のオペランドです。
- 演算子が
gmove(&n2, res);
からgmove(&n1, res);
へ変更: 演算結果がn1
に格納されるようになったため、n1
から結果レジスタres
へ移動するように変更されました。regfree(&n2);
の削除:n2
レジスタが一時的に割り当てられることがなくなったため、その解放も不要になりました。
これらの変更により、OMINUS
演算のコード生成において、0
を一時レジスタにロードし、その後減算を行うという冗長な手順が排除され、より直接的にRSB
命令が利用されるようになりました。
src/cmd/5g/gsubr.c
の変更
optoas
関数に新しいcase
文が追加されました。CASE(OMINUS, ...)
:OMINUS
演算子と、TINT8
(8ビット符号付き整数)からTPTR32
(32ビットポインタ)までの様々な整数型に対して、アセンブリ命令コードARSB
(これはARMのRSB
命令に対応します)を返すように定義されました。
この変更が、cgen.c
での最適化の鍵となります。optoas
がOMINUS
に対してRSB
命令を返すようになったことで、コンパイラは0 - X
という否定演算を、単一のRSB
命令にマッピングできるようになり、中間的なレジスタ操作が不要になりました。
関連リンク
- Go Change List 6842045: https://golang.org/cl/6842045
参考にした情報源リンク
- ARM Architecture Reference Manual (RSB instruction): ARMの公式ドキュメントや、ARMアセンブリに関する一般的な情報源。
- Go言語のコンパイラに関するドキュメントやソースコード。
- Web検索: "golang CL 6819123", "ARM RSB instruction"