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

[インデックス 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と、abを表す子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という余分なオブジェクトを介していたことを意味します。

このコミットでは、以下の変更が行われました。

  1. typenamesym(Type *t)の導入: reflect.cに新しい関数typenamesymが追加されました。この関数は、Typeを受け取り、その型に対応するSym*を直接返します。これにより、型シンボルを取得する際にNodeオブジェクトを生成する必要がなくなりました。
  2. typename(Type *t)の変更: 既存のtypename関数は、そのシグネチャがNode* typename(Type *t)であるため、引き続きNode*を返す必要があります。しかし、その実装はtypenamesymを呼び出してSym*を取得し、そのSym*からNode*を構築するように変更されました。これにより、typenameのセマンティクスは維持しつつ、シンボルを直接取得するパスが分離されました。
  3. 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関数の呼び出し箇所の変更です。

  1. reflect.cの変更:

    • 元のtypename関数は、TypeからNodeを生成し、そのNodeを返していました。しかし、多くの場合、このNode自体ではなく、そのNodeが参照するシンボル(Sym)が必要とされていました。
    • 新しいtypenamesym(Type *t)関数は、Typeから直接Symを取得し、それを返します。これにより、Nodeの生成という中間ステップが不要になります。
    • 元のtypename(Type *t)関数は、そのシグネチャ(Node*を返す)を維持するために残されましたが、その実装はtypenamesymを呼び出してSymを取得し、そのSymからNodeを構築するように変更されました。これは、既存のコードベースでtypenameを呼び出している箇所に影響を与えないための互換性維持策です。
  2. subr.cの変更:

    • ngotype関数は、Nodeの実際の型(n->realtype)からシンボルを取得する必要がありました。変更前は、typename(n->realtype)->left->symという形で、一度typenameNodeを生成し、その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言語のコンパイラ開発に関するブログ記事やプレゼンテーション