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

[インデックス 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であるのに対し、RSBRd = Rm - Rnのようにオペランドの順序が逆になります。これにより、0 - Xのような否定演算をRSB Rd, Rn, #0Rd = 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の処理は以下のようでした。

  1. nl(否定されるオペランド)の値をn1というレジスタにロード。
  2. 0を定数としてn3というノードに設定。
  3. n2という一時レジスタを割り当て。
  4. n30)の値をn2に移動(gmove(&n3, &n2))。
  5. n20)からn1(オペランド)を減算(gins(optoas(OSUB, nl->type), &n1, &n2))。
  6. 結果をresに移動。
  7. n1n2のレジスタを解放。

このプロセスでは、0を一時レジスタに移動するためのMOVW命令と、その一時レジスタの割り当て・解放が必要でした。

新しいコードでは、cgen.cからn3ノードと、gmove(&n3, &n2)、そしてn2のレジスタ解放が削除されました。代わりに、gins(optoas(OMINUS, nl->type), &n2, &n1)という形式で命令が生成されます。ここでn20の定数ノード、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)に合わせたものです。ここでn20の定数ノード、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での最適化の鍵となります。optoasOMINUSに対してRSB命令を返すようになったことで、コンパイラは0 - Xという否定演算を、単一のRSB命令にマッピングできるようになり、中間的なレジスタ操作が不要になりました。

関連リンク

参考にした情報源リンク

  • ARM Architecture Reference Manual (RSB instruction): ARMの公式ドキュメントや、ARMアセンブリに関する一般的な情報源。
  • Go言語のコンパイラに関するドキュメントやソースコード。
  • Web検索: "golang CL 6819123", "ARM RSB instruction"