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

[インデックス 14919] ファイルの概要

このコミットは、Goコンパイラ(cmd/gc)における一時変数(temporaries)のレジスタ割り当て(registerization)を改善するための変更です。具体的には、インライン化によって生成される一時変数の名前付け規則を変更し、最適化器がこれらの変数を適切に処理できるようにしています。これにより、コンパイラの最適化能力が向上し、生成されるコードの効率が改善される可能性があります。

コミット

commit dfdfba14b98591a45186a9e9182b71e7df816e2c
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Jan 18 22:25:17 2013 +0100

    cmd/gc: allow registerization of temporaries created by inlining.
    
    Names beginning with a dot are ignored by optimizers.
    
    R=rsc, lvd, golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/7098049

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/dfdfba14b98591a45186a9e9182b71e7df816e2c

元コミット内容

cmd/gc: allow registerization of temporaries created by inlining.

Names beginning with a dot are ignored by optimizers.

R=rsc, lvd, golang-dev, dave
CC=golang-dev
https://golang.org/cl/7098049

変更の背景

この変更の背景には、Goコンパイラ(cmd/gc)の最適化プロセスにおける特定の制約がありました。コミットメッセージに「Names beginning with a dot are ignored by optimizers.(ドットで始まる名前は最適化器によって無視される)」と明記されている通り、Goコンパイラ内部では、一時変数や匿名変数に.anon%d.r%dのようなドット(.)で始まる名前が付けられていました。

しかし、コンパイラの最適化パス、特にレジスタ割り当てを行う部分では、慣習的にドットで始まるシンボル名が「デバッグ情報用」や「内部的な特殊な変数」として扱われ、レジスタ割り当ての対象から除外される傾向がありました。これにより、インライン化(inlining)によって生成された一時変数(temporaries)がレジスタに割り当てられず、メモリ上に配置されることになり、結果としてプログラムの実行速度が低下する可能性がありました。

このコミットは、このような最適化の障壁を取り除き、インライン化によって生成される一時変数も適切にレジスタに割り当てられるようにすることで、Goプログラムのパフォーマンスを向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGoコンパイラおよび一般的なコンパイラの概念に関する知識が必要です。

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラの一つで、Goソースコードを機械語に変換します。gcはGoコンパイラの歴史的な名称であり、現在も内部的にはこの名前が使われることがあります。
  • 一時変数 (Temporaries): プログラムの実行中に一時的に値を保持するためにコンパイラが内部的に生成する変数です。これらはソースコードには明示的に現れませんが、式の評価や関数呼び出しの際に頻繁に利用されます。例えば、a + b * cのような式では、b * cの結果を一時的に保持する変数が生成されることがあります。
  • インライン化 (Inlining): コンパイラ最適化の一種で、関数呼び出しのオーバーヘッドを削減するために、呼び出される関数の本体を呼び出し元のコードに直接埋め込む(インライン展開する)技術です。これにより、関数呼び出しのスタックフレームの作成や破棄といった処理が不要になり、パフォーマンスが向上します。インライン化された関数内で使用されていたローカル変数や一時変数は、呼び出し元のコンテキストに「移動」し、新たな一時変数として扱われることがあります。
  • レジスタ割り当て (Registerization / Register Allocation): コンパイラ最適化の最も重要な部分の一つです。プログラムで使用される変数の値をCPUのレジスタに割り当てるプロセスを指します。レジスタはメモリよりもはるかに高速にアクセスできるため、変数をレジスタに配置することでプログラムの実行速度が大幅に向上します。コンパイラは、どの変数をどのレジスタに割り当てるか、いつメモリに退避させるか(スピル)、いつレジスタにロードするかを決定します。
  • エスケープ解析 (Escape Analysis): コンパイラ最適化の一種で、変数がそのスコープを「エスケープ」するかどうかを分析します。具体的には、ローカル変数が関数から返されたり、グローバル変数やヒープ上のデータ構造に格納されたりして、その変数の寿命が関数の実行期間を超えるかどうかを判断します。エスケープしない変数はスタックに割り当てることができ、エスケープする変数はヒープに割り当てる必要があります。スタック割り当てはヒープ割り当てよりも高速です。このコミットのコード変更箇所には「// give it a name so escape analysis has nodes to work with」というコメントがあり、エスケープ解析が一時変数を適切に追跡できるように名前を付けることの重要性を示唆しています。
  • シンボル名と最適化器の挙動: コンパイラ内部では、変数、関数、型など、プログラムの様々な要素が「シンボル」として表現され、それぞれに名前が付けられます。最適化器はこれらのシンボル名を基に処理を行うことがありますが、特定の命名規則(例:ドットで始まる名前)を持つシンボルを、最適化の対象から除外する、あるいは特殊な方法で扱うことがあります。これは、コンパイラの内部的なデバッグ情報や、ユーザーコードからは見えない内部的な処理のための変数と区別するためによく行われます。

技術的詳細

このコミットの技術的な核心は、Goコンパイラが内部的に生成する一時変数の命名規則の変更にあります。

Goコンパイラは、ソースコードには直接現れない一時的な値を保持するために、内部的に匿名変数や一時変数を生成します。これらの変数には、デバッグや内部処理のために一意の名前が割り当てられます。変更前は、これらの名前は慣習的にドット(.)で始まる形式でした。例えば、dcl.cでは匿名変数に.anon%dという名前が、inl.cではインライン化された関数の戻り値用の一時変数に.r%dという名前が付けられていました。

