[インデックス 17204] ファイルの概要
このコミットは、Goランタイムにおけるハッシュマップのメモリ管理とガベージコレクション(GC)に関する以前の変更(CL 12840043 / 3b9f54db72a1)を取り消すものです。具体的には、ハッシュマップの内部構造(バケット配列)がGCによってスキャンされないようにする試みを元に戻し、その変更がビルドを破壊し、イテレータによって保持されている古いバケット配列がGCによって適切にスキャンされる必要があるという問題に対処しています。
コミット
commit 74e78df107f9e6642ecfc6808f542e2625f7cfb2
Author: Keith Randall <khr@golang.org>
Date: Tue Aug 13 12:59:39 2013 -0700
undo CL 12840043 / 3b9f54db72a1
Breaks the build. Old bucket arrays kept by iterators
still need to be scanned.
««« original CL description
runtime: tell GC not to scan internal hashmap structures.
We'll do it ourselves via hash_gciter, thanks.
Fixes bug 6119.
R=golang-dev, dvyukov, cookieo9, rsc
CC=golang-dev
https://golang.org/cl/12840043
»»»
R=golang-dev
CC=golang-dev
https://golang.org/cl/12884043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/74e78df107f9e6642ecfc6808f542e2625f7cfb2
元コミット内容
このコミットが取り消した元のコミット(CL 12840043 / 3b9f54db72a1)の目的は以下の通りです。
「runtime: tell GC not to scan internal hashmap structures. We'll do it ourselves via hash_gciter, thanks. Fixes bug 6119.」
これは、Goランタイムのハッシュマップ(map
型)の内部構造(特にバケット配列)について、Goのガベージコレクタ(GC)が直接スキャンしないように指示し、代わりにhash_gciter
というカスタムのGCイテレータを通じてこれらの構造をGCに認識させることで、バグ6119を修正しようとしたものです。この変更は、runtime·mallocgc
関数を呼び出す際にFlagNoPointers
フラグを追加することで実現されていました。
変更の背景
元のコミット(CL 12840043)は、GoのハッシュマップのGC処理を最適化し、特定のバグ(バグ6119)を修正することを目的としていました。バグ6119は、ハッシュマップのバケット配列がGCによって適切に処理されないことに関連する可能性があり、FlagNoPointers
フラグを使用してGCの挙動を変更することで、この問題を解決しようとしました。
しかし、このコミット(CL 12884043)のコミットメッセージによると、元の変更は「Breaks the build. Old bucket arrays kept by iterators still need to be scanned.」という問題を引き起こしました。これは、ハッシュマップのイテレータが古いバケット配列を参照し続けている場合、GCがこれらの古い配列をスキャンしないと、参照されているオブジェクトが誤って解放されてしまう可能性があることを示唆しています。つまり、FlagNoPointers
フラグによってGCがバケット配列をスキャンしなくなった結果、イテレータが保持する古いバケット配列内のポインタがGCによって追跡されなくなり、メモリ安全性や正確なGC処理が損なわれるという重大な問題が発生したため、元の変更を取り消す必要が生じました。
前提知識の解説
Goのガベージコレクション (GC)
Goのガベージコレクションは、プログラムが動的に確保したメモリのうち、もはや到達不可能(参照されていない)なオブジェクトを自動的に解放する仕組みです。GoのGCは並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGCが動作することで、アプリケーションの一時停止時間(ストップ・ザ・ワールド時間)を最小限に抑えることを目指しています。
GCは、プログラムが使用しているオブジェクトを「マーク」し、マークされなかったオブジェクトを「スイープ」(解放)します。このマークフェーズでは、GCはルート(グローバル変数、スタック上の変数など)から到達可能なすべてのオブジェクトをたどってマークします。この「たどる」プロセスにおいて、オブジェクトが他のオブジェクトへのポインタを含んでいる場合、GCはそのポインタをたどってさらにオブジェクトをマークします。
runtime·mallocgc
と FlagNoPointers
runtime·mallocgc
は、Goランタイム内部で使用されるメモリ割り当て関数です。これは、GCによって管理されるヒープメモリを割り当てる際に使用されます。この関数は、割り当てるメモリのサイズ、アラインメント、そしてGCに関するフラグを受け取ります。
FlagNoPointers
は、runtime·mallocgc
に渡されるフラグの一つで、割り当てられるメモリ領域にポインタが含まれていないことをGCに伝えるために使用されます。このフラグが設定されている場合、GCはこのメモリ領域をポインタの有無についてスキャンしません。これは、GCのオーバーヘッドを削減するために使用される最適化手法の一つです。例えば、純粋なデータ構造(整数や浮動小数点数のみを含む配列など)を割り当てる際にこのフラグを使用することで、GCがその領域を不必要にスキャンするのを避けることができます。
しかし、このフラグを誤って使用すると、GCが実際にポインタを含むメモリ領域をスキャンしなくなり、その結果、到達可能なオブジェクトが誤って解放されたり、メモリリークが発生したりする可能性があります。
Goのハッシュマップ (map
) の内部構造
Goのmap
型は、内部的にはハッシュテーブルとして実装されています。ハッシュテーブルは、キーと値のペアを格納するために「バケット」と呼ばれる配列を使用します。各バケットは、複数のキーと値のペアを保持できる小さな構造体です。ハッシュ衝突が発生した場合(異なるキーが同じバケットにハッシュされる場合)、Goのマップはオーバーフローバケットやチェイニングなどのメカニズムを使用して衝突を解決します。
マップが成長すると、より多くのバケットが必要になり、既存のバケット配列が拡張されたり、新しいバケット配列にデータが再ハッシュされたりすることがあります。このプロセス中に、古いバケット配列が一時的に存在し続けることがあります。
イテレータとGC
Goのマップはイテレータ(for range
ループなど)をサポートしています。イテレータは、マップの要素を順に処理するために使用されます。イテレータが動作している間、マップの内部構造(特にバケット配列)は変更されないように保護される必要があります。マップのサイズ変更(成長)が発生した場合、イテレータは古いバケット配列を参照し続けることがあります。
GCの観点から見ると、イテレータが参照している古いバケット配列は、まだ「到達可能」なメモリ領域と見なされるべきです。なぜなら、イテレータがその配列内のポインタを介してキーや値にアクセスする可能性があるからです。もしGCがこれらの古いバケット配列をスキャンしない場合、それらが参照しているキーや値のオブジェクトがGCによって誤って解放されてしまう可能性があります。
技術的詳細
このコミットは、src/pkg/runtime/hashmap.c
ファイルに対する変更を取り消しています。具体的には、runtime·mallocgc
関数の呼び出しからFlagNoPointers
フラグを削除しています。
元のコミットでは、ハッシュマップのバケット配列を割り当てる際に、GCがその配列をスキャンしないようにFlagNoPointers
フラグが追加されていました。これは、バケット配列自体はポインタの配列ですが、GCはhash_gciter
というカスタムイテレータを通じてこれらのポインタを明示的に処理するという意図がありました。このアプローチは、GCの効率を向上させ、バグ6119を修正することを目的としていました。
しかし、このコミットが示すように、この変更は「Breaks the build. Old bucket arrays kept by iterators still need to be scanned.」という問題を引き起こしました。これは、FlagNoPointers
が設定されたことで、GCがバケット配列をスキャンしなくなり、その結果、イテレータが保持している古いバケット配列内のポインタがGCによって追跡されなくなったことを意味します。イテレータがまだアクティブであるにもかかわらず、GCが古いバケット配列を到達不可能と判断し、その中のポインタが指すオブジェクトを解放してしまう可能性がありました。これは、メモリ破損やクラッシュにつながる重大なバグです。
このコミットは、この問題を解決するために、runtime·mallocgc
呼び出しからFlagNoPointers
フラグを削除し、GCがハッシュマップのバケット配列を通常通りスキャンするように戻しました。これにより、イテレータが参照する古いバケット配列もGCによって適切に処理され、メモリの安全性が確保されます。
コアとなるコードの変更箇所
src/pkg/runtime/hashmap.c
ファイルにおいて、runtime·mallocgc
関数の呼び出しから FlagNoPointers
フラグが削除されています。
具体的には、以下の行で変更が行われています。
--- a/src/pkg/runtime/hashmap.c
+++ b/src/pkg/runtime/hashmap.c
@@ -259,10 +259,7 @@ hash_init(MapType *t, Hmap *h, uint32 hint)
// done lazily later.
buckets = nil;
} else {
- buckets = runtime·mallocgc(bucketsize << B, 0, FlagNoZero | FlagNoPointers);
- // Note: the array really does have pointers, but we tell the gc about
- // them explicitly via gciter below. We use FlagNoPointers to prevent
- // the gc from scanning the bucket array itself. Fixes issue 6119.
+ buckets = runtime·mallocgc(bucketsize << B, 0, FlagNoZero);
for(i = 0; i < (uintptr)1 << B; i++) {
b = (Bucket*)(buckets + i * bucketsize);
clearbucket(b);
@@ -333,7 +330,7 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket)
if((hash & newbit) == 0) {
if(xi == BUCKETSIZE) {
if(checkgc) mstats.next_gc = mstats.heap_alloc;
- newx = runtime·mallocgc(h->bucketsize, 0, FlagNoZero | FlagNoPointers);
+ newx = runtime·mallocgc(h->bucketsize, 0, FlagNoZero);
clearbucket(newx);
x->overflow = newx;
x = newx;
@@ -358,7 +355,7 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket)
} else {
if(yi == BUCKETSIZE) {
if(checkgc) mstats.next_gc = mstats.heap_alloc;
- newy = runtime·mallocgc(h->bucketsize, 0, FlagNoZero | FlagNoPointers);
+ newy = runtime·mallocgc(h->bucketsize, 0, FlagNoZero);
clearbucket(newy);
y->overflow = newy;
y = newy;
@@ -454,7 +451,7 @@ hash_grow(MapType *t, Hmap *h)
old_buckets = h->buckets;
// NOTE: this could be a big malloc, but since we don't need zeroing it is probably fast.
if(checkgc) mstats.next_gc = mstats.heap_alloc;
- new_buckets = runtime·mallocgc((uintptr)h->bucketsize << (h->B + 1), 0, FlagNoZero | FlagNoPointers);
+ new_buckets = runtime·mallocgc((uintptr)h->bucketsize << (h->B + 1), 0, FlagNoZero);
flags = (h->flags & ~(Iterator | OldIterator));
if((h->flags & Iterator) != 0) {
flags |= OldIterator;
@@ -618,7 +615,7 @@ hash_insert(MapType *t, Hmap *h, void *key, void *value)
hash = h->hash0;
t->key->alg->hash(&hash, t->key->size, key);
if(h->buckets == nil) {
- h->buckets = runtime·mallocgc(h->bucketsize, 0, FlagNoZero | FlagNoPointers);
+ h->buckets = runtime·mallocgc(h->bucketsize, 0, FlagNoZero);
b = (Bucket*)(h->buckets);
clearbucket(b);
}
@@ -668,7 +665,7 @@ hash_insert(MapType *t, Hmap *h, void *key, void *value)
if(inserti == nil) {
// all current buckets are full, allocate a new one.
if(checkgc) mstats.next_gc = mstats.heap_alloc;
- newb = runtime·mallocgc(h->bucketsize, 0, FlagNoZero | FlagNoPointers);
+ newb = runtime·mallocgc(h->bucketsize, 0, FlagNoZero);
clearbucket(newb);
b->overflow = newb;
inserti = newb->tophash;
コアとなるコードの解説
上記の差分は、runtime/hashmap.c
内の複数の箇所で runtime·mallocgc
の呼び出しから FlagNoPointers
フラグが削除されていることを示しています。
hash_init
関数内: ハッシュマップが初期化される際に、最初のバケット配列が割り当てられます。元のコミットでは、ここでFlagNoPointers
が使用されていましたが、このコミットで削除されました。evacuate
関数内: マップのサイズ変更(成長)やバケットの再配置中に、新しいバケットが割り当てられることがあります。ここでもFlagNoPointers
が削除されました。hash_grow
関数内: マップが成長し、新しいより大きなバケット配列が必要になったときに呼び出されます。新しいバケット配列の割り当て時にFlagNoPointers
が削除されました。hash_insert
関数内: 新しい要素がマップに挿入される際に、必要に応じて新しいバケットが割り当てられることがあります。ここでもFlagNoPointers
が削除されました。
これらの変更は、ハッシュマップのバケット配列がGCによって通常通りスキャンされるように戻すことを意味します。これにより、イテレータが参照する古いバケット配列内のポインタもGCによって適切に追跡され、メモリの安全性が確保されます。
元のコミットのコメントにあった「Note: the array really does have pointers, but we tell the gc about them explicitly via gciter below. We use FlagNoPointers to prevent the gc from scanning the bucket array itself. Fixes issue 6119.」という記述は、FlagNoPointers
を使用しても、hash_gciter
というカスタムGCイテレータがポインタをGCに明示的に伝えることで、GCが正しく動作すると考えていたことを示しています。しかし、このコミットの取り消しは、このアプローチがイテレータが保持する古いバケット配列のケースで問題を引き起こしたことを明確に示しています。
このコミットは、GCの最適化は重要であるものの、メモリ安全性と正確性を犠牲にしてはならないというGoランタイム開発の原則を強調しています。
関連リンク
- 元のコミット (CL 12840043): https://golang.org/cl/12840043
- このコミット (CL 12884043): https://golang.org/cl/12884043
- Go Issue 6119: https://go.dev/issue/6119 (このコミットが取り消した元のコミットが修正しようとしたバグ)
参考にした情報源リンク
- Goのガベージコレクションに関する公式ドキュメントやブログ記事
- Goの
map
実装に関する技術文書やソースコード解析 runtime·mallocgc
およびFlagNoPointers
に関するGoランタイムのソースコードコメント- Goのイテレータの動作に関する情報
[インデックス 17204] ファイルの概要
このコミットは、Goランタイムにおけるハッシュマップのメモリ管理とガベージコレクション(GC)に関する以前の変更(CL 12840043 / 3b9f54db72a1)を取り消すものです。具体的には、ハッシュマップの内部構造(バケット配列)がGCによってスキャンされないようにする試みを元に戻し、その変更がビルドを破壊し、イテレータによって保持されている古いバケット配列がGCによって適切にスキャンされる必要があるという問題に対処しています。
コミット
commit 74e78df107f9e6642ecfc6808f542e2625f7cfb2
Author: Keith Randall <khr@golang.org>
Date: Tue Aug 13 12:59:39 2013 -0700
undo CL 12840043 / 3b9f54db72a1
Breaks the build. Old bucket arrays kept by iterators
still need to be scanned.
««« original CL description
runtime: tell GC not to scan internal hashmap structures.
We'll do it ourselves via hash_gciter, thanks.
Fixes bug 6119.
R=golang-dev, dvyukov, cookieo9, rsc
CC=golang-dev
https://golang.org/cl/12840043
»»»
R=golang-dev
CC=golang-dev
https://golang.org/cl/12884043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/74e78df107f9e6642ecfc6808f542e2625f7cfb2
元コミット内容
このコミットが取り消した元のコミット(CL 12840043 / 3b9f54db72a1)の目的は以下の通りです。
「runtime: tell GC not to scan internal hashmap structures. We'll do it ourselves via hash_gciter, thanks. Fixes bug 6119.」
これは、Goランタイムのハッシュマップ(map
型)の内部構造(特にバケット配列)について、Goのガベージコレクタ(GC)が直接スキャンしないように指示し、代わりにhash_gciter
というカスタムのGCイテレータを通じてこれらの構造をGCに認識させることで、バグ6119を修正しようとしたものです。この変更は、runtime·mallocgc
関数を呼び出す際にFlagNoPointers
フラグを追加することで実現されていました。
変更の背景
元のコミット(CL 12840043)は、GoのハッシュマップのGC処理を最適化し、特定のバグ(バグ6119)を修正することを目的としていました。バグ6119は、ハッシュマップのバケット配列がGCによって適切に処理されないことに関連する可能性があり、FlagNoPointers
フラグを使用してGCの挙動を変更することで、この問題を解決しようとしました。
しかし、このコミット(CL 12884043)のコミットメッセージによると、元の変更は「Breaks the build. Old bucket arrays kept by iterators still need to be scanned.」という問題を引き起こしました。これは、ハッシュマップのイテレータが古いバケット配列を参照し続けている場合、GCがこれらの古い配列をスキャンしないと、参照されているオブジェクトが誤って解放されてしまう可能性があることを示唆しています。つまり、FlagNoPointers
フラグによってGCがバケット配列をスキャンしなくなった結果、イテレータが保持する古いバケット配列内のポインタがGCによって追跡されなくなり、メモリ安全性や正確なGC処理が損なわれるという重大な問題が発生したため、元の変更を取り消す必要が生じました。
前提知識の解説
Goのガベージコレクション (GC)
Goのガベージコレクションは、プログラムが動的に確保したメモリのうち、もはや到達不可能(参照されていない)なオブジェクトを自動的に解放する仕組みです。GoのGCは並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGCが動作することで、アプリケーションの一時停止時間(ストップ・ザ・ワールド時間)を最小限に抑えることを目指しています。
GCは、プログラムが使用しているオブジェクトを「マーク」し、マークされなかったオブジェクトを「スイープ」(解放)します。このマークフェーズでは、GCはルート(グローバル変数、スタック上の変数など)から到達可能なすべてのオブジェクトをたどってマークします。この「たどる」プロセスにおいて、オブジェクトが他のオブジェクトへのポインタを含んでいる場合、GCはそのポインタをたどってさらにオブジェクトをマークします。
runtime·mallocgc
と FlagNoPointers
runtime·mallocgc
は、Goランタイム内部で使用されるメモリ割り当て関数です。これは、GCによって管理されるヒープメモリを割り当てる際に使用されます。この関数は、割り当てるメモリのサイズ、アラインメント、そしてGCに関するフラグを受け取ります。
FlagNoPointers
は、runtime·mallocgc
に渡されるフラグの一つで、割り当てられるメモリ領域にポインタが含まれていないことをGCに伝えるために使用されます。このフラグが設定されている場合、GCはこのメモリ領域をポインタの有無についてスキャンしません。これは、GCのオーバーヘッドを削減するために使用される最適化手法の一つです。例えば、純粋なデータ構造(整数や浮動小数点数のみを含む配列など)を割り当てる際にこのフラグを使用することで、GCがその領域を不必要にスキャンするのを避けることができます。
しかし、このフラグを誤って使用すると、GCが実際にポインタを含むメモリ領域をスキャンしなくなり、その結果、到達可能なオブジェクトが誤って解放されたり、メモリリークが発生したりする可能性があります。
Goのハッシュマップ (map
) の内部構造
Goのmap
型は、内部的にはハッシュテーブルとして実装されています。ハッシュテーブルは、キーと値のペアを格納するために「バケット」と呼ばれる配列を使用します。各バケットは、複数のキーと値のペアを保持できる小さな構造体です。ハッシュ衝突が発生した場合(異なるキーが同じバケットにハッシュされる場合)、Goのマップはオーバーフローバケットやチェイニングなどのメカニズムを使用して衝突を解決します。
マップが成長すると、より多くのバケットが必要になり、既存のバケット配列が拡張されたり、新しいバケット配列にデータが再ハッシュされたりすることがあります。このプロセス中に、古いバケット配列が一時的に存在し続けることがあります。
イテレータとGC
Goのマップはイテレータ(for range
ループなど)をサポートしています。イテレータは、マップの要素を順に処理するために使用されます。イテレータが動作している間、マップの内部構造(特にバケット配列)は変更されないように保護される必要があります。マップのサイズ変更(成長)が発生した場合、イテレータは古いバケット配列を参照し続けることがあります。
GCの観点から見ると、イテレータが参照している古いバケット配列は、まだ「到達可能」なメモリ領域と見なされるべきです。なぜなら、イテレータがその配列内のポインタを介してキーや値にアクセスする可能性があるからです。もしGCがこれらの古いバケット配列をスキャンしない場合、それらが参照しているキーや値のオブジェクトがGCによって誤って解放されてしまう可能性があります。
技術的詳細
このコミットは、src/pkg/runtime/hashmap.c
ファイルに対する変更を取り消しています。具体的には、runtime·mallocgc
関数の呼び出しからFlagNoPointers
フラグを削除しています。
元のコミットでは、ハッシュマップのバケット配列を割り当てる際に、GCがその配列をスキャンしないようにFlagNoPointers
フラグが追加されていました。これは、バケット配列自体はポインタの配列ですが、GCはhash_gciter
というカスタムイテレータを通じてこれらのポインタを明示的に処理するという意図がありました。このアプローチは、GCの効率を向上させ、バグ6119を修正することを目的としていました。
しかし、このコミットが示すように、この変更は「Breaks the build. Old bucket arrays kept by iterators still need to be scanned.」という問題を引き起こしました。これは、FlagNoPointers
が設定されたことで、GCがバケット配列をスキャンしなくなり、その結果、イテレータが保持している古いバケット配列内のポインタがGCによって追跡されなくなったことを意味します。イテレータがまだアクティブであるにもかかわらず、GCが古いバケット配列を到達不可能と判断し、その中のポインタが指すオブジェクトを解放してしまう可能性がありました。これは、メモリ破損やクラッシュにつながる重大なバグです。
このコミットは、この問題を解決するために、runtime·mallocgc
呼び出しからFlagNoPointers
フラグを削除し、GCがハッシュマップのバケット配列を通常通りスキャンするように戻しました。これにより、イテレータが参照する古いバケット配列もGCによって適切に処理され、メモリの安全性が確保されます。
コアとなるコードの変更箇所
src/pkg/runtime/hashmap.c
ファイルにおいて、runtime·mallocgc
関数の呼び出しから FlagNoPointers
フラグが削除されています。
具体的には、以下の行で変更が行われています。
--- a/src/pkg/runtime/hashmap.c
+++ b/src/pkg/runtime/hashmap.c
@@ -259,10 +259,7 @@ hash_init(MapType *t, Hmap *h, uint32 hint)
// done lazily later.
buckets = nil;
} else {
- buckets = runtime·mallocgc(bucketsize << B, 0, FlagNoZero | FlagNoPointers);
- // Note: the array really does have pointers, but we tell the gc about
- // them explicitly via gciter below. We use FlagNoPointers to prevent
- // the gc from scanning the bucket array itself. Fixes issue 6119.
+ buckets = runtime·mallocgc(bucketsize << B, 0, FlagNoZero);
for(i = 0; i < (uintptr)1 << B; i++) {
b = (Bucket*)(buckets + i * bucketsize);
clearbucket(b);
@@ -333,7 +330,7 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket)
if((hash & newbit) == 0) {
if(xi == BUCKETSIZE) {
if(checkgc) mstats.next_gc = mstats.heap_alloc;
- newx = runtime·mallocgc(h->bucketsize, 0, FlagNoZero | FlagNoPointers);
+ newx = runtime·mallocgc(h->bucketsize, 0, FlagNoZero);
clearbucket(newx);
x->overflow = newx;
x = newx;
@@ -358,7 +355,7 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket)
} else {
if(yi == BUCKETSIZE) {
if(checkgc) mstats.next_gc = mstats.heap_alloc;
- newy = runtime·mallocgc(h->bucketsize, 0, FlagNoZero | FlagNoPointers);
+ newy = runtime·mallocgc(h->bucketsize, 0, FlagNoZero);
clearbucket(newy);
y->overflow = newy;
y = newy;
@@ -454,7 +451,7 @@ hash_grow(MapType *t, Hmap *h)
old_buckets = h->buckets;
// NOTE: this could be a big malloc, but since we don't need zeroing it is probably fast.
if(checkgc) mstats.next_gc = mstats.heap_alloc;
- new_buckets = runtime·mallocgc((uintptr)h->bucketsize << (h->B + 1), 0, FlagNoZero | FlagNoPointers);
+ new_buckets = runtime·mallocgc((uintptr)h->bucketsize << (h->B + 1), 0, FlagNoZero);
flags = (h->flags & ~(Iterator | OldIterator));
if((h->flags & Iterator) != 0) {
flags |= OldIterator;
@@ -618,7 +615,7 @@ hash_insert(MapType *t, Hmap *h, void *key, void *value)
hash = h->hash0;
t->key->alg->hash(&hash, t->key->size, key);
if(h->buckets == nil) {
- h->buckets = runtime·mallocgc(h->bucketsize, 0, FlagNoZero | FlagNoPointers);
+ h->buckets = runtime·mallocgc(h->bucketsize, 0, FlagNoZero);
b = (Bucket*)(h->buckets);
clearbucket(b);
}
@@ -668,7 +665,7 @@ hash_insert(MapType *t, Hmap *h, void *key, void *value)
if(inserti == nil) {
// all current buckets are full, allocate a new one.
if(checkgc) mstats.next_gc = mstats.heap_alloc;
- newb = runtime·mallocgc(h->bucketsize, 0, FlagNoZero | FlagNoPointers);
+ newb = runtime·mallocgc(h->bucketsize, 0, FlagNoZero);
clearbucket(newb);
b->overflow = newb;
inserti = newb->tophash;
コアとなるコードの解説
上記の差分は、runtime/hashmap.c
内の複数の箇所で runtime·mallocgc
の呼び出しから FlagNoPointers
フラグが削除されていることを示しています。
hash_init
関数内: ハッシュマップが初期化される際に、最初のバケット配列が割り当てられます。元のコミットでは、ここでFlagNoPointers
が使用されていましたが、このコミットで削除されました。evacuate
関数内: マップのサイズ変更(成長)やバケットの再配置中に、新しいバケットが割り当てられることがあります。ここでもFlagNoPointers
が削除されました。hash_grow
関数内: マップが成長し、新しいより大きなバケット配列が必要になったときに呼び出されます。新しいバケット配列の割り当て時にFlagNoPointers
が削除されました。hash_insert
関数内: 新しい要素がマップに挿入される際に、必要に応じて新しいバケットが割り当てられることがあります。ここでもFlagNoPointers
が削除されました。
これらの変更は、ハッシュマップのバケット配列がGCによって通常通りスキャンされるように戻すことを意味します。これにより、イテレータが参照する古いバケット配列内のポインタもGCによって適切に追跡され、メモリの安全性が確保されます。
元のコミットのコメントにあった「Note: the array really does have pointers, but we tell the gc about them explicitly via gciter below. We use FlagNoPointers to prevent the gc from scanning the bucket array itself. Fixes issue 6119.」という記述は、FlagNoPointers
を使用しても、hash_gciter
というカスタムGCイテレータがポインタをGCに明示的に伝えることで、GCが正しく動作すると考えていたことを示しています。しかし、このコミットの取り消しは、このアプローチがイテレータが保持する古いバケット配列のケースで問題を引き起こしたことを明確に示しています。
このコミットは、GCの最適化は重要であるものの、メモリ安全性と正確性を犠牲にしてはならないというGoランタイム開発の原則を強調しています。
関連リンク
- 元のコミット (CL 12840043): https://golang.org/cl/12840043
- このコミット (CL 12884043): https://golang.org/cl/12884043
- Go Issue 6119: https://go.dev/issue/6119 (このコミットが取り消した元のコミットが修正しようとしたバグ)
参考にした情報源リンク
- Goのガベージコレクションに関する公式ドキュメントやブログ記事
- Goの
map
実装に関する技術文書やソースコード解析 runtime·mallocgc
およびFlagNoPointers
に関するGoランタイムのソースコードコメント- Goのイテレータの動作に関する情報
FlagNoPointers
に関するWeb検索結果:FlagNoPointers
はGoランタイム内部のフラグであり、特定の型がポインタを含まないことをGCに伝えるために使用されます。これによりGCはスキャンを最適化できますが、Go開発者が直接操作するものではありません。