[インデックス 17454] ファイルの概要
このコミットは、Goランタイムにおけるハッシュテーブル(マップ)の内部構造に対するガベージコレクション(GC)の扱い方を変更するものです。具体的には、ハッシュテーブルに特化したGCコードを削除し、代わりに型情報に基づいてGCが動作するように修正しています。これにより、GCの汎用性が向上し、マップのGC処理がより効率的かつ正確になります。
コミット
commit fb376021bea084d5320a8059176ab86880832f5c
Author: Keith Randall <khr@golang.org>
Date: Sat Aug 31 09:09:50 2013 -0700
runtime: record type information for hashtable internal structures.
Remove all hashtable-specific GC code.
Fixes bug 6119.
R=cshapiro, dvyukov, khr
CC=golang-dev
https://golang.org/cl/13078044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fb376021bea084d5320a8059176ab86880832f5c
元コミット内容
Goランタイムにおいて、ハッシュテーブルの内部構造に関する型情報を記録するように変更し、ハッシュテーブルに特化したGCコードをすべて削除します。これにより、バグ6119が修正されます。
変更の背景
Goのガベージコレクタは、ヒープ上のオブジェクトを正確に識別し、到達可能なオブジェクトを保持し、到達不能なオブジェクトを解放する役割を担っています。以前のGoランタイムでは、マップ(ハッシュテーブル)の内部構造(バケットやHmapヘッダなど)をGCが処理するために、マップに特化したGCロジックがsrc/pkg/runtime/mgc0.c
内に存在していました。
このアプローチにはいくつかの問題がありました。
- GCロジックの重複と複雑性: マップの内部構造が変更されるたびに、GCコードも手動で更新する必要があり、GCロジックが複雑化し、バグの温床となる可能性がありました。
- 汎用性の欠如: マップに特化したGCコードは、他のデータ構造のGCには適用できず、GCシステムの汎用性を損なっていました。
- 正確性の問題: マップの内部構造がGCによって正しくスキャンされない場合、メモリリークや不正なメモリ解放が発生する可能性がありました。
このコミットは、これらの問題を解決するために、マップの内部構造を通常のGoの型システムに統合し、GCがその型情報に基づいて自動的にマップをスキャンできるようにすることを目指しています。これにより、マップに特化したGCコードが不要になり、GCシステムの堅牢性と保守性が向上します。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムとGCに関する基本的な知識が必要です。
- Goのマップ(ハッシュテーブル)の内部構造: Goのマップは、キーと値のペアを格納するためのデータ構造です。内部的には、
Hmap
というヘッダ構造と、Bucket
と呼ばれるバケットの配列で構成されています。各バケットは複数のキーと値を保持でき、オーバーフローした場合には追加のバケット(オーバーフローバケット)にリンクされます。 - Goのガベージコレクション(GC): Goはトレース型GCを採用しており、ヒープ上のオブジェクトをスキャンして到達可能性を判断します。GCは、オブジェクトの型情報(
_type
構造体)を利用して、そのオブジェクトがポインタを含むかどうか、含まれる場合はどのオフセットにポインタがあるかを判断します。 - GCプログラム(GC Prog): GoのGCは、オブジェクトのレイアウトを記述する「GCプログラム」と呼ばれるバイトコードのようなものを使用します。このプログラムは、GCがオブジェクトをスキャンする際に、どの部分がポインタであり、どの部分がポインタでないかを指示します。
reflect
パッケージ: Goのreflect
パッケージは、実行時にGoの型情報を検査・操作するための機能を提供します。ランタイム内部でも、型の情報を扱うために利用されます。src/cmd/gc
: Goコンパイラのバックエンドの一部で、型情報の生成やGCプログラムの生成など、ランタイムと密接に関連する処理を行います。src/pkg/runtime
: Goランタイムのソースコードが含まれるディレクトリで、GC、スケジューラ、メモリ管理など、Goプログラムの実行に必要な低レベルな機能が実装されています。runtime·mallocgc
: Goランタイムのメモリ割り当て関数で、GCヒープからメモリを割り当てます。この関数は、割り当てられたメモリの型情報を受け取り、GCがそのメモリをスキャンできるようにします。
技術的詳細
このコミットの主要な変更点は、マップの内部構造であるHmap
とBucket
を、Goの型システムに組み込むことです。これにより、GCはこれらの構造を通常のGoオブジェクトと同様に扱い、型情報に基づいてポインタをスキャンできるようになります。
具体的には、以下の変更が行われています。
-
src/cmd/gc/go.h
とsrc/cmd/gc/reflect.c
における型情報の追加:Type
構造体にbucket
とhmap
フィールドが追加され、マップのバケット型とHmap型へのポインタを保持できるようになりました。mapbucket
関数とhmap
関数が追加されました。これらの関数は、与えられたマップ型に基づいて、その内部構造であるBucket
とHmap
に対応するType
構造体を動的に生成します。これらの生成された型は、GCがマップの内部構造を正確にスキャンするために必要な情報(サイズ、ポインタのオフセットなど)を含んでいます。dtypesym
関数が変更され、マップ型を処理する際に、新しく生成されたBucket
型とHmap
型のシンボルもGCに登録されるようになりました。
-
src/pkg/reflect/type.go
における型情報の拡張:mapType
構造体にbucket
とhmap
フィールドが追加され、ランタイムレベルでマップの内部構造の型情報を保持できるようになりました。MapOf
関数(マップ型を生成する関数)が変更され、bucketOf
とhMapOf
関数を呼び出して、マップのキーと値の型に基づいてBucket
型とHmap
型を生成し、mapType
に設定するようになりました。bucketOf
関数とhMapOf
関数は、reflect
パッケージ内でBucket
とHmap
のGCプログラムを動的に構築します。これにより、GCはこれらの内部構造を適切にスキャンできます。特に、_GC_PTR
などのGC命令を使用して、ポインタフィールドのオフセットをGCプログラムにエンコードしています。chanMapGC
がchanGC
にリネームされ、マップに特化したGCプログラムが削除されました。
-
src/pkg/runtime/hashmap.c
におけるGCコードの削除とruntime·mallocgc
の変更:hashmap.c
から、マップのバケットやキー、値のGCに関する明示的なコード(clearbucket
マクロの削除、CanFreeBucket
、CanFreeKey
フラグの削除、hash_gciter
関連の関数の削除など)が大幅に削除されました。runtime·mallocgc
の呼び出しにおいて、マップのバケットやHmapを割り当てる際に、以前は汎用的なTypeInfo_Array
やTypeInfo_Map
を使用していた箇所が、新しく生成されたt->bucket
やt->hmap
といった具体的な型情報(uintptr)t->bucket | TypeInfo_Array
のように)を渡すように変更されました。これにより、GCは割り当てられたメモリがマップのどの内部構造であるかを正確に認識し、その型情報に基づいてスキャンできるようになります。
-
src/pkg/runtime/mgc0.c
とsrc/pkg/runtime/mgc0.h
におけるGCロジックの簡素化:mgc0.c
から、マップに特化したGCスキャンロジック(TypeInfo_Map
ケースの削除、GC_MAP_NEXT
命令の削除など)が削除されました。mgc0.h
からGC_MAP_PTR
命令が削除されました。
これらの変更により、GoのGCは、マップの内部構造を明示的に知る必要がなくなり、汎用的な型情報に基づいてポインタをスキャンするメカニズムに完全に依存するようになりました。これは、GCシステムの設計における重要な改善であり、将来的なデータ構造の追加や変更に対するGCの適応性を高めます。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に以下のファイルに集中しています。
src/cmd/gc/reflect.c
:mapbucket
関数とhmap
関数の追加。これらは、マップのバケットとHmapヘッダの型情報を動的に生成します。dtypesym
関数におけるマップ型処理の変更。新しく生成されたBucket
型とHmap
型のシンボルをGCに登録します。
src/pkg/reflect/type.go
:mapType
構造体へのbucket
とhmap
フィールドの追加。MapOf
関数におけるbucketOf
とhMapOf
関数の呼び出しによる内部構造の型情報の設定。bucketOf
関数とhMapOf
関数の追加。これらは、Bucket
とHmap
のGCプログラムを動的に構築します。
src/pkg/runtime/hashmap.c
:- マップに特化したGC関連のフラグ(
CanFreeBucket
,CanFreeKey
)やマクロ(clearbucket
)の削除。 runtime·mallocgc
の呼び出しにおいて、マップの内部構造を割り当てる際に、具体的な型情報(t->bucket
やt->hmap
)を渡すように変更。hash_gciter
関連の構造体と関数の削除。
- マップに特化したGC関連のフラグ(
src/pkg/runtime/mgc0.c
:- マップに特化したGCスキャンロジックの削除。
コアとなるコードの解説
src/cmd/gc/reflect.c
// Builds a type respresenting a Bucket structure for
// the given map type. This type is not visible to users -
// we include only enough information to generate a correct GC
// program for it.
// Make sure this stays in sync with ../../pkg/runtime/hashmap.c!
static Type*
mapbucket(Type *t)
{
// ... (Bucket構造体のフィールドに対応するType構造体を構築)
// overflowfield->type = ptrto(bucket); // overflowフィールドはBucketへのポインタ
// keysfield->type->type = keytype; // keysフィールドはキーの配列
// valuesfield->type->type = valtype; // valuesフィールドは値の配列
// ...
}
// Builds a type respresenting a Hmap structure for
// the given map type. This type is not visible to users -
// we include only enough information to generate a correct GC
// program for it.
// Make sure this stays in sync with ../../pkg/runtime/hashmap.c!
static Type*
hmap(Type *t)
{
// ... (Hmap構造体のフィールドに対応するType構造体を構築)
// bucketsfield->type = ptrto(bucket); // bucketsフィールドはBucketへのポインタ
// oldbucketsfield->type = ptrto(bucket); // oldbucketsフィールドもBucketへのポインタ
// ...
}
mapbucket
とhmap
関数は、コンパイラがマップの内部構造(Bucket
とHmap
)の型情報を生成するために使用されます。これらの型情報は、GCがマップのメモリレイアウトを理解し、ポインタを正確にスキャンするために不可欠です。特に、ptrto(bucket)
のように、ポインタフィールドの型を明示的に指定することで、GCが参照を追跡できるようにしています。
src/pkg/reflect/type.go
type mapType struct {
rtype `reflect:"map"`
key *rtype // map key type
elem *rtype // map element (value) type
bucket *rtype // internal bucket structure
hmap *rtype // internal map header
}
func MapOf(key, elem Type) Type {
// ...
mt.bucket = bucketOf(ktyp, etyp)
mt.hmap = hMapOf(mt.bucket)
// ...
}
func bucketOf(ktyp, etyp *rtype) *rtype {
// ...
// GCプログラムを動的に構築
// gc = append(gc, _GC_PTR, offset, 0 /*self pointer set below*/) // overflow
// gc = append(gc, _GC_ARRAY_START, offset, BUCKETSIZE, ktyp.size) // keys
// gc = appendGCProgram(gc, ktyp)
// gc = append(gc, _GC_ARRAY_NEXT)
// ...
}
func hMapOf(bucket *rtype) *rtype {
// ...
// GCプログラムを動的に構築
// gc = append(gc, _GC_PTR, offset, uintptr(bucket.gc)) // buckets
// gc = append(gc, _GC_PTR, offset, uintptr(bucket.gc)) // oldbuckets
// ...
}
mapType
構造体にbucket
とhmap
フィールドが追加されたことで、Goのreflect
パッケージを通じてマップの内部構造の型情報にアクセスできるようになりました。MapOf
関数は、マップ型を生成する際に、bucketOf
とhMapOf
を呼び出して、これらの内部構造の型情報を設定します。
bucketOf
とhMapOf
関数は、GoのGCプログラムを動的に生成する重要な役割を担っています。これらの関数は、_GC_PTR
(ポインタ)、_GC_ARRAY_START
(配列の開始)、_GC_ARRAY_NEXT
(配列の次の要素)などのGC命令を使用して、Bucket
とHmap
構造体内のポインタフィールドのオフセットと型情報をGCプログラムにエンコードします。これにより、GCはこれらの構造体をスキャンする際に、どこにポインタがあるかを正確に判断できます。
src/pkg/runtime/hashmap.c
struct Bucket
{
// Note: the format of the Bucket is encoded in ../../cmd/gc/reflect.c and
// ../reflect/type.go. Don't change this structure without also changing that code!
uint8 tophash[BUCKETSIZE]; // top 8 bits of hash of each entry (0 = empty)
Bucket *overflow; // overflow bucket, if any
byte data[1]; // BUCKETSIZE keys followed by BUCKETSIZE values
};
// ...
struct Hmap
{
// Note: the format of the Hmap is encoded in ../../cmd/gc/reflect.c and
// ../reflect/type.go. Don't change this structure without also changing that code!
uintgo count; // # live cells == size of map. Must be first (used by len() builtin)
uint32 flags;
uint32 hash0; // hash seed
uint8 B; // log_2 of # of buckets (can hold up to LOAD * 2^B items)
uint8 keysize; // key size in bytes
uint8 valuesize; // value size in bytes
uint16 bucketsize; // bucket size in bytes
Bucket *buckets; // array of buckets; may be nil if count==0.
Bucket *oldbuckets; // array of old buckets, nil if no ongoing grow
uintptr nevacuate; // progress counter for evacuation (buckets less than this have been evacuated)
};
// ...
// runtime·mallocgcの呼び出し例
// buckets = runtime·mallocgc(bucketsize << B, (uintptr)t->bucket | TypeInfo_Array, 0);
// newx = runtime·mallocgc(h->bucketsize, (uintptr)t->bucket, 0);
// h = runtime·mallocgc(sizeof(*h), (uintptr)typ->hmap, 0);
hashmap.c
では、Bucket
とHmap
構造体の定義に、これらの構造体のレイアウトがreflect.c
とtype.go
でエンコードされていることを示すコメントが追加されています。これは、これらの構造体を変更する際には、GCが正しく動作するように型情報も更新する必要があることを開発者に警告するものです。
最も重要な変更は、runtime·mallocgc
の呼び出し方です。以前は、マップの内部構造を割り当てる際に、GCに対して汎用的な情報(例: TypeInfo_Array
)しか渡していませんでした。しかし、このコミット以降は、t->bucket
やtyp->hmap
といった、reflect.c
とtype.go
で動的に生成された具体的な型情報を渡すようになりました。これにより、GCは割り当てられたメモリがマップのどの部分であるかを正確に認識し、その型情報に基づいてポインタをスキャンできるようになります。これにより、hashmap.c
からマップに特化したGCロジックを削除することが可能になりました。
関連リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事
- Goの
reflect
パッケージのドキュメント - Goのマップの実装に関する技術記事や解説
参考にした情報源リンク
- Goのソースコード(特に
src/cmd/gc
、src/pkg/reflect
、src/pkg/runtime
ディレクトリ) - GoのIssue Tracker (bug 6119に関する情報があれば)
- GoのCode Reviewシステム (golang.org/cl/13078044に関する情報があれば)
- Goのガベージコレクションに関する技術ブログや論文
- Goのマップ実装に関する技術ブログや論文