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

[インデックス 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()の再有効化による様々なベンチマーク結果が示されており、多くのケースでパフォーマンスが大幅に改善していることが分かります。特に、BenchmarkSelectUncontendedBenchmarkSelectContendedBenchmarkChanCreationなどで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行を削除したことです。

  1. USED(p);
  2. // TODO(kaib): optimizer disabled because it smashes R8 when running out of registers
  3. // the disable is unconventionally here because the call is in common code shared by 5c/6c/8c
  4. return;
  5. #ifdef NOTDEF
  6. #endif (関数の末尾近く)

これらの行は、以前にregopt()関数を無効化するために挿入されていました。

  • USED(p);: これは、引数pがコード内で使用されていないことによるコンパイラの警告を抑制するためのマクロです。regopt()return;で即座に終了していたため、pが実際に使われることはありませんでした。
  • // TODO(kaib): ...: これは、regopt()が無効化されている理由(レジスタ不足時にR8を破壊するバグ)と、その無効化が他のコンパイラ(6c/8c)と共通のコードパスにあるため、この場所で行われているという説明のコメントです。
  • return;: この行がregopt()関数の冒頭に存在することで、関数が呼び出されてもすぐに制御が戻り、本来のレジスタ最適化ロジックが一切実行されないようになっていました。
  • #ifdef NOTDEF#endif: これらのプリプロセッサディレクティブは、NOTDEFというシンボルが定義されていない限り、その間のコードブロックをコンパイル対象から除外します。これにより、regopt()の実際の最適化ロジックがコンパイル時に含まれないようにしていました。

これらの行を削除することで、regopt()関数はもはや即座にリターンせず、またその本体のコードもコンパイル時に含まれるようになります。結果として、regopt()内のレジスタ最適化ロジックが有効になり、コンパイラがより最適化された機械語コードを生成できるようになります。これが、コミットメッセージに示されたベンチマークの性能向上に直結しています。

関連リンク

参考にした情報源リンク