[インデックス 14588] ファイルの概要
このコミットは、Goコンパイラのcmd/gc
(現在のcmd/compile
に相当)における最適化に関するものです。具体的には、ngotype
関数が不要なメモリ割り当てを行うのを防ぐための変更が加えられています。これにより、コンパイラのパフォーマンスが向上し、メモリ使用量が削減されることが期待されます。
コミット
commit 10d14b63c2c2693cd2132859c34342c5f4b398f6
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Sun Dec 9 19:27:23 2012 +0100
cmd/gc: prevent ngotype from allocating.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6904061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/10d14b63c2c2693cd2132859c34342c5f4b398f6
元コミット内容
このコミットの元の内容は、Goコンパイラのcmd/gc
(現在のcmd/compile
)において、ngotype
関数が型情報を取得する際に発生していた不要なメモリ割り当てを排除することです。具体的には、typename
関数がNode*
を返していたために発生していた中間オブジェクトの生成を避け、直接Sym*
(シンボル)を返す新しいヘルパー関数typenamesym
を導入することで、この割り当てを回避しています。
変更の背景
Goコンパイラは、コンパイル速度とメモリ効率が非常に重視されるシステムです。コンパイルプロセス中に発生する小さなメモリ割り当ても、大量のコードを処理する際には累積して大きなオーバーヘッドとなる可能性があります。特に、型情報を頻繁に参照するような処理では、その都度オブジェクトが生成されると、ガベージコレクションの頻度が増加し、コンパイル時間が長くなる原因となります。
このコミットは、ngotype
関数が型シンボルを取得する際に、typename
関数を介してNode
オブジェクトを一時的に生成し、そのNode
からシンボルを取り出すという非効率なパスを辿っていた問題を解決するために導入されました。このNode
オブジェクトの生成が不要な割り当てであり、これを排除することでコンパイラの効率を改善することが目的でした。
前提知識の解説
このコミットを理解するためには、Goコンパイラの内部構造、特に以下の概念について理解しておく必要があります。
cmd/gc
(現在のcmd/compile
): Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する主要な役割を担っています。このディレクトリには、字句解析、構文解析、型チェック、中間表現の生成、最適化、コード生成など、コンパイルの様々なフェーズを処理するコードが含まれています。Type
: Goコンパイラ内部で型情報を表現するためのデータ構造です。例えば、int
,string
,struct { ... }
,func(...)
などのGoの型は、コンパイラ内部ではType
構造体として表現されます。Sym
(Symbol): シンボルは、変数、関数、型、パッケージなど、プログラム内の名前付きエンティティを一意に識別するためのデータ構造です。コンパイラはシンボルテーブルを管理し、各シンボルに関連する型情報、スコープ、アドレスなどのメタデータを格納します。Sym
は、プログラムの要素を効率的に参照・管理するために不可欠です。Node
: 抽象構文木(AST)のノードを表すデータ構造です。Goのソースコードは、構文解析フェーズでNode
のツリー構造に変換されます。各Node
は、式、文、宣言など、プログラムの構文要素に対応します。例えば、a + b
という式は、OADD
(加算演算子)を表すNode
と、a
とb
を表す子Node
で構成されます。- メモリ割り当てとガベージコレクション: プログラムが実行時にメモリを要求することを「メモリ割り当て」と呼びます。Go言語にはガベージコレクタ(GC)が組み込まれており、不要になったメモリを自動的に解放します。しかし、頻繁な小さな割り当てはGCの負荷を増やし、パフォーマンスに影響を与える可能性があります。コンパイラのようなパフォーマンスが重要なアプリケーションでは、不要な割り当てを避けることが最適化の重要な側面となります。
ngotype
関数: この関数は、Goコンパイラの型システムの一部であり、特定のNode
に関連付けられたGoの型(Type
)を取得する役割を担っています。特に、コンパイラが内部的に生成する一時変数(autotmp_
で始まるシンボル名を持つもの)でない限り、そのNode
の実際の型(realtype
)に対応するシンボルを返すことを目的としています。
技術的詳細
このコミットの技術的な核心は、typename
関数の役割の分離と、それによる中間Node
オブジェクトの生成回避です。
変更前、typename(Type *t)
関数は、与えられたType
に対応するシンボル(Sym
)を内部的に取得し、そのシンボルを基にNode
オブジェクトを生成して返していました。ngotype
関数は、このtypename
が返したNode*
から、さらに->left->sym
という形でシンボルを取り出していました。これは、シンボル自体が欲しいにもかかわらず、一度Node
という余分なオブジェクトを介していたことを意味します。
このコミットでは、以下の変更が行われました。
typenamesym(Type *t)
の導入:reflect.c
に新しい関数typenamesym
が追加されました。この関数は、Type
を受け取り、その型に対応するSym*
を直接返します。これにより、型シンボルを取得する際にNode
オブジェクトを生成する必要がなくなりました。typename(Type *t)
の変更: 既存のtypename
関数は、そのシグネチャがNode* typename(Type *t)
であるため、引き続きNode*
を返す必要があります。しかし、その実装はtypenamesym
を呼び出してSym*
を取得し、そのSym*
からNode*
を構築するように変更されました。これにより、typename
のセマンティクスは維持しつつ、シンボルを直接取得するパスが分離されました。ngotype
の修正:subr.c
内のngotype
関数は、typename(n->realtype)->left->sym
という呼び出しを、新しく導入されたtypenamesym(n->realtype)
に直接変更しました。これにより、ngotype
は型シンボルを直接取得できるようになり、中間的なNode
オブジェクトの生成と、それに伴うメモリ割り当てが完全に回避されます。
この変更は、コンパイラのホットパス(頻繁に実行されるコードパス)における小さな最適化であり、特に大規模なGoプログラムのコンパイル時に、ガベージコレクションの負荷を軽減し、コンパイル時間を短縮する効果が期待されます。
コアとなるコードの変更箇所
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -1211,6 +1211,7 @@ void dumptypestructs(void);\
Type* methodfunc(Type *f, Type*);\
Node* typename(Type *t);\
Sym* typesym(Type *t);\
+Sym* typenamesym(Type *t);\
Sym* tracksym(Type *t);\
Sym* typesymprefix(char *prefix, Type *t);\
int haspointers(Type *t);
typenamesym(Type *t)
関数のプロトタイプが追加されました。
src/cmd/gc/reflect.c
--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -652,8 +652,8 @@ typesymprefix(char *prefix, Type *t)\
return s;\
}\
\
-Node*\
-typename(Type *t)\
+Sym*\
+typenamesym(Type *t)\
{\
Sym *s;\
Node *n;\
@@ -674,7 +674,16 @@ typename(Type *t)\
\
signatlist = list(signatlist, typenod(t));\
}\
+\treturn s->def->sym;\
+}\
+\
+Node*\
+typename(Type *t)\
+{\
+\tSym *s;\
+\tNode *n;\
\
+\ts = typenamesym(t);\
\tn = nod(OADDR, s->def, N);\
\tn->type = ptrto(s->def->type);\
\tn->addable = 1;\
typename
関数の名前がtypenamesym
に変更され、戻り値の型がNode*
からSym*
になりました。- 元の
typename
関数が再定義され、内部でtypenamesym
を呼び出し、その結果からNode*
を構築して返すようになりました。
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -3517,7 +3517,7 @@ ngotype(Node *n)\
{\
if(n->sym != S && n->realtype != T)\
if(strncmp(n->sym->name, "autotmp_", 8) != 0)\
-\t\treturn typename(n->realtype)->left->sym;\
+\t\treturn typenamesym(n->realtype);\
\
return S;\
}\
ngotype
関数内のtypename(n->realtype)->left->sym
という呼び出しが、typenamesym(n->realtype)
に直接変更されました。
コアとなるコードの解説
このコミットの核心は、reflect.c
におけるtypename
関数の分割とsubr.c
におけるngotype
関数の呼び出し箇所の変更です。
-
reflect.c
の変更:- 元の
typename
関数は、Type
からNode
を生成し、そのNode
を返していました。しかし、多くの場合、このNode
自体ではなく、そのNode
が参照するシンボル(Sym
)が必要とされていました。 - 新しい
typenamesym(Type *t)
関数は、Type
から直接Sym
を取得し、それを返します。これにより、Node
の生成という中間ステップが不要になります。 - 元の
typename(Type *t)
関数は、そのシグネチャ(Node*
を返す)を維持するために残されましたが、その実装はtypenamesym
を呼び出してSym
を取得し、そのSym
からNode
を構築するように変更されました。これは、既存のコードベースでtypename
を呼び出している箇所に影響を与えないための互換性維持策です。
- 元の
-
subr.c
の変更:ngotype
関数は、Node
の実際の型(n->realtype
)からシンボルを取得する必要がありました。変更前は、typename(n->realtype)->left->sym
という形で、一度typename
でNode
を生成し、そのNode
の内部構造を辿ってシンボルを取得していました。- この変更により、
ngotype
は直接typenamesym(n->realtype)
を呼び出すことで、必要なシンボルを直接取得できるようになりました。これにより、typename
が生成していた一時的なNode
オブジェクトが不要になり、その分のメモリ割り当てとガベージコレクションのオーバーヘッドが削減されます。
この最適化は、コンパイラの内部で頻繁に型シンボルが参照される箇所において、小さなメモリ割り当てを積み重ねることを防ぎ、全体的なコンパイルパフォーマンスの向上に寄与します。
関連リンク
- Go言語のコンパイラに関するドキュメントやソースコードは、Goの公式リポジトリで確認できます。
- Goコンパイラの内部構造に関する情報は、Goの公式ブログやGo開発者向けのドキュメントで提供されることがあります。
参考にした情報源リンク
- Go CL 6904061: cmd/gc: prevent ngotype from allocating. (このコミットのChange-ID)
- Go言語のコンパイラソースコード (特に
src/cmd/gc
ディレクトリ、現在のsrc/cmd/compile
) - Go言語のガベージコレクションに関する一般的な情報源
- Go言語のシンボルテーブルやASTに関する一般的な情報源
- Go言語のコンパイラ開発に関するブログ記事やプレゼンテーション