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

[インデックス 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 という名前は適切ではありませんでした。

このコミットは、フラグの意図をより正確に反映させるために、FlagNoPointersFlagNoScan に改名しました。FlagNoScan は、「GCがこのオブジェクトをスキャンする必要がない」という事実を直接的に示しており、ポインタの有無に関わらず、GCの動作を制御するフラグとしての役割を明確にしています。これにより、コードの可読性と意図の明確化が図られています。

前提知識の解説

Goのガベージコレクション (GC)

Goのガベージコレクションは、主に並行マーク&スイープ方式を採用しています。これは、プログラムの実行と並行してGCが動作し、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えることを目指しています。

GCの主要なフェーズは以下の通りです。

  1. マークフェーズ (Mark Phase):

    • GCは、ルート(グローバル変数、スタック上の変数など)から到達可能なすべてのオブジェクトを特定します。
    • 到達可能なオブジェクトには「マーク」が付けられます。
    • このフェーズでは、オブジェクトがポインタを含むかどうか、そしてそのポインタが他のオブジェクトを参照しているかどうかを判断するために、オブジェクトのメモリを「スキャン」する必要があります。
  2. スイープフェーズ (Sweep Phase):

    • マークされなかったオブジェクト(到達不能なオブジェクト)は、メモリから解放されます。

GCにおける型情報とスキャン

Goのランタイムは、各オブジェクトの型情報を保持しています。この型情報には、そのオブジェクトがポインタを含むかどうか、そしてポインタがどのオフセットに存在するかといったメタデータが含まれています。GCは、この型情報に基づいて、オブジェクトを効率的にスキャンします。

FlagNoPointers (そして新しい FlagNoScan) のようなフラグは、この型情報の一部として機能します。特定のオブジェクトがこのフラグを持つ場合、GCはそのオブジェクトのメモリ領域をポインタのためにスキャンする必要がないと判断します。これは、GCのマークフェーズにおいて、不要なスキャンをスキップすることでパフォーマンスを向上させるための重要な最適化です。

例えば、純粋なバイト配列や整数のみを含む構造体など、ポインタを一切含まないオブジェクトは FlagNoScan とマークされます。これにより、GCはこれらのオブジェクトを高速に処理できます。また、前述の通り、ポインタを含むがGCがスキャンすべきではない特定のケースでもこのフラグが使用されます。

mallocgc 関数

runtime·mallocgc は、Goランタイム内でメモリを割り当てるための主要な関数です。この関数は、割り当てるメモリのサイズ、型情報、そしてGCに関連するフラグ(FlagNoGCFlagNoProfiling、そしてこのコミットで変更された FlagNoPointers/FlagNoScan など)を受け取ります。これらのフラグは、割り当てられたメモリがGCによってどのように扱われるべきかをランタイムに指示します。

技術的詳細

このコミットの技術的な詳細は、主にGoランタイムのメモリ管理とガベージコレクションの内部実装におけるフラグのセマンティクス変更にあります。

FlagNoPointers から FlagNoScan への名称変更は、単なるリネーム以上の意味を持ちます。これは、GCがオブジェクトを処理する際の「スキャン」という概念をより強調しています。

  • FlagNoPointers の限界: 従来の FlagNoPointers は、「このオブジェクトにはポインタがない」ということを示唆していました。しかし、Goのランタイムには、C言語のコードとの連携や、特定の内部データ構造において、GoのGCが追跡すべきではないポインタが存在する場合があります。このような場合でも、オブジェクト自体はポインタを含んでいるため、FlagNoPointers という名前は直感的ではありませんでした。
  • FlagNoScan の導入: FlagNoScan は、「GCはこのオブジェクトをスキャンする必要がない」という、より広範で正確な意味合いを持ちます。これにより、GCは、ポインタの有無にかかわらず、そのオブジェクトのメモリ領域を走査して他のオブジェクトへの参照を探す必要がないと判断できます。これは、GCのマークフェーズにおける重要な最適化であり、GCのオーバーヘッドを削減します。
  • GCビットマップとの関連: GoのGCは、ヒープメモリの各ワードがポインタであるか、またはオブジェクトの開始点であるかを示すビットマップを使用します。このビットマップには、bitAllocatedbitMarkedbitSpecial といったビットが含まれます。このコミットでは、bitNoPointersbitNoScan に変更されており、GCがオブジェクトをスキャンすべきかどうかを判断するための内部的なメカニズムが、新しい命名規則に沿って更新されています。

この変更は、GoのGCがより堅牢で、かつ意図が明確な形で動作するための基盤を強化するものです。特に、GCの内部動作を理解する上で、フラグの名称がその役割を正確に反映していることは、開発者にとって非常に重要です。

コアとなるコードの変更箇所

このコミットでは、以下のファイルで FlagNoPointersFlagNoScan に、bitNoPointersbitNoScan に変更されています。

  • 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 関数の呼び出し箇所で FlagNoPointersFlagNoScan に変更されています。

--- 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·newcnew といったメモリ割り当て関数の内部で、FlagNoPointersFlagNoScan に置き換えられています。これは、メモリ割り当て時にオブジェクトが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

FlagNoPointersFlagNoScan に変更され、コメントも「GC doesn't have to scan object」(GCがオブジェクトをスキャンする必要がない)と更新されています。これにより、フラグの意図がより明確になりました。

src/pkg/runtime/mfinal.c

ファイナライザ関連のコードでも FlagNoPointersFlagNoScan に変更されています。

--- 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 関数内で、ファイナライザテーブルのキーを格納するためのメモリ割り当て時に FlagNoPointersFlagNoScan に変更されています。これは、これらの内部データ構造が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 に変更され、関連するコメントも更新されています。また、flushptrbufdebug_scanblock といったGCの内部関数で、オブジェクトがスキャン不要であるかを判断する際に bitNoPointers の代わりに bitNoScan が使用されるようになっています。runtime·markallocated 関数の引数名も noptr から noscan に変更され、内部でのビット設定も bitNoPointers から bitNoScan に変わっています。

src/pkg/runtime/string.goc

文字列関連のメモリ割り当てでも FlagNoPointersFlagNoScan に変更されています。

--- 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 といった文字列やスライスを扱う関数内で、メモリ割り当て時に FlagNoPointersFlagNoScan に変更されています。これは、これらのデータ型が通常、GCがスキャンすべきポインタを含まないため、GCの効率化のためにこのフラグが設定されることを示しています。

これらの変更は、Goランタイム全体で FlagNoPointers の概念が FlagNoScan に統一され、GCの動作に関する意図がより明確になったことを示しています。

関連リンク

参考にした情報源リンク