しかし、Goコンパイラの最適化パス、特にレジスタ割り当てを行う部分では、ドットで始まるシンボル名を持つ変数を最適化の対象から除外する、あるいはレジスタ割り当てを行わないという内部的なルールが存在していました。これは、ドットで始まる名前が、デバッグ情報やコンパイラ内部の特殊な目的のために予約された変数であることを示す慣習的なマーカーとして機能していたためです。

このルールにより、インライン化によって生成された一時変数がドットで始まる名前を持つ場合、それらはレジスタに割り当てられず、必然的にメモリ上に配置されることになります。メモリへのアクセスはCPUレジスタへのアクセスよりもはるかに遅いため、これはプログラムのパフォーマンスに悪影響を与えていました。

このコミットでは、この問題を解決するために、一時変数の命名規則をドット(.)からチルダ(~)に変更しています。具体的には、snprint関数で名前を生成する際に、フォーマット文字列を.anon%dから~anon%dへ、.r%dから~r%dへと変更しています。

この変更により、一時変数がチルダで始まる名前を持つようになり、最適化器がこれらの変数を通常の変数と同様に扱い、レジスタ割り当ての対象とすることができるようになります。結果として、インライン化によって生成される一時変数も効率的にレジスタに配置されるようになり、Goプログラムの実行時パフォーマンスが向上します。

この変更は、コンパイラの内部的な命名規則と最適化器の挙動の間の不整合を解消し、より積極的な最適化を可能にするための重要な修正と言えます。

コアとなるコードの変更箇所

変更は以下の2つのファイルで行われています。

src/cmd/gc/dcl.c

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -638,7 +638,7 @@ funcargs(Node *nt)
 
 		if(n->left == N) {
 			// give it a name so escape analysis has nodes to work with
-			snprint(namebuf, sizeof(namebuf), ".anon%d", gen++);
+			snprint(namebuf, sizeof(namebuf), "~anon%d", gen++);
 			n->left = newname(lookup(namebuf));
 			n->left->orig = N;  // signal that the original was absent
 
@@ -653,7 +653,7 @@ funcargs(Node *nt)
 			*nn = *n->left;
 			n->left = nn;
 			
-			snprint(namebuf, sizeof(namebuf), ".anon%d", gen++);
+			snprint(namebuf, sizeof(namebuf), "~anon%d", gen++);
 			n->left->sym = lookup(namebuf);
 		}

src/cmd/gc/inl.c

--- a/src/cmd/gc/inl.c
+++ b/src/cmd/gc/inl.c
@@ -699,7 +699,7 @@ retvar(Type *t, int i)
 {
 	Node *n;
 
-	snprint(namebuf, sizeof(namebuf), ".r%d", i);
+	snprint(namebuf, sizeof(namebuf), "~r%d", i);
 	n = newname(lookup(namebuf));
 	n->type = t->type;
 	n->class = PAUTO;

コアとなるコードの解説

  • src/cmd/gc/dcl.c:

    • このファイルは、Goコンパイラの宣言(declaration)処理に関連する部分です。特に、funcargs関数は関数引数や戻り値の処理、および匿名の一時変数の生成に関わっています。
    • 変更箇所は、snprint(namebuf, sizeof(namebuf), ".anon%d", gen++); の部分が snprint(namebuf, sizeof(namebuf), "~anon%d", gen++); に変更されています。
    • gen++ は、一意な名前を生成するためのカウンタです。
    • .anon%d は、匿名の一時変数に割り当てられる名前のパターンです。この変更により、匿名の一時変数の名前がドット(.)ではなくチルダ(~)で始まるようになります。
    • コメント「// give it a name so escape analysis has nodes to work with」は、エスケープ解析がこれらの匿名変数を追跡し、適切にスタックまたはヒープに割り当てるために、名前を付ける必要があることを示しています。
  • src/cmd/gc/inl.c:

    • このファイルは、Goコンパイラのインライン化(inlining)処理に関連する部分です。retvar関数は、インライン化された関数の戻り値を保持するための一時変数を生成する際に使用されます。
    • 変更箇所は、snprint(namebuf, sizeof(namebuf), ".r%d", i); の部分が snprint(namebuf, sizeof(namebuf), "~r%d", i); に変更されています。
    • .r%d は、インライン化された関数の戻り値用の一時変数に割り当てられる名前のパターンです。この変更により、これらの変数もチルダ(~)で始まる名前を持つようになります。
    • PAUTO クラスは、この変数が自動変数(スタックに割り当てられる可能性のあるローカル変数)であることを示しています。

これらの変更は、コンパイラが内部的に生成する一時変数の命名規則を統一し、最適化器がこれらの変数をレジスタ割り当ての対象として適切に認識できるようにすることを目的としています。ドットからチルダへの変更は、Goコンパイラの内部的な慣習と最適化器の挙動の間の不整合を解消するための、シンプルながらも効果的な修正です。

関連リンク

参考にした情報源リンク

  • (必要に応じてWeb検索の結果をここに記載)
    • Goコンパイラの内部構造に関する一般的な情報源
    • コンパイラのレジスタ割り当てに関する一般的な情報源
    • エスケープ解析に関する一般的な情報源
    • Go言語のインライン化に関する情報源
    • Go言語のコンパイラ最適化に関する情報源
    • Go言語のIssueトラッカーやメーリングリストでの関連議論(もしあれば)
    • Go言語のソースコード(src/cmd/gcディレクトリ内の他のファイルや関連するドキュメント)

(注:上記の「参考にした情報源リンク」は、この解説を生成する際に実際にWeb検索を行った場合に具体的なURLを記載します。今回は提供された情報のみで解説を生成したため、一般的な情報源のカテゴリを記載しています。)