[インデックス 18077] ファイルの概要
このコミットは、Goランタイムのガベージコレクション(GC)におけるメモリ管理の最適化に関するものです。具体的には、フリーリスト(解放されたが再利用可能なメモリブロックのリスト)内のオブジェクトの扱いを変更し、GC中にこれらのオブジェクトを「割り当て済みかつスキャン不要」としてマークするようにしました。これにより、ポインタを持たないオブジェクトをmalloc
する際のビット変更のオーバーヘッドを削減し、全体的なパフォーマンス向上を目指しています。
コミット
commit 2d20c0d6257f5dfc57bb8a78f4e2824803bd6d0e
Author: Keith Randall <khr@golang.org>
Date: Wed Dec 18 17:13:59 2013 -0800
runtime: mark objects in free lists as allocated and unscannable.
On the plus side, we don't need to change the bits when mallocing
pointerless objects. On the other hand, we need to mark objects in the
free lists during GC. But the free lists are small at GC time, so it
should be a net win.
benchmark old ns/op new ns/op delta
BenchmarkMalloc8 40 33 -17.65%
BenchmarkMalloc16 45 38 -15.72%
BenchmarkMallocTypeInfo8 58 59 +0.85%
BenchmarkMallocTypeInfo16 63 64 +1.10%
R=golang-dev, rsc, dvyukov
CC=cshapiro, golang-dev
https://golang.org/cl/41040043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2d20c0d6257f5dfc57bb8a78f4e2824803bd6d0e
元コミット内容
runtime: mark objects in free lists as allocated and unscannable.
On the plus side, we don't need to change the bits when mallocing
pointerless objects. On the other hand, we need to mark objects in the
free lists during GC. But the free lists are small at GC time, so it
should be a net win.
benchmark old ns/op new ns/op delta
BenchmarkMalloc8 40 33 -17.65%
BenchmarkMalloc16 45 38 -15.72%
BenchmarkMallocTypeInfo8 58 59 +0.85%
BenchmarkMallocTypeInfo16 63 64 +1.10%
R=golang-dev, rsc, dvyukov
CC=cshapiro, golang-dev
https://golang.org/cl/41040043
変更の背景
Goのランタイムは、効率的なメモリ管理とガベージコレクション(GC)を実現するために、ヒープ上のオブジェクトの状態を管理するためのビットマップを使用しています。このビットマップは、各メモリワードが割り当てられているか、ポインタを含むか、マークされているかなどの情報を示します。
以前のGoランタイムでは、オブジェクトが解放されてフリーリストに戻されると、そのメモリ領域に対応するビットマップのビットが「解放済み」の状態を示すように変更されていました。しかし、その後、同じメモリ領域がポインタを持たない(スキャン不要な)オブジェクトのために再利用される場合、再びビットマップのビットを「割り当て済みかつスキャン不要」に変更する必要がありました。このビットの変更操作は、特に頻繁に小さなオブジェクトが割り当て・解放される場合に、オーバーヘッドとなっていました。
このコミットの背景にあるのは、このオーバーヘッドを削減することです。フリーリストにあるオブジェクトは、実際にはまだGCの対象ではない(再利用される可能性がある)ため、それらを「割り当て済みかつスキャン不要」として扱うことで、malloc
時のビット変更を不要にすることができます。その代わり、GCのマークフェーズ中にフリーリスト内のオブジェクトを明示的にマークする必要が生じますが、コミットメッセージにあるように、GC時のフリーリストは通常小さいため、この追加のオーバーヘッドはmalloc
時の削減分を上回り、全体としてパフォーマンスが向上すると見込まれました。
ベンチマーク結果は、特に小さなオブジェクトのmalloc
において顕著な改善(BenchmarkMalloc8
とBenchmarkMalloc16
で約15-17%の高速化)を示しており、この最適化が成功したことを裏付けています。ポインタ情報を持つオブジェクトのmalloc
(BenchmarkMallocTypeInfo
)ではわずかな性能低下が見られますが、これはポインタを持たないオブジェクトのmalloc
の改善によって相殺されると判断されたと考えられます。
前提知識の解説
このコミットを理解するためには、Goランタイムのメモリ管理とガベージコレクションに関する以下の概念を理解しておく必要があります。
- ヒープとアリーナ: Goのプログラムが動的に確保するメモリは「ヒープ」と呼ばれます。ヒープはさらに「アリーナ」と呼ばれる大きな連続したメモリ領域に分割されます。
- MSpan: ヒープは
MSpan
と呼ばれる固定サイズのページ(通常は8KB)の連続したブロックに分割されます。MSpan
は、そのページがどのサイズのオブジェクトを格納しているか、フリーリストの状態などを管理します。 - MCache: 各P(プロセッサ、Goスケジューラにおける論理CPU)は、ローカルなメモリキャッシュである
MCache
を持っています。MCache
は、小さなオブジェクトの割り当てを高速化するために、様々なサイズのオブジェクトに対応するフリーリストを保持しています。これにより、グローバルなヒープロックを避けて、並行性を高めます。 - フリーリスト: 解放されたがまだ再利用されていないメモリブロックのリストです。
MCache
やMSpan
が管理しています。 - ヒープビットマップ: Goランタイムは、ヒープ上の各メモリワードの状態を追跡するためにビットマップを使用します。このビットマップは、GCがオブジェクトをスキャンし、マークするために不可欠です。各メモリワードに対応するビットは、以下のような意味を持ちます(コミット当時の定義に近いもの):
bitAllocated
: そのメモリワードがオブジェクトの先頭であり、割り当て済みであることを示します。bitNoScan
(変更前) /bitScan
(変更後):bitAllocated
が設定されている場合、そのオブジェクトがポインタを含まずスキャン不要であるか(bitNoScan
)、またはポインタを含みスキャンが必要であるか(bitScan
が設定されていない場合)を示します。このコミットでbitNoScan
からbitScan
に名称が変更され、意味が反転しています(bitScan
が設定されていればスキャンが必要)。bitMarked
: GCのマークフェーズ中に、そのオブジェクトが到達可能(生きている)としてマークされたことを示します。bitSpecial
: そのオブジェクトがファイナライザを持つか、プロファイリングされているかなど、特別な処理が必要であることを示します。bitBlockBoundary
:bitAllocated
が設定されていない場合、そのメモリワードがブロックの境界であることを示します。これは、FlagNoGC
(GCの対象外)のオブジェクトや、フリーリスト内のオブジェクトのマークにも使用されます。
- ガベージコレクションのフェーズ: GoのGCは主に以下のフェーズで構成されます。
- マークフェーズ: ルート(スタック、グローバル変数など)から到達可能なすべてのオブジェクトを特定し、
bitMarked
ビットを設定します。 - スイープフェーズ: マークされていない(到達不可能な)オブジェクトを解放し、そのメモリをフリーリストに戻します。
- マークフェーズ: ルート(スタック、グローバル変数など)から到達可能なすべてのオブジェクトを特定し、
このコミットは、特にヒープビットマップのbitNoScan
/bitScan
とbitBlockBoundary
の利用方法、およびフリーリスト内のオブジェクトのGCマークフェーズでの扱い方に焦点を当てています。
技術的詳細
このコミットの主要な変更点は以下の通りです。
-
bitNoScan
からbitScan
への変更と意味の反転:- 以前は
bitNoScan
というビットがあり、これが設定されていると「スキャン不要」を意味していました。 - このコミットでは
bitNoScan
がbitScan
に名称変更され、意味が反転しました。bitScan
が設定されていると「スキャンが必要」を意味します。これにより、ポインタを持つオブジェクトを明示的にマークする形になります。 src/pkg/runtime/mgc0.c
のビットマップ定義で、#define bitNoScan
が#define bitScan
に変更されています。flushptrbuf
やdebug_scanblock
などの関数で、if((bits & bitNoScan) != 0)
がif((bits & bitScan) == 0)
に変更され、スキャン要否の判定ロジックが反転しています。
- 以前は
-
runtime·markallocated
関数の分割と新しいマーク関数:- 以前は
runtime·markallocated(void *v, uintptr n, bool noscan)
という単一の関数で、オブジェクトの割り当てとスキャン要否のマークを行っていました。 - このコミットでは、この関数が
runtime·markscan(void *v)
とruntime·marknogc(void *v)
の2つの関数に分割されました。runtime·markscan(void *v)
: スキャンが必要な(ポインタを含む)オブジェクトをマークします。bitScan
ビットを設定します。runtime·marknogc(void *v)
: GCの対象外(FlagNoGC
)のオブジェクトをマークします。これはbitBlockBoundary
ビットを設定することで行われます。
src/pkg/runtime/malloc.goc
のruntime·mallocgc
内で、runtime·markallocated
の呼び出しが新しい関数に置き換えられています。
- 以前は
-
フリーリスト内のオブジェクトのGCマーク:
- この変更の核心は、GCのマークフェーズ中にフリーリスト内のオブジェクトを明示的にマークすることです。
src/pkg/runtime/mgc0.c
にaddfreelists()
という新しい静的関数が追加されました。この関数は、各PのMCache
内のフリーリストと、各MSpan
のフリーリストを走査し、そこにあるオブジェクトをmarkonly
(またはsweepspan
内で直接ビットを操作)することで、bitMarked
ビットを設定します。gc()
関数(GCのメインエントリポイント)内で、addroots()
の後にaddfreelists()
が呼び出されるようになりました。これにより、GCが開始されるとすぐにフリーリスト内のオブジェクトがマークされ、誤って回収されるのを防ぎます。sweepspan
関数内でも、s->freelist
を走査し、フリーリスト内のオブジェクトのbitMarked
ビットを直接設定するロジックが追加されました。これは、markonly
関数を呼び出すよりも高速な方法です。
-
runtime·markfreed
の変更:runtime·markfreed
関数は、オブジェクトが解放されてフリーリストに戻される際に呼び出されます。- 以前は、解放されたオブジェクトのビットを
bitBlockBoundary
に設定していました。 - このコミットでは、解放されたオブジェクトのビットを
bitAllocated
に設定するように変更されました。これは、フリーリスト内のオブジェクトを「割り当て済み」として扱うという新しい戦略に合致します。これにより、ポインタを持たないオブジェクトがmalloc
された際に、ビットマップのbitAllocated
ビットを変更する必要がなくなります。
-
runtime·markspan
の変更:runtime·markspan
は、新しいMSpan
がヒープに割り当てられた際に、そのスパン内のビットマップを初期化するために使用されます。- 以前は、各ブロックの境界に
bitBlockBoundary
を設定していました。 - このコミットでは、各ブロックの境界に
bitAllocated
を設定するように変更されました。これは、新しいスパンのメモリが割り当てられた直後から、その領域が「割り当て済み」として扱われることを意味します。
これらの変更により、Goランタイムは、フリーリスト内のオブジェクトをGCの対象外として扱い、malloc
時のビットマップ操作を削減することで、特に小さなポインタレスオブジェクトの割り当て性能を向上させています。
コアとなるコードの変更箇所
src/pkg/runtime/malloc.goc
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -97,8 +97,10 @@ runtime·mallocgc(uintptr size, uintptr typ, uint32 flag)
runtime·markspan(v, 0, 0, true);
}
- if(!(flag & FlagNoGC))
- runtime·markallocated(v, size, (flag&FlagNoScan) != 0);
+ if(flag & FlagNoGC)
+ runtime·marknogc(v);
+ else if(!(flag & FlagNoScan))
+ runtime·markscan(v);
if(DebugTypeAtBlockEnd)
*(uintptr*)((uintptr)v+size-sizeof(uintptr)) = typ;
src/pkg/runtime/malloc.h
--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -449,7 +449,8 @@ void* runtime·mallocgc(uintptr size, uintptr typ, uint32 flag);
void* runtime·persistentalloc(uintptr size, uintptr align, uint64 *stat);
int32 runtime·mlookup(void *v, byte **base, uintptr *size, MSpan **s);
void runtime·gc(int32 force);
-void runtime·markallocated(void *v, uintptr n, bool noptr);
+void runtime·markscan(void *v);
+void runtime·marknogc(void *v);
void runtime·checkallocated(void *v, uintptr n);
void runtime·markfreed(void *v, uintptr n);
void runtime·checkfreed(void *v, uintptr n);
src/pkg/runtime/mgc0.c
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -81,7 +81,7 @@ clearpools(void)
// 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 bitNoScan/bitBlockBoundary bits, then the 16 bitAllocated bits.
+// then the 16 bitScan/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
@@ -97,13 +97,13 @@ clearpools(void)
// bits = *b >> shift;
// /* then test bits & bitAllocated, bits & bitMarked, etc. */
//
-#define bitAllocated ((uintptr)1<<(bitShift*0))
-#define bitNoScan ((uintptr)1<<(bitShift*1)) /* when bitAllocated is set */
+#define bitAllocated ((uintptr)1<<(bitShift*0)) /* block start; eligible for garbage collection */
+#define bitScan ((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 */
+#define bitBlockBoundary ((uintptr)1<<(bitShift*1)) /* when bitAllocated is NOT set - mark for FlagNoGC objects */
-#define bitMask (bitBlockBoundary | bitAllocated | bitMarked | bitSpecial)
+#define bitMask (bitAllocated | bitScan | bitMarked | bitSpecial)
// Holding worldsema grants an M the right to try to stop the world.
// The procedure is:
@@ -534,7 +534,7 @@ flushptrbuf(Scanbuf *sbuf)
}
// If object has no pointers, don't need to scan further.
- if((bits & bitNoScan) != 0)
+ if((bits & bitScan) == 0)
continue;
// Ask span about size class.
@@ -1187,7 +1187,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.
- if((bits & bitNoScan) != 0)
+ if((bits & bitScan) == 0)
continue;
debug_scanblock(obj, size);
@@ -1676,6 +1676,28 @@ addroots(void)
addroot((Obj){(byte*)fb->fin, fb->cnt*sizeof(fb->fin[0]), 0});
}
+static void
+addfreelists(void)
+{
+ int32 i;
+ P *p, **pp;
+ MCache *c;
+ MLink *m;
+
+ // Mark objects in the MCache of each P so we don't collect them.
+ for(pp=runtime·allp; p=*pp; pp++) {
+ c = p->mcache;
+ if(c==nil)
+ continue;
+ for(i = 0; i < NumSizeClasses; i++) {
+ for(m = c->list[i].list; m != nil; m = m->next) {
+ markonly(m);
+ }
+ }
+ }
+ // Note: the sweeper will mark objects in each span's freelist.
+}
+
static bool
handlespecial(byte *p, uintptr size)
{
@@ -1722,7 +1744,7 @@ static void
sweepspan(ParFor *desc, uint32 idx)
{
int32 cl, n, npages;
- uintptr size;
+ uintptr size, off, *bitp, shift;
byte *p;
MCache *c;
byte *arena_start;
@@ -1732,6 +1754,17 @@ sweepspan(ParFor *desc, uint32 idx)
byte compression;
uintptr type_data_inc;
MSpan *s;
+ MLink *x;
USED(&desc);
s = runtime·mheap.allspans[idx];
@@ -1751,6 +1784,17 @@ sweepspan(ParFor *desc, uint32 idx)
nfree = 0;
end = &head;
c = m->mcache;
+
+ // mark any free objects in this span so we don't collect them
+ for(x = s->freelist; x != nil; x = x->next) {
+ // This is markonly(x) but faster because we don't need
+ // atomic access and we're guaranteed to be pointing at
+ // the head of a valid object.
+ off = (uintptr*)x - (uintptr*)runtime·mheap.arena_start;
+ bitp = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
+ shift = off % wordsPerBitmapWord;
+ *bitp |= bitMarked<<shift;
+ }
type_data = (byte*)s->types.data;
type_data_inc = sizeof(uintptr);
@@ -1794,8 +1838,8 @@ sweepspan(ParFor *desc, uint32 idx)
continue;
}
- // Mark freed; restore block boundary bit.
- *bitp = (*bitp & ~(bitMask<<shift)) | (bitBlockBoundary<<shift);
+ // Clear mark, scan, and special bits.
+ *bitp &= ~((bitScan|bitMarked|bitSpecial)<<shift);
if(cl == 0) {
// Free large span.
@@ -2213,6 +2257,7 @@ gc(struct gc_args *args)
work.debugmarkdone = 0;
work.nproc = runtime·gcprocs();
addroots();
+ addfreelists();
runtime·parforsetup(work.markfor, work.nproc, work.nroot, nil, false, markroot);
runtime·parforsetup(work.sweepfor, work.nproc, runtime·mheap.nspan, nil, true, sweepspan);
if(work.nproc > 1) {
@@ -2439,18 +2484,35 @@ runfinq(void)
}
}
-// mark the block at v of size n as allocated.
-// If noscan is true, mark it as not needing scanning.
void
-runtime·markallocated(void *v, uintptr n, bool noscan)
+runtime·marknogc(void *v)
{
uintptr *b, obits, bits, off, shift;
-\tif(0)
-\t\truntime·printf("markallocated %p+%p\n", v, n);
+\toff = (uintptr*)v - (uintptr*)runtime·mheap.arena_start; // word offset
+\tb = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
+\tshift = off % wordsPerBitmapWord;
-\tif((byte*)v+n > (byte*)runtime·mheap.arena_used || (byte*)v < runtime·mheap.arena_start)
-\t\truntime·throw("markallocated: bad pointer");
+\tfor(;;) {
+\t\tobits = *b;
+\t\tif((obits>>shift & bitMask) != bitAllocated)
+\t\t\truntime·throw("bad initial state for marknogc");
+\t\tbits = (obits & ~(bitAllocated<<shift)) | bitBlockBoundary<<shift;
+\t\tif(runtime·gomaxprocs == 1) {
+\t\t\t*b = bits;
+\t\t\tbreak;
+\t\t} else {
+\t\t\t// more than one goroutine is potentially running: use atomic op
+\t\t\tif(runtime·casp((void**)b, (void*)obits, (void*)bits))\n+\t\t\t\tbreak;
+\t\t}\n+\t}\n+}\n+\n+void
+runtime·markscan(void *v)
+{
+ uintptr *b, obits, bits, off, shift;
\toff = (uintptr*)v - (uintptr*)runtime·mheap.arena_start; // word offset
\tb = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
@@ -2458,9 +2520,9 @@ runtime·markallocated(void *v, uintptr n, bool noscan)
for(;;) {
obits = *b;
-\t\tbits = (obits & ~(bitMask<<shift)) | (bitAllocated<<shift);
-\t\tif(noscan)\n-\t\t\tbits |= bitNoScan<<shift;
+\t\tif((obits>>shift & bitMask) != bitAllocated)
+\t\t\truntime·throw("bad initial state for markscan");
+\t\tbits = obits | bitScan<<shift;
if(runtime·gomaxprocs == 1) {
*b = bits;
break;
@@ -2490,7 +2552,10 @@ runtime·markfreed(void *v, uintptr n)
for(;;) {
obits = *b;
-\t\tbits = (obits & ~(bitMask<<shift)) | (bitBlockBoundary<<shift);
+\t\t// This could be a free of a gc-eligible object (bitAllocated + others) or
+\t\t// a FlagNoGC object (bitBlockBoundary set). In either case, we revert to
+\t\t// a simple no-scan allocated object because it is going on a free list.
+\t\tbits = (obits & ~(bitMask<<shift)) | (bitAllocated<<shift);
if(runtime·gomaxprocs == 1) {
*b = bits;
break;
@@ -2531,12 +2596,22 @@ runtime·checkfreed(void *v, uintptr n)
void
runtime·markspan(void *v, uintptr size, uintptr n, bool leftover)
{
-\tuintptr *b, off, shift;\n+\tuintptr *b, off, shift, i;
byte *p;
if((byte*)v+size*n > (byte*)runtime·mheap.arena_used || (byte*)v < runtime·mheap.arena_start)
runtime·throw("markspan: bad pointer");
+\tif(runtime·checking) {
+\t\t// bits should be all zero at the start
+\t\toff = (byte*)v + size - runtime·mheap.arena_start;
+\t\tb = (uintptr*)(runtime·mheap.arena_start - off/wordsPerBitmapWord);
+\t\tfor(i = 0; i < size/PtrSize/wordsPerBitmapWord; i++) {
+\t\t\tif(b[i] != 0)\n+\t\t\t\truntime·throw("markspan: span bits not zero");
+\t\t}\n+\t}\n+\n p = v;
if(leftover) // mark a boundary just past end of last block too
n++;
@@ -2548,7 +2623,7 @@ runtime·markspan(void *v, uintptr size, uintptr n, bool leftover)
off = (uintptr*)p - (uintptr*)runtime·mheap.arena_start; // word offset
b = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
shift = off % wordsPerBitmapWord;
-\t\t*b = (*b & ~(bitMask<<shift)) | (bitBlockBoundary<<shift);
+\t\t*b = (*b & ~(bitMask<<shift)) | (bitAllocated<<shift);
}
}
コアとなるコードの解説
src/pkg/runtime/malloc.goc
の変更
runtime·mallocgc
関数は、Goのオブジェクト割り当ての主要なエントリポイントです。- 変更前は、
FlagNoGC
(GC対象外)でないオブジェクトに対してruntime·markallocated
を呼び出し、FlagNoScan
フラグに基づいてスキャン要否を渡していました。 - 変更後は、
FlagNoGC
が設定されている場合は新しく導入されたruntime·marknogc(v)
を呼び出します。これは、GC対象外のオブジェクトをbitBlockBoundary
としてマークします。 FlagNoGC
が設定されておらず、かつFlagNoScan
が設定されていない(つまりスキャンが必要な)場合は、新しく導入されたruntime·markscan(v)
を呼び出します。これは、ポインタを含むオブジェクトをbitScan
としてマークします。- この変更により、オブジェクトの性質(GC対象か否か、スキャン必要か否か)に応じて、より適切なビットマップ操作を行う関数が直接呼び出されるようになりました。特に、ポインタを持たないオブジェクト(
FlagNoScan
が設定されている場合)は、runtime·markscan
が呼び出されないため、bitScan
ビットが設定されず、スキャン不要であることが明示されます。
src/pkg/runtime/malloc.h
の変更
runtime·markallocated
関数の宣言が削除され、代わりにruntime·markscan
とruntime·marknogc
の新しい2つの関数の宣言が追加されました。これは、上記malloc.goc
の変更と整合性を保つためのヘッダファイルの更新です。
src/pkg/runtime/mgc0.c
の変更
-
ビットマップ定義の変更:
#define bitNoScan
が#define bitScan
に変更され、そのコメントも「when bitAllocated is set」に変更されました。これは、bitScan
が設定されている場合にスキャンが必要であることを示します。bitAllocated
のコメントに「block start; eligible for garbage collection」が追加され、bitBlockBoundary
のコメントに「mark for FlagNoGC objects」が追加されました。これにより、各ビットの役割がより明確になりました。bitMask
の定義からbitBlockBoundary
が削除され、代わりにbitScan
が追加されました。これは、bitMask
がbitAllocated
が設定されている場合のオブジェクトの状態ビットのマスクとして機能することを明確にします。
-
スキャンロジックの反転:
flushptrbuf
とdebug_scanblock
関数内のスキャン要否のチェックが、if((bits & bitNoScan) != 0)
からif((bits & bitScan) == 0)
に変更されました。これは、bitNoScan
からbitScan
への意味の反転に対応するものです。bitScan
が0であればスキャン不要、1であればスキャン必要となります。
-
addfreelists()
関数の追加:- この新しい関数は、GCのマークフェーズ中にフリーリスト内のオブジェクトをマークするために導入されました。
runtime·allp
を走査し、各PのMCache
内のフリーリスト(c->list[i].list
)にあるすべてのMLink
(フリーオブジェクト)をmarkonly(m)
でマークします。markonly
は、オブジェクトのbitMarked
ビットを設定する関数です。- コメントにあるように、
sweepspan
関数もスパンのフリーリスト内のオブジェクトをマークするため、ここではMCache
のフリーリストに焦点を当てています。
-
sweepspan
関数内のフリーリストマークロジックの追加:sweepspan
関数は、GCのスイープフェーズ中に各MSpan
を処理します。- この関数内に、
s->freelist
(スパンのフリーリスト)を走査し、各フリーオブジェクトx
のbitMarked
ビットを直接設定するループが追加されました。 *bitp |= bitMarked<<shift;
という直接的なビット操作は、markonly(x)
を呼び出すよりも高速であり、アトミックアクセスが不要なため、このコンテキストで効率的です。
-
sweepspan
関数内のビットクリアロジックの変更:- オブジェクトが解放される際、以前は
*bitp = (*bitp & ~(bitMask<<shift)) | (bitBlockBoundary<<shift);
として、bitBlockBoundary
を設定していました。 - 変更後は、
*bitp &= ~((bitScan|bitMarked|bitSpecial)<<shift);
として、bitScan
、bitMarked
、bitSpecial
ビットをクリアするようになりました。これは、解放されたオブジェクトがフリーリストに入る際に、これらのビットをクリアし、bitAllocated
の状態を維持するという新しい戦略に合致します。
- オブジェクトが解放される際、以前は
-
gc()
関数内のaddfreelists()
呼び出し:- GCのメイン関数である
gc()
内で、addroots()
の直後にaddfreelists()
が呼び出されるようになりました。これにより、GCのマークフェーズの初期段階で、フリーリスト内のオブジェクトが確実にマークされ、誤って回収されるのを防ぎます。
- GCのメイン関数である
-
runtime·marknogc
関数の追加:- この新しい関数は、
FlagNoGC
が設定されたオブジェクト(GC対象外)をマークするために使用されます。 - オブジェクトのビットマップの
bitAllocated
ビットをクリアし、代わりにbitBlockBoundary
ビットを設定します。これは、GC対象外のオブジェクトがヒープ上で占有しているが、GCによってスキャンもマークもされないことを示します。
- この新しい関数は、
-
runtime·markscan
関数の追加:- この新しい関数は、ポインタを含む(スキャンが必要な)オブジェクトをマークするために使用されます。
- オブジェクトのビットマップの
bitScan
ビットを設定します。これにより、GCがこのオブジェクトをスキャンする必要があることを示します。
-
runtime·markfreed
関数の変更:- オブジェクトが解放されてフリーリストに戻される際、以前は
bitBlockBoundary
を設定していました。 - 変更後は、
bitMask
をクリアし、bitAllocated
を設定するように変更されました。これは、フリーリスト内のオブジェクトを「割り当て済み」として扱うという新しい戦略に合致します。これにより、malloc
時にbitAllocated
ビットを変更する必要がなくなります。
- オブジェクトが解放されてフリーリストに戻される際、以前は
-
runtime·markspan
関数の変更:- 新しいスパンがヒープに割り当てられた際、以前は各ブロックの境界に
bitBlockBoundary
を設定していました。 - 変更後は、各ブロックの境界に
bitAllocated
を設定するように変更されました。これは、新しいスパンのメモリが割り当てられた直後から、その領域が「割り当て済み」として扱われることを意味します。また、デバッグビルドでは、初期状態のビットがすべてゼロであることを確認するチェックが追加されました。
- 新しいスパンがヒープに割り当てられた際、以前は各ブロックの境界に
これらの変更は、Goランタイムのメモリ管理とGCの効率を向上させるために、ヒープビットマップのセマンティクスとフリーリストの扱い方を根本的に見直したものです。特に、ポインタを持たないオブジェクトのmalloc
性能が向上したことは、この最適化の成功を示しています。
関連リンク
参考にした情報源リンク
- Goの公式ドキュメントやソースコード(特に
src/runtime
ディレクトリ) - Goのガベージコレクションに関する技術ブログや論文(コミット当時のGo 1.2前後のGCに関する情報)
- Goのメモリ管理に関する解説記事