[インデックス 17381] ファイルの概要
このコミットは、Goランタイムにおけるガベージコレクション(GC)の内部フラグ FlagNoPointers
の名称を FlagNoScan
に変更するものです。この変更は、フラグの実際の用途をより正確に表現することを目的としています。特に、ポインタを実際に含むがGCがスキャンする必要のないオブジェクトに対して、このフラグがどのように機能するかを明確にします。
コミット
commit d0dd420a245763494ec564d8163724a2a6d374f4
Author: Keith Randall <khr@golang.org>
Date: Fri Aug 23 17:28:47 2013 -0700
runtime: rename FlagNoPointers to FlagNoScan. Better represents
the use of the flag, especially for objects which actually do have
pointers but we don't want the GC to scan them.
R=golang-dev, cshapiro
CC=golang-dev
https://golang.org/cl/13181045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d0dd420a245763494ec564d8163724a2a6d374f4
元コミット内容
runtime: rename FlagNoPointers to FlagNoScan. Better represents
the use of the flag, especially for objects which actually do have
pointers but we don't want the GC to scan them.
変更の背景
Goのガベージコレクタは、ヒープ上のオブジェクトをスキャンして、到達可能なオブジェクトを特定し、到達不能なオブジェクトを解放します。このスキャンプロセスにおいて、特定のオブジェクトがポインタを含まないことが既知である場合、GCはそのオブジェクトをスキャンする必要がありません。これにより、GCの効率が向上します。
以前は FlagNoPointers
というフラグがこの目的で使用されていました。しかし、この名称は誤解を招く可能性がありました。なぜなら、GCがスキャンしないオブジェクトの中には、実際にはポインタを含むものも存在し得るからです。例えば、内部的にポインタを持つが、それらのポインタがGCの対象とならない(例えば、C言語のコードが管理するメモリへのポインタなど)場合、FlagNoPointers
という名前は適切ではありませんでした。
このコミットは、フラグの意図をより正確に反映させるために、FlagNoPointers
を FlagNoScan
に改名しました。FlagNoScan
は、「GCがこのオブジェクトをスキャンする必要がない」という事実を直接的に示しており、ポインタの有無に関わらず、GCの動作を制御するフラグとしての役割を明確にしています。これにより、コードの可読性と意図の明確化が図られています。
前提知識の解説
Goのガベージコレクション (GC)
Goのガベージコレクションは、主に並行マーク&スイープ方式を採用しています。これは、プログラムの実行と並行してGCが動作し、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えることを目指しています。
GCの主要なフェーズは以下の通りです。
-
マークフェーズ (Mark Phase):
- GCは、ルート(グローバル変数、スタック上の変数など)から到達可能なすべてのオブジェクトを特定します。
- 到達可能なオブジェクトには「マーク」が付けられます。
- このフェーズでは、オブジェクトがポインタを含むかどうか、そしてそのポインタが他のオブジェクトを参照しているかどうかを判断するために、オブジェクトのメモリを「スキャン」する必要があります。
-
スイープフェーズ (Sweep Phase):
- マークされなかったオブジェクト(到達不能なオブジェクト)は、メモリから解放されます。
GCにおける型情報とスキャン
Goのランタイムは、各オブジェクトの型情報を保持しています。この型情報には、そのオブジェクトがポインタを含むかどうか、そしてポインタがどのオフセットに存在するかといったメタデータが含まれています。GCは、この型情報に基づいて、オブジェクトを効率的にスキャンします。
FlagNoPointers
(そして新しい FlagNoScan
) のようなフラグは、この型情報の一部として機能します。特定のオブジェクトがこのフラグを持つ場合、GCはそのオブジェクトのメモリ領域をポインタのためにスキャンする必要がないと判断します。これは、GCのマークフェーズにおいて、不要なスキャンをスキップすることでパフォーマンスを向上させるための重要な最適化です。
例えば、純粋なバイト配列や整数のみを含む構造体など、ポインタを一切含まないオブジェクトは FlagNoScan
とマークされます。これにより、GCはこれらのオブジェクトを高速に処理できます。また、前述の通り、ポインタを含むがGCがスキャンすべきではない特定のケースでもこのフラグが使用されます。
mallocgc
関数
runtime·mallocgc
は、Goランタイム内でメモリを割り当てるための主要な関数です。この関数は、割り当てるメモリのサイズ、型情報、そしてGCに関連するフラグ(FlagNoGC
、FlagNoProfiling
、そしてこのコミットで変更された FlagNoPointers
/FlagNoScan
など)を受け取ります。これらのフラグは、割り当てられたメモリがGCによってどのように扱われるべきかをランタイムに指示します。
技術的詳細
このコミットの技術的な詳細は、主にGoランタイムのメモリ管理とガベージコレクションの内部実装におけるフラグのセマンティクス変更にあります。
FlagNoPointers
から FlagNoScan
への名称変更は、単なるリネーム以上の意味を持ちます。これは、GCがオブジェクトを処理する際の「スキャン」という概念をより強調しています。
FlagNoPointers
の限界: 従来のFlagNoPointers
は、「このオブジェクトにはポインタがない」ということを示唆していました。しかし、Goのランタイムには、C言語のコードとの連携や、特定の内部データ構造において、GoのGCが追跡すべきではないポインタが存在する場合があります。このような場合でも、オブジェクト自体はポインタを含んでいるため、FlagNoPointers
という名前は直感的ではありませんでした。FlagNoScan
の導入:FlagNoScan
は、「GCはこのオブジェクトをスキャンする必要がない」という、より広範で正確な意味合いを持ちます。これにより、GCは、ポインタの有無にかかわらず、そのオブジェクトのメモリ領域を走査して他のオブジェクトへの参照を探す必要がないと判断できます。これは、GCのマークフェーズにおける重要な最適化であり、GCのオーバーヘッドを削減します。- GCビットマップとの関連: GoのGCは、ヒープメモリの各ワードがポインタであるか、またはオブジェクトの開始点であるかを示すビットマップを使用します。このビットマップには、
bitAllocated
、bitMarked
、bitSpecial
といったビットが含まれます。このコミットでは、bitNoPointers
もbitNoScan
に変更されており、GCがオブジェクトをスキャンすべきかどうかを判断するための内部的なメカニズムが、新しい命名規則に沿って更新されています。
この変更は、GoのGCがより堅牢で、かつ意図が明確な形で動作するための基盤を強化するものです。特に、GCの内部動作を理解する上で、フラグの名称がその役割を正確に反映していることは、開発者にとって非常に重要です。
コアとなるコードの変更箇所
このコミットでは、以下のファイルで FlagNoPointers
が FlagNoScan
に、bitNoPointers
が bitNoScan
に変更されています。
src/pkg/runtime/malloc.goc
src/pkg/runtime/malloc.h
src/pkg/runtime/mfinal.c
src/pkg/runtime/mgc0.c
src/pkg/runtime/string.goc
具体的な変更は、主に FlagNoPointers
の定義と、runtime·mallocgc
の呼び出し箇所、およびGCビットマップ関連の定数定義です。
コアとなるコードの解説
src/pkg/runtime/malloc.goc
このファイルでは、runtime·mallocgc
関数の呼び出し箇所で FlagNoPointers
が FlagNoScan
に変更されています。
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -98,12 +98,14 @@ runtime·mallocgc(uintptr size, uintptr typ, uint32 flag)
}
if(!(flag & FlagNoGC))
- runtime·markallocated(v, size, (flag&FlagNoPointers) != 0);
+ runtime·markallocated(v, size, (flag&FlagNoScan) != 0);
if(DebugTypeAtBlockEnd)
*(uintptr*)((uintptr)v+size-sizeof(uintptr)) = typ;
- if(UseSpanType && !(flag & FlagNoPointers) && typ != 0) {
+ // TODO: save type even if FlagNoScan? Potentially expensive but might help
+ // heap profiling/tracing.
+ if(UseSpanType && !(flag & FlagNoScan) && typ != 0) {
uintptr *buf, i;
buf = m->settype_buf;
@@ -114,7 +116,7 @@ runtime·mallocgc(uintptr size, uintptr typ, uint32 flag)
}
m->mallocing = 0;
- if(UseSpanType && !(flag & FlagNoPointers) && typ != 0 && m->settype_bufsize == nelem(m->settype_buf))
+ if(UseSpanType && !(flag & FlagNoScan) && typ != 0 && m->settype_bufsize == nelem(m->settype_buf))
runtime·settype_flush(m);
m->locks--;
if(m->locks == 0 && g->preempt) // restore the preemption request in case we've cleared it in newstack
@@ -601,7 +603,7 @@ runtime·settype_flush(M *mp)
case MTypes_Empty:
ntypes = (s->npages << PageShift) / size;
nbytes3 = 8*sizeof(uintptr) + 1*ntypes;
-\t\t\tdata3 = runtime·mallocgc(nbytes3, 0, FlagNoProfiling|FlagNoPointers|FlagNoInvokeGC);\n+\t\t\tdata3 = runtime·mallocgc(nbytes3, 0, FlagNoProfiling|FlagNoScan|FlagNoInvokeGC);\n s->types.compression = MTypes_Bytes;
s->types.data = (uintptr)data3;
((uintptr*)data3)[1] = typ;
@@ -628,7 +630,7 @@ runtime·settype_flush(M *mp)
} else {
ntypes = (s->npages << PageShift) / size;
nbytes2 = ntypes * sizeof(uintptr);
-\t\t\t\tdata2 = runtime·mallocgc(nbytes2, 0, FlagNoProfiling|FlagNoPointers|FlagNoInvokeGC);\n+\t\t\t\tdata2 = runtime·mallocgc(nbytes2, 0, FlagNoProfiling|FlagNoScan|FlagNoInvokeGC);\n s->types.compression = MTypes_Words;
s->types.data = (uintptr)data2;
@@ -699,7 +701,7 @@ runtime·mal(uintptr n)
void
runtime·new(Type *typ, uint8 *ret)
{
-\tret = runtime·mallocgc(typ->size, (uintptr)typ | TypeInfo_SingleObject, typ->kind&KindNoPointers ? FlagNoPointers : 0);\n+\tret = runtime·mallocgc(typ->size, (uintptr)typ | TypeInfo_SingleObject, typ->kind&KindNoPointers ? FlagNoScan : 0);\n FLUSH(&ret);
}
@@ -710,7 +712,7 @@ cnew(Type *typ, intgo n, int32 objtyp)
\truntime·throw("runtime: invalid objtyp");
if(n < 0 || (typ->size > 0 && n > MaxMem/typ->size))
\truntime·panicstring("runtime: allocation size out of range");
-\treturn runtime·mallocgc(typ->size*n, (uintptr)typ | objtyp, typ->kind&KindNoPointers ? FlagNoPointers : 0);\n+\treturn runtime·mallocgc(typ->size*n, (uintptr)typ | objtyp, typ->kind&KindNoPointers ? FlagNoScan : 0);\n }
// same as runtime·new, but callable from C
runtime·markallocated
の呼び出しや、UseSpanType
の条件分岐、runtime·settype_flush
内での mallocgc
呼び出し、そして runtime·new
や cnew
といったメモリ割り当て関数の内部で、FlagNoPointers
が FlagNoScan
に置き換えられています。これは、メモリ割り当て時にオブジェクトがGCによってスキャンされるべきかどうかを示すフラグが、新しい名称で渡されるようになったことを意味します。
src/pkg/runtime/malloc.h
このヘッダーファイルでは、FlagNoPointers
の定義が FlagNoScan
に変更されています。
--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -467,7 +467,7 @@ uintptr runtime·gettype(void*);
enum
{
// flags to malloc
-\tFlagNoPointers\t= 1<<0,\t// no pointers here\n+\tFlagNoScan\t= 1<<0,\t// GC doesn't have to scan object\n FlagNoProfiling\t= 1<<1,\t// must not profile
FlagNoGC\t= 1<<2,\t// must not free or scan for pointers
FlagNoZero\t= 1<<3, // don't zero memory
FlagNoPointers
が FlagNoScan
に変更され、コメントも「GC doesn't have to scan object」(GCがオブジェクトをスキャンする必要がない)と更新されています。これにより、フラグの意図がより明確になりました。
src/pkg/runtime/mfinal.c
ファイナライザ関連のコードでも FlagNoPointers
が FlagNoScan
に変更されています。
--- a/src/pkg/runtime/mfinal.c
+++ b/src/pkg/runtime/mfinal.c
@@ -123,7 +123,7 @@ resizefintab(Fintab *tab)
newtab.max *= 3;
}
-\tnewtab.key = runtime·mallocgc(newtab.max*sizeof newtab.key[0], 0, FlagNoInvokeGC|FlagNoPointers);\n+\tnewtab.key = runtime·mallocgc(newtab.max*sizeof newtab.key[0], 0, FlagNoInvokeGC|FlagNoScan);\n newtab.val = runtime·mallocgc(newtab.max*sizeof newtab.val[0], 0, FlagNoInvokeGC);
for(i=0; i<tab->max; i++) {
resizefintab
関数内で、ファイナライザテーブルのキーを格納するためのメモリ割り当て時に FlagNoPointers
が FlagNoScan
に変更されています。これは、これらの内部データ構造がGCによってスキャンされる必要がないことを示しています。
src/pkg/runtime/mgc0.c
このファイルはGCのコアロジックを含んでおり、GCビットマップの定義と使用箇所で変更が行われています。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -51,7 +51,7 @@ enum {
// The bits in the word are packed together by type first, then by
// heap location, so each 64-bit bitmap word consists of, from top to bottom,
// the 16 bitSpecial bits for the corresponding heap words, then the 16 bitMarked bits,
-// then the 16 bitNoPointers/bitBlockBoundary bits, then the 16 bitAllocated bits.
+// then the 16 bitNoScan/bitBlockBoundary bits, then the 16 bitAllocated bits.
// This layout makes it easier to iterate over the bits of a given type.
//
// The bitmap starts at mheap.arena_start and extends *backward* from
@@ -68,7 +68,7 @@ enum {
// /* then test bits & bitAllocated, bits & bitMarked, etc. */
//
#define bitAllocated ((uintptr)1<<(bitShift*0))
-#define bitNoPointers ((uintptr)1<<(bitShift*1)) /* when bitAllocated is set */
+#define bitNoScan ((uintptr)1<<(bitShift*1)) /* when bitAllocated is set */
#define bitMarked ((uintptr)1<<(bitShift*2)) /* when bitAllocated is set */
#define bitSpecial ((uintptr)1<<(bitShift*3)) /* when bitAllocated is set - has finalizer or being profiled */
#define bitBlockBoundary ((uintptr)1<<(bitShift*1)) /* when bitAllocated is NOT set */
@@ -454,7 +454,7 @@ flushptrbuf(PtrTarget *ptrbuf, PtrTarget **ptrbufpos, Obj **_wp, Workbuf **_wbuf
}
// If object has no pointers, don't need to scan further.
-\t\t\tif((bits & bitNoPointers) != 0)\n+\t\t\tif((bits & bitNoScan) != 0)\n continue;
// Ask span about size class.
@@ -1198,7 +1198,7 @@ debug_scanblock(byte *b, uintptr n)
runtime·printf("found unmarked block %p in %p\n", obj, vp+i);
// If object has no pointers, don't need to scan further.
-\t\tif((bits & bitNoPointers) != 0)\n+\t\tif((bits & bitNoScan) != 0)\n continue;
debug_scanblock(obj, size);
@@ -2345,9 +2345,9 @@ runfinq(void)
runtime·free(frame);
// The frame does not contain pointers interesting for GC,
// all not yet finalized objects are stored in finc.
-\t\t\t\t\t// If we do not mark it as FlagNoPointers,\n+\t\t\t\t\t// If we do not mark it as FlagNoScan,\n // the last finalized object is not collected.
-\t\t\t\t\tframe = runtime·mallocgc(framesz, 0, FlagNoPointers|FlagNoInvokeGC);\n+\t\t\t\t\tframe = runtime·mallocgc(framesz, 0, FlagNoScan|FlagNoInvokeGC);\n framecap = framesz;
}
if(f->fint == nil)
@@ -2381,9 +2381,9 @@ runfinq(void)
}
// mark the block at v of size n as allocated.
-// If noptr is true, mark it as having no pointers.
+// If noscan is true, mark it as not needing scanning.
void
-runtime·markallocated(void *v, uintptr n, bool noptr)\n+runtime·markallocated(void *v, uintptr n, bool noscan)\n {
uintptr *b, obits, bits, off, shift;
for(;;) {
\tobits = *b;
\tbits = (obits & ~(bitMask<<shift)) | (bitAllocated<<shift);
-\t\tif(noptr)\n-\t\t\tbits |= bitNoPointers<<shift;\n+\t\tif(noscan)\n+\t\t\tbits |= bitNoScan<<shift;\n \tif(runtime·gomaxprocs == 1) {
\t\t*b = bits;
\t\tbreak;
bitNoPointers
マクロが bitNoScan
に変更され、関連するコメントも更新されています。また、flushptrbuf
や debug_scanblock
といったGCの内部関数で、オブジェクトがスキャン不要であるかを判断する際に bitNoPointers
の代わりに bitNoScan
が使用されるようになっています。runtime·markallocated
関数の引数名も noptr
から noscan
に変更され、内部でのビット設定も bitNoPointers
から bitNoScan
に変わっています。
src/pkg/runtime/string.goc
文字列関連のメモリ割り当てでも FlagNoPointers
が FlagNoScan
に変更されています。
--- a/src/pkg/runtime/string.goc
+++ b/src/pkg/runtime/string.goc
@@ -47,7 +47,7 @@ gostringsize(intgo l)
if(l == 0)
return runtime·emptystring;
// leave room for NUL for C runtime (e.g., callers of getenv)
-\ts.str = runtime·mallocgc(l+1, 0, FlagNoPointers|FlagNoZero);\n+\ts.str = runtime·mallocgc(l+1, 0, FlagNoScan|FlagNoZero);\n s.len = l;
s.str[l] = 0;
for(;;) {
@@ -85,7 +85,7 @@ runtime·gobytes(byte *p, intgo n)
{
Slice sl;
-\tsl.array = runtime·mallocgc(n, 0, FlagNoPointers|FlagNoZero);\n+\tsl.array = runtime·mallocgc(n, 0, FlagNoScan|FlagNoZero);\n sl.len = n;
sl.cap = n;
runtime·memmove(sl.array, p, n);
@@ -252,7 +252,7 @@ func slicebytetostring(b Slice) (s String) {
}
func stringtoslicebyte(s String) (b Slice) {
-\tb.array = runtime·mallocgc(s.len, 0, FlagNoPointers|FlagNoZero);\n+\tb.array = runtime·mallocgc(s.len, 0, FlagNoScan|FlagNoZero);\n b.len = s.len;
b.cap = s.len;
runtime·memmove(b.array, s.str, s.len);
@@ -301,7 +301,7 @@ func stringtoslicerune(s String) (b Slice) {
tn++;
}
-\tb.array = runtime·mallocgc(n*sizeof(r[0]), 0, FlagNoPointers|FlagNoZero);\n+\tb.array = runtime·mallocgc(n*sizeof(r[0]), 0, FlagNoScan|FlagNoZero);\n b.len = n;
b.cap = n;
p = s.str;
gostringsize
, runtime·gobytes
, stringtoslicebyte
, stringtoslicerune
といった文字列やスライスを扱う関数内で、メモリ割り当て時に FlagNoPointers
が FlagNoScan
に変更されています。これは、これらのデータ型が通常、GCがスキャンすべきポインタを含まないため、GCの効率化のためにこのフラグが設定されることを示しています。
これらの変更は、Goランタイム全体で FlagNoPointers
の概念が FlagNoScan
に統一され、GCの動作に関する意図がより明確になったことを示しています。