[インデックス 13082] ファイルの概要
コミット
commit 70db440885fead4f0bb391d92e4e7f16b9c67389
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu May 17 02:58:14 2012 +0800
cmd/5c: re-enable regopt()
After CL 6185047, ./all.bash passed.
benchmark old ns/op new ns/op delta
BenchmarkAppend 5558 4894 -11.95%
BenchmarkAppendSpecialCase 5242 4572 -12.78%
BenchmarkSelectUncontended 3719 2821 -24.15%
BenchmarkSelectContended 3776 2832 -25.00%
BenchmarkSelectNonblock 1030 1089 +5.73%
BenchmarkChanUncontended 530 422 -20.38%
BenchmarkChanContended 534 444 -16.85%
BenchmarkChanSync 1613 1492 -7.50%
BenchmarkChanProdCons0 1520 1351 -11.12%
BenchmarkChanProdCons10 785 668 -14.90%
BenchmarkChanProdCons100 564 473 -16.13%
BenchmarkChanProdConsWork0 11205 10337 -7.75%
BenchmarkChanProdConsWork10 9806 9567 -2.44%
BenchmarkChanProdConsWork100 9413 9398 -0.16%
BenchmarkChanCreation 11687 8715 -25.43%
BenchmarkChanSem 553 453 -18.08%
BenchmarkCallClosure 22 22 +0.44%
BenchmarkCallClosure1 28 28 +0.71%
BenchmarkCallClosure2 2224 1668 -25.00%
BenchmarkCallClosure3 2217 1629 -26.52%
BenchmarkCallClosure4 2240 1684 -24.82%
BenchmarkComplex128DivNormal 930 912 -1.94%
BenchmarkComplex128DivNisNaN 862 866 +0.46%
BenchmarkComplex128DivDisNaN 849 852 +0.35%
BenchmarkComplex128DivNisInf 556 583 +4.86%
BenchmarkComplex128DivDisInf 522 512 -1.92%
BenchmarkConvT2E 175 159 -9.14%
BenchmarkConvT2EBig 2418 1823 -24.61%
BenchmarkConvT2I 545 549 +0.73%
BenchmarkConvI2E 35 32 -9.58%
BenchmarkConvI2I 404 391 -3.22%
BenchmarkAssertE2T 75 62 -16.25%
BenchmarkAssertE2TBig 76 63 -16.80%
BenchmarkAssertE2I 427 409 -4.22%
BenchmarkAssertI2T 82 66 -20.29%
BenchmarkAssertI2I 430 416 -3.26%
BenchmarkAssertI2E 36 32 -12.50%
BenchmarkAssertE2E 35 35 +0.57%
BenchmarkFinalizer 3224 2941 -8.78%
BenchmarkFinalizerRun 117392 84772 -27.79%
BenchmarkStackGrowth 5267 5930 +12.59%
BenchmarkSyscall 191 167 -12.57%
BenchmarkSyscallWork 9918 7713 -22.23%
BenchmarkIfaceCmp100 1645 1652 +0.43%
BenchmarkIfaceCmpNil100 1433 1440 +0.49%
R=dave, rsc
CC=golang-dev
https://golang.org/cl/6202070
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/70db440885fead4f0bb391d92e4e7f16b9c67389
元コミット内容
このコミットは、Goコンパイラのcmd/5c
(ARMアーキテクチャ向けのCコンパイラ)において、レジスタ最適化機能であるregopt()
を再有効化するものです。以前の変更(CL 6185047)によって発生していた問題が解決されたため、./all.bash
テストスイートが正常にパスするようになったことを受けて、この最適化を再度有効にしています。
コミットメッセージには、regopt()
の再有効化による様々なベンチマーク結果が示されており、多くのケースでパフォーマンスが大幅に改善していることが分かります。特に、BenchmarkSelectUncontended
やBenchmarkSelectContended
、BenchmarkChanCreation
などで20%以上の性能向上が見られます。
変更の背景
この変更の背景には、Goコンパイラの最適化プロセスにおける以前の課題があります。regopt()
関数は、コンパイラのバックエンドにおける重要なレジスタ割り当て最適化を担当しています。しかし、過去にはこの最適化が特定の条件下で問題を引き起こしていました。コミットメッセージ内の削除されたコメントには、「optimizer disabled because it smashes R8 when running out of registers」(レジスタが不足した際にR8を破壊するため、オプティマイザは無効化されている)と明記されており、これがregopt()
が無効化されていた具体的な理由です。
CL 6185047という別の変更リスト(Change List)によって、このレジスタ不足時のR8破壊問題が解決されたため、./all.bash
というGoプロジェクト全体のテストスイートが正常に実行されるようになりました。この問題解決を受けて、以前はパフォーマンス上の理由で無効化されていたregopt()
を安全に再有効化できるようになった、というのがこのコミットの主要な背景です。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
-
Goコンパイラと
cmd/5c
: Go言語の初期のコンパイラツールチェーンは、Plan 9というオペレーティングシステムから派生したツール群に基づいていました。cmd/5c
は、そのツールチェーンの一部であり、ARMアーキテクチャ(5
はARMを指す)向けのC言語コンパイラです。現在のGoコンパイラは主にgc
ツールチェーンが使われていますが、当時はこのような特定のアーキテクチャ向けのコンパイラが存在していました。コンパイラは、ソースコードを機械語に変換するソフトウェアであり、その過程で様々な最適化を行います。 -
レジスタ最適化(Register Optimization / Register Allocation): レジスタ最適化は、コンパイラのバックエンドにおける最も重要かつ複雑な最適化の一つです。CPUには限られた数の高速な記憶領域である「レジスタ」があります。プログラムの実行速度を最大化するためには、頻繁にアクセスされる変数や計算結果を効率的にこれらのレジスタに割り当てる必要があります。レジスタ割り当てが不適切だと、データが低速なメモリに頻繁にアクセスされることになり、パフォーマンスが著しく低下します。
regopt()
のような関数は、このレジスタ割り当てを最適化する役割を担っています。 -
静的単一割り当て形式(Static Single Assignment, SSA): レジスタ最適化は、多くの場合、プログラムのコードをSSA形式に変換した後に行われます。SSA形式では、各変数が一度だけ定義されるように変換され、データの流れが明確になります。これにより、レジスタ割り当てなどのデータフロー解析に基づく最適化が容易になります。
-
ベンチマーク(Benchmark): ソフトウェア開発において、特定のコードやシステムの性能を測定するために実行されるテストです。Go言語には標準でベンチマーク機能が組み込まれており、
go test -bench=.
などのコマンドで実行できます。このコミットメッセージに記載されているベンチマーク結果は、regopt()
の再有効化がGoプログラムの様々な側面(アペンド、チャネル操作、クロージャ呼び出しなど)に与える性能影響を示しています。ns/op
は「operation per nanosecond」の略で、1操作あたりのナノ秒を示し、値が小さいほど高速であることを意味します。 -
CL (Change List): Goプロジェクトでは、Gerritというコードレビューシステムが使われており、各変更は「Change List (CL)」として管理されます。コミットメッセージに記載されている「CL 6185047」は、このコミットの前提となる別の変更がGerrit上で管理されていたことを示しています。
技術的詳細
このコミットの技術的詳細の核心は、Goコンパイラのcmd/5c
におけるレジスタ最適化パスであるregopt()
関数の状態変更です。
以前のバージョンでは、regopt()
関数は意図的に無効化されていました。これは、関数内のコメント// TODO(kaib): optimizer disabled because it smashes R8 when running out of registers
が示すように、レジスタが不足する特定のシナリオでR8レジスタの内容を破壊するというバグが存在したためです。このバグは、コンパイラが生成するコードの誤動作やクラッシュにつながる可能性があったため、安定性を優先して最適化機能全体を一時的に無効にするという判断がなされていました。
無効化の方法は、regopt()
関数の冒頭にreturn;
ステートメントを配置し、その後の最適化ロジックが実行されないようにすることでした。さらに、最適化の本体コードは#ifdef NOTDEF ... #endif
というプリプロセッサディレクティブで囲まれていました。これは、NOTDEF
が定義されていない限り、その間のコードがコンパイル時に含まれないことを意味します。この二重の無効化メカニズムは、regopt()
が完全に機能しないようにするための確実な方法でした。
コミットメッセージに記載されている「After CL 6185047, ./all.bash passed.」という記述は、このR8レジスタ破壊の問題がCL 6185047という別の変更によって修正されたことを示唆しています。問題が解決され、Goプロジェクト全体のテストスイートである./all.bash
が正常にパスするようになったため、regopt()
を安全に再有効化できる環境が整いました。
このコミットでは、regopt()
を再有効化するために、無効化のために挿入されていたreturn;
ステートメントと、最適化ロジックを囲んでいた#ifdef NOTDEF
および#endif
ディレクティブが削除されています。これにより、regopt()
内の本来のレジスタ最適化ロジックがコンパイルされ、実行されるようになります。
結果として、コミットメッセージに示されているように、多くのベンチマークで顕著なパフォーマンス改善が見られました。これは、レジスタ最適化が有効になったことで、コンパイラがより効率的な機械語コードを生成し、CPUレジスタを最大限に活用できるようになったためです。特に、チャネル操作やクロージャ呼び出しなど、Go言語の並行処理や高階関数に関連する部分で大きな改善が見られるのは、これらの操作がレジスタ割り当ての効率に大きく依存するためと考えられます。
コアとなるコードの変更箇所
変更はsrc/cmd/5c/reg.c
ファイルに集中しています。
--- a/src/cmd/5c/reg.c
+++ b/src/cmd/5c/reg.c
@@ -66,12 +66,6 @@ rcmp(const void *a1, const void *a2)
void
regopt(Prog *p)
{
- USED(p);
- // TODO(kaib): optimizer disabled because it smashes R8 when running out of registers
- // the disable is unconventionally here because the call is in common code shared by 5c/6c/8c
- return;
-
-#ifdef NOTDEF
Reg *r, *r1, *r2;
Prog *p1;
int i, z;
@@ -500,7 +494,6 @@ brk:
r1->link = freer;
freer = firstr;
}
-#endif
}
void
コアとなるコードの解説
このコミットのコアとなる変更は、src/cmd/5c/reg.c
ファイル内のregopt
関数の定義部分から、以下の7行を削除したことです。
USED(p);
// TODO(kaib): optimizer disabled because it smashes R8 when running out of registers
// the disable is unconventionally here because the call is in common code shared by 5c/6c/8c
return;
#ifdef NOTDEF
#endif
(関数の末尾近く)
これらの行は、以前にregopt()
関数を無効化するために挿入されていました。
USED(p);
: これは、引数p
がコード内で使用されていないことによるコンパイラの警告を抑制するためのマクロです。regopt()
がreturn;
で即座に終了していたため、p
が実際に使われることはありませんでした。// TODO(kaib): ...
: これは、regopt()
が無効化されている理由(レジスタ不足時にR8を破壊するバグ)と、その無効化が他のコンパイラ(6c
/8c
)と共通のコードパスにあるため、この場所で行われているという説明のコメントです。return;
: この行がregopt()
関数の冒頭に存在することで、関数が呼び出されてもすぐに制御が戻り、本来のレジスタ最適化ロジックが一切実行されないようになっていました。#ifdef NOTDEF
と#endif
: これらのプリプロセッサディレクティブは、NOTDEF
というシンボルが定義されていない限り、その間のコードブロックをコンパイル対象から除外します。これにより、regopt()
の実際の最適化ロジックがコンパイル時に含まれないようにしていました。
これらの行を削除することで、regopt()
関数はもはや即座にリターンせず、またその本体のコードもコンパイル時に含まれるようになります。結果として、regopt()
内のレジスタ最適化ロジックが有効になり、コンパイラがより最適化された機械語コードを生成できるようになります。これが、コミットメッセージに示されたベンチマークの性能向上に直結しています。
関連リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/70db440885fead4f0bb391d92e4e7f16b9c67389
- GoのChange List (CL) 6185047 (このコミットの前提となる修正): https://golang.org/cl/6185047 (※リンク切れの可能性あり、当時のGerritシステムへのリンク)
- GoのChange List (CL) 6202070 (このコミット自体): https://golang.org/cl/6202070 (※リンク切れの可能性あり、当時のGerritシステムへのリンク)
参考にした情報源リンク
- Goの
cmd/5c
に関する情報:- https://go.dev/doc/install/source (Goのソースからのインストールに関する一般的な情報)
- https://cheney.net/go-compiler-internals (Goコンパイラの内部に関するブログ記事)
- レジスタ最適化/割り当てに関する情報:
- https://www.redhat.com/en/blog/go-compiler-internals-part-2-ssa-and-register-allocation (GoコンパイラのSSAとレジスタ割り当てに関するRed Hatのブログ記事)
- https://theyahya.com/posts/go-compiler-internals-part-2-ssa-and-register-allocation/ (GoコンパイラのSSAとレジスタ割り当てに関する記事)
src/cmd/5c/gc.h
におけるregopt
の宣言:- https://go.googlesource.com/go/+/refs/heads/master/src/cmd/5c/gc.h (Goのソースコードリポジトリ)