[インデックス 17116] ファイルの概要
このコミットは、Goコンパイラ(5c
, 5g
)とリンカ(5l
)における8ビットおよび16ビット整数値のレジスタ間移動命令のセマンティクスを明確化し、関連する最適化を改善するものです。具体的には、MOVB
とMOVH
を単純な移動命令として扱い、符号拡張(sign-extension)やゼロ拡張(zero-extension)のセマンティクスをMOVBS
/MOVBU
、MOVHS
/MOVHU
といった新しい擬似命令に分離することで、コンパイラのコード生成と最適化の精度を高めています。これにより、特に短い整数型に対する算術演算のパフォーマンスが向上しています。
コミット
- コミットハッシュ:
357f73369510e3ef37e2a473a7fd9034b1ddeeed
- Author: Rémy Oudompheng oudomphe@phare.normalesup.org
- Date: Fri Aug 9 06:43:17 2013 +0200
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/357f73369510e3ef37e2a473a7fd9034b1ddeeed
元コミット内容
commit 357f73369510e3ef37e2a473a7fd9034b1ddeeed
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Fri Aug 9 06:43:17 2013 +0200
cmd/5c, cmd/5g, cmd/5l: turn MOVB, MOVH into plain moves, optimize short arithmetic.
Pseudo-instructions MOVBS and MOVHS are used to clarify
the semantics of short integers vs. registers:
* 8-bit and 16-bit values in registers are assumed to always
be zero-extended or sign-extended depending on their type.
* MOVB is truncation or move of an already extended value
between registers.
* MOVBU enforces zero-extension at the destination (register).
* MOVBS enforces sign-extension at the destination (register).
And similarly for MOVH/MOVS/MOVHU.
The linker is adapted to assemble MOVB and MOVH to an ordinary
mov. Also a peephole pass in 5g that aims at eliminating
redundant zero/sign extensions is improved.
encoding/binary:
benchmark old ns/op new ns/op delta
BenchmarkReadSlice1000Int32s 220387 217185 -1.45%
BenchmarkReadStruct 12839 12910 +0.55%
BenchmarkReadInts 5692 5534 -2.78%
BenchmarkWriteInts 6137 6016 -1.97%
BenchmarkPutUvarint32 257 241 -6.23%
BenchmarkPutUvarint64 812 754 -7.14%
benchmark old MB/s new MB/s speedup
BenchmarkReadSlice1000Int32s 18.15 18.42 1.01x
BenchmarkReadStruct 5.45 5.42 0.99x
BenchmarkReadInts 5.27 5.42 1.03x
BenchmarkWriteInts 4.89 4.99 1.02x
BenchmarkPutUvarint32 15.56 16.57 1.06x
BenchmarkPutUvarint64 9.85 10.60 1.08x
crypto/des:
benchmark old ns/op new ns/op delta
BenchmarkEncrypt 7002 5169 -26.18%
BenchmarkDecrypt 7015 5195 -25.94%
benchmark old MB/s new MB/s speedup
BenchmarkEncrypt 1.14 1.55 1.36x
BenchmarkDecrypt 1.14 1.54 1.35x
strconv:
benchmark old ns/op new ns/op delta
BenchmarkAtof64Decimal 457 385 -15.75%
BenchmarkAtof64Float 574 479 -16.55%
BenchmarkAtof64FloatExp 1035 906 -12.46%
BenchmarkAtof64Big 1793 1457 -18.74%
BenchmarkAtof64RandomBits 2267 2066 -8.87%
BenchmarkAtof64RandomFloats 1416 1194 -15.68%
BenchmarkAtof32Decimal 451 379 -15.96%
BenchmarkAtof32Float 547 435 -20.48%
BenchmarkAtof32FloatExp 1095 986 -9.95%
BenchmarkAtof32Random 1154 1006 -12.82%
BenchmarkAtoi 1415 1380 -2.47%
BenchmarkAtoiNeg 1414 1401 -0.92%
BenchmarkAtoi64 1744 1671 -4.19%
BenchmarkAtoi64Neg 1737 1662 -4.32%
Fixes #1837.
R=rsc, dave, bradfitz
CC=golang-dev
https://golang.org/cl/12424043
変更の背景
このコミットは、Goコンパイラとリンカにおける8ビット(byte
, int8
, uint8
)および16ビット(int16
, uint16
)の短い整数型を扱う際のセマンティクスの曖昧さを解消し、それによって生じる非効率性を改善するために導入されました。
Go言語では、レジスタは通常、プロセッサのワードサイズ(例えば32ビットや64ビット)で扱われます。しかし、Goの型システムには8ビットや16ビットの整数型が存在します。これらの短い整数値をレジスタにロードしたり、レジスタ間で移動させたりする際に、残りのビット(上位ビット)をどのように扱うか(ゼロで埋めるか、符号ビットで埋めるか)が問題となります。
従来のGoコンパイラでは、MOVB
(バイト移動)やMOVH
(ハーフワード移動)といった命令が、文脈によってゼロ拡張または符号拡張を暗黙的に行うことがありました。この暗黙的な挙動は、コンパイラが生成するコードの予測可能性を低下させ、冗長な拡張命令の生成につながることがありました。特に、短い整数型に対する算術演算(加算、減算、乗算など)の後、結果がレジスタのフルワードに格納されるため、その後の操作で再度適切な拡張が必要になる場合がありました。
この問題は、Go issue #1837 "cmd/5g: short arithmetic is slow" で報告されており、短い整数型に対する算術演算が期待よりも遅いというパフォーマンス上の懸念が示されていました。このコミットは、このパフォーマンス問題を解決し、コンパイラがより効率的で正確なコードを生成できるようにすることを目的としています。
前提知識の解説
1. Goコンパイラとリンカ (5c
, 5g
, 5l
)
Go言語のツールチェインは、特定のアーキテクチャ向けに設計されたコンパイラとリンカを含んでいます。このコミットで言及されている5c
, 5g
, 5l
は、それぞれARMアーキテクチャ(Goの内部ではGOARCH=arm
またはGOARCH=arm64
に対応)向けのCコンパイラ、Goコンパイラ、リンカを指します。
5c
: Goのランタイムや標準ライブラリの一部でC言語で書かれた部分をコンパイルするCコンパイラ。5g
: Go言語のソースコードをアセンブリコードにコンパイルするGoコンパイラ。5l
: コンパイルされたオブジェクトファイルをリンクして実行可能ファイルを生成するリンカ。
これらのツールは、Goプログラムがターゲットアーキテクチャ上で効率的に動作するための機械語を生成する役割を担っています。
2. 擬似命令 (Pseudo-instructions)
擬似命令とは、アセンブラが最終的な機械語に変換する際に、一つ以上の実際のCPU命令に展開される抽象的な命令のことです。プログラマにとっては、より高レベルなセマンティクスで操作を記述できるため、コードの可読性と記述性が向上します。
このコミットでは、MOVBS
, MOVBU
, MOVHS
, MOVHU
といった新しい擬似命令が導入されています。これらは、レジスタへの値のロードや移動の際に、明示的に符号拡張またはゼロ拡張を行うことを指示します。
MOVB
: バイト(8ビット)の移動。このコミット後は、単純な移動として扱われ、拡張は行わない。MOVH
: ハーフワード(16ビット)の移動。このコミット後は、単純な移動として扱われ、拡張は行わない。MOVBS
: バイト値をレジスタにロードする際に、符号拡張を行う。MOVBU
: バイト値をレジスタにロードする際に、ゼロ拡張を行う。MOVHS
: ハーフワード値をレジスタにロードする際に、符号拡張を行う。MOVHU
: ハーフワード値をレジスタにロードする際に、ゼロ拡張を行う。
3. ゼロ拡張 (Zero-extension) と 符号拡張 (Sign-extension)
コンピュータアーキテクチャにおいて、短いビット幅の数値をより長いビット幅のレジスタに格納する際に、上位ビットをどのように埋めるかという概念です。
-
ゼロ拡張: 符号なし整数(
uint8
,uint16
など)を拡張する際に使用されます。元の数値の最上位ビットに関わらず、新しい上位ビットをすべて0
で埋めます。これにより、数値の非負性が保たれます。 例: 8ビットの0b10101010
(170) を32ビットにゼロ拡張すると0b00000000000000000000000010101010
となります。 -
符号拡張: 符号付き整数(
int8
,int16
など)を拡張する際に使用されます。元の数値の最上位ビット(符号ビット)の値を、新しい上位ビットすべてにコピーして埋めます。これにより、数値の符号と値が保たれます。 例: 8ビットの0b10101010
(-86) を32ビットに符号拡張すると0b11111111111111111111111110101010
となります。
これらの拡張操作は、異なるビット幅の数値間で演算を行う際に非常に重要です。
4. ピーホール最適化 (Peephole Optimization)
ピーホール最適化は、コンパイラの最適化手法の一つで、生成されたアセンブリコードの小さな「窓(peephole)」を覗き込み、非効率な命令シーケンスをより効率的なものに置き換えるものです。通常、局所的な最適化であり、隣接する数命令のパターンを認識して変換します。
このコミットでは、5g
コンパイラのピーホールパスが改善され、冗長なゼロ/符号拡張命令を排除する能力が向上しています。例えば、「値をレジスタにロードして拡張し、すぐにそのレジスタの値を別のレジスタに移動して再度拡張する」といった冗長なパターンを検出し、後者の拡張を不要なものとして削除することができます。
技術的詳細
このコミットの核心は、Goコンパイラとリンカが短い整数型(8ビット、16ビット)をレジスタで扱う方法のセマンティクスを明確化し、それに基づいて最適化を改善した点にあります。
1. MOVB
, MOVH
のセマンティクス変更
以前は、MOVB
やMOVH
といった命令が、ソースとデスティネーションの型に応じて暗黙的にゼロ拡張または符号拡張を行うことがありました。このコミットにより、これらの命令は「プレーンな移動(plain moves)」として扱われるようになります。つまり、レジスタ内の既に拡張された値の切り捨て(truncation)や、レジスタ間の単純な値の移動を意味するようになります。
これにより、コンパイラはMOVB
/MOVH
命令を生成する際に、拡張のセマンティクスについて考慮する必要がなくなり、よりシンプルで予測可能なコード生成が可能になります。
2. 新しい擬似命令の導入
拡張のセマンティクスを明示的にするために、以下の擬似命令が導入されました。
MOVBS
(Move Byte Sign-extended): バイト値をレジスタにロードする際に、符号拡張を強制します。MOVBU
(Move Byte Zero-extended): バイト値をレジスタにロードする際に、ゼロ拡張を強制します。MOVHS
(Move Halfword Sign-extended): ハーフワード値をレジスタにロードする際に、符号拡張を強制します。MOVHU
(Move Halfword Zero-extended): ハーフワード値をレジスタにロードする際に、ゼロ拡張を強制します。
これらの命令は、レジスタに格納される8ビットまたは16ビットの値が、常にその型に応じてゼロ拡張または符号拡張されるという前提を明確にします。これにより、コンパイラはレジスタ内の値の正確な状態を把握しやすくなり、後続の演算や移動で不必要な拡張を避けることができます。
3. リンカの適応
5l
リンカは、新しいセマンティクスに合わせてMOVB
とMOVH
を通常のアセンブリmov
命令に変換するように適応されました。これにより、コンパイラが生成した擬似命令が、最終的な機械語に正しくマッピングされます。
4. ピーホール最適化の改善
5g
コンパイラのピーホールパス(peep.c
)が強化され、冗長なゼロ/符号拡張を排除する能力が向上しました。特に、MOVBS x, R; MOVBS R, R'
のような連続する拡張命令のパターンを検出し、後続の拡張をより効率的な単純な移動(MOVB R, R'
)に置き換えることができるようになりました。これは、レジスタR
に既に適切に拡張された値が格納されている場合、再度同じ種類の拡張を行う必要がないためです。この最適化により、生成されるコードの命令数が削減され、実行効率が向上します。
5. パフォーマンスベンチマークの結果
コミットメッセージには、encoding/binary
, crypto/des
, strconv
パッケージにおけるベンチマーク結果が示されています。多くのベンチマークでパフォーマンスが向上しており、特にcrypto/des
のBenchmarkEncrypt
とBenchmarkDecrypt
では25%以上の大幅な改善が見られます。これは、短い整数型に対する操作がこれらのパッケージで頻繁に行われており、今回の最適化が直接的に効果を発揮したことを示しています。
encoding/binary
:BenchmarkPutUvarint32
とBenchmarkPutUvarint64
で約6-7%の改善。crypto/des
:BenchmarkEncrypt
とBenchmarkDecrypt
で約26%の改善。strconv
:BenchmarkAtof
系の関数で10-20%程度の改善、BenchmarkAtoi
系でも数%の改善。
これらの結果は、今回の変更がGoプログラム全体のパフォーマンス、特に数値演算やデータ変換が頻繁に行われる領域において、顕著な改善をもたらすことを裏付けています。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。これらはGoコンパイラ(5c
, 5g
)とリンカ(5l
)のバックエンド部分に属します。
src/cmd/5c/reg.c
: 4行の変更 (+2, -2)src/cmd/5g/cgen.c
: 10行の変更 (+10, -0)src/cmd/5g/ggen.c
: 9行の変更 (+9, -0)src/cmd/5g/gsubr.c
: 49行の変更 (+33, -16)src/cmd/5g/peep.c
: 103行の変更 (+70, -33)src/cmd/5g/reg.c
: 4行の変更 (+2, -2)src/cmd/5l/asm.c
: 2行の変更 (+2, -0)src/cmd/5l/optab.c
: 4行の変更 (+2, -2)
合計で8ファイルが変更され、136行が追加され、49行が削除されています。
コアとなるコードの解説
src/cmd/5c/reg.c
および src/cmd/5g/reg.c
これらのファイルは、レジスタ割り当てと命令生成に関連する部分です。変更点では、TCHAR
/TUCHAR
(8ビット)やTSHORT
/TUSHORT
(16ビット)といった型に対して、従来のAMOVB
/AMOVH
ではなく、明示的に符号拡張を行うAMOVBS
/AMOVHS
を使用するように変更されています。これは、レジスタにロードされる短い整数値が、その型に応じて適切に拡張されるべきであるという新しいセマンティクスを反映しています。
例:
--- a/src/cmd/5c/reg.c
+++ b/src/cmd/5c/reg.c
@@ -559,9 +559,9 @@ addmove(Reg *r, int bn, int rn, int f)
p1->as = AMOVW;
if(v->etype == TCHAR || v->etype == TUCHAR)
- p1->as = AMOVB;
+ p1->as = AMOVBS;
if(v->etype == TSHORT || v->etype == TUSHORT)
- p1->as = AMOVH;
+ p1->as = AMOVHS;
if(v->etype == TFLOAT)
p1->as = AMOVF;
if(v->etype == TDOUBLE)
src/cmd/5g/cgen.c
および src/cmd/5g/ggen.c
これらのファイルは、Go言語のAST(抽象構文木)からアセンブリコードを生成する部分です。短い整数型に対する算術演算(OADD
, OSUB
, OMUL
)やシフト演算(OLSH
)の後、結果がレジスタのフルワードに格納されるため、その結果が元の短い型に適切に正規化(ゼロ拡張または符号拡張)されるようにgins(optoas(OAS, n->type), &n1, &n1);
のような命令が追加されています。これにより、後続の操作で値のセマンティクスが正しく保たれます。
例:
--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -466,6 +466,16 @@ abop: // asymmetric binary
cgen(nl, &n1);
}
gins(a, &n2, &n1);
+ // Normalize result for types smaller than word.
+ if(n->type->width < widthptr) {
+ switch(n->op) {
+ case OADD:
+ case OSUB:
+ case OMUL:
+ gins(optoas(OAS, n->type), &n1, &n1);
+ break;
+ }
+ }
gmove(&n1, res);
regfree(&n1);
if(n2.op != OLITERAL)
src/cmd/5g/gsubr.c
このファイルは、Goコンパイラの汎用サブルーチンを含み、特にgmove
関数は異なる型間での値の移動を処理します。このコミットでは、gmove
関数内の型変換ロジックが大幅に修正され、MOVB
/MOVH
が単純な移動として扱われ、MOVBS
/MOVBU
/MOVHS
/MOVHU
が明示的な拡張を行うように変更されています。特に、メモリからレジスタへのロードの場合と、レジスタ間の移動の場合で異なる命令が選択されるようになっています。
例:
--- a/src/cmd/5g/gsubr.c
+++ b/src/cmd/5g/gsubr.c
@@ -706,16 +706,24 @@ gmove(Node *f, Node *t)
* integer copy and truncate
*/
case CASE(TINT8, TINT8): // same size
+ if(!ismem(f)) {
+ a = AMOVB;
+ break;
+ }
case CASE(TUINT8, TINT8):
case CASE(TINT16, TINT8): // truncate
case CASE(TUINT16, TINT8):
case CASE(TINT32, TINT8):
case CASE(TUINT32, TINT8):
- a = AMOVB;
+ a = AMOVBS;
break;
- case CASE(TINT8, TUINT8):
case CASE(TUINT8, TUINT8):
+ if(!ismem(f)) {
+ a = AMOVB;
+ break;
+ }
+ case CASE(TINT8, TUINT8):
case CASE(TINT16, TUINT8):
case CASE(TUINT16, TUINT8):
case CASE(TINT32, TUINT8):
src/cmd/5g/peep.c
このファイルは、Goコンパイラのピーホール最適化パスを実装しています。最も大きな変更が行われたファイルであり、shortprop
という新しい関数が追加されています。この関数は、連続する冗長なゼロ/符号拡張命令(例: MOVBS x, R; MOVBS R, R'
)を検出し、後続の拡張を単純な移動命令(MOVB R, R'
)に置き換えることで最適化を行います。これにより、不必要な命令が削減され、コードの効率が向上します。
また、以前のピーホールパスでMOVB
/MOVH
系の命令に対して行われていた冗長な最適化ロジックが削除されています。
例: shortprop
関数の追加
--- a/src/cmd/5g/peep.c
+++ b/src/cmd/5g/peep.c
@@ -587,6 +578,64 @@ constprop(Adr *c1, Adr *v1, Reg *r)
}
}
+/*
+ * shortprop eliminates redundant zero/sign extensions.
+ *
+ * MOVBS x, R
+ * <no use R>
+ * MOVBS R, R'
+ *
+ * changed to
+ *
+ * MOVBS x, R
+ * ...
+ * MOVB R, R' (compiled to mov)
+ *
+ * MOVBS above can be a MOVBS, MOVBU, MOVHS or MOVHU.
+ */
+int
+shortprop(Reg *r)
+{
+ Prog *p, *p1;
+ Reg *r1;
+
+ p = r->prog;
+ r1 = findpre(r, &p->from);
+ if(r1 == R)
+ return 0;
+
+ p1 = r1->prog;
+ if(p1->as == p->as) {
+ // Two consecutive extensions.
+ goto gotit;
+ }
+
+ if(p1->as == AMOVW && isdconst(&p1->from)
+ && p1->from.offset >= 0 && p1->from.offset < 128) {
+ // Loaded an immediate.
+ goto gotit;
+ }
+
+ return 0;
+
+gotit:
+ if(debug['P'])
+ print("shortprop\n%P\n%P", p1, p);
+ switch(p->as) {
+ case AMOVBS:
+ case AMOVBU:
+ p->as = AMOVB;
+ break;
+ case AMOVHS:
+ case AMOVHU:
+ p->as = AMOVH;
+ break;
+ }
+ if(debug['P'])
+ print(" => %A\n", p->as);
+ return 1;
+}
+
/*
* ASLL x,y,w
* .. (not use w, not set x y w)
src/cmd/5l/asm.c
および src/cmd/5l/optab.c
これらのファイルは、Goリンカの命令アセンブルと命令テーブルに関連します。asm.c
では、AMOVB
とAMOVH
がAMOVW
(通常のワード移動)と同じ命令グループとして扱われるように変更されています。これは、MOVB
/MOVH
がプレーンな移動命令として扱われるという新しいセマンティクスをリンカに反映させるためです。
optab.c
では、AMOVB
とAMOVH
の命令のエンコーディング情報が変更され、より効率的な命令として扱われるようになっています。特に、AMOVB
とAMOVH
のsize
が14
から1
に、param
が8
から4
に変更されており、これはこれらの命令がより単純な形式でアセンブルされることを示唆しています。
例: src/cmd/5l/optab.c
--- a/src/cmd/5l/optab.c
+++ b/src/cmd/5l/optab.c
@@ -94,10 +94,10 @@ Optab optab[] =
{ AMVN, C_LCON, C_NONE, C_REG, 13, 8, 0, LFROM },
{ ACMP, C_LCON, C_REG, C_NONE, 13, 8, 0, LFROM },
- { AMOVB, C_REG, C_NONE, C_REG, 14, 8, 0 },
+ { AMOVB, C_REG, C_NONE, C_REG, 1, 4, 0 },
{ AMOVBS, C_REG, C_NONE, C_REG, 14, 8, 0 },
{ AMOVBU, C_REG, C_NONE, C_REG, 58, 4, 0 },
- { AMOVH, C_REG, C_NONE, C_REG, 14, 8, 0 },
+ { AMOVH, C_REG, C_NONE, C_REG, 1, 4, 0 },
{ AMOVHS, C_REG, C_NONE, C_REG, 14, 8, 0 },
{ AMOVHU, C_REG, C_NONE, C_REG, 14, 8, 0 },
関連リンク
- Go Issue #1837: cmd/5g: short arithmetic is slow
- Go CL 12424043: https://golang.org/cl/12424043
参考にした情報源リンク
- Go issue #1837: cmd/5g: short arithmetic is slow
- Go CL 12424043: cmd/5c, cmd/5g, cmd/5l: turn MOVB, MOVH into plain moves, optimize short arithmetic.
- Peephole optimization - Wikipedia
- Sign extension - Wikipedia
- Zero-extension - Wikipedia
- Go Assembly Language - The Go Programming Language (一般的なGoアセンブリの概念理解のため)
- Go's Toolchain - The Go Programming Language (Goツールチェインの一般的な理解のため)
- Go Compiler Internals - Go Wiki (Goコンパイラの内部構造の一般的な理解のため)