[インデックス 18668] ファイルの概要
このコミットは、Goランタイムのメモリ管理における重要な変更を導入しています。特に、settype
バッファとそれに関連するロックを排除し、MCache
(各P(プロセッサ)ごとのキャッシュ)が特定のMSpan
(メモリページの連続したブロック)に対して排他的なアクセスを持つようにすることで、並行処理のパフォーマンスを大幅に向上させています。これにより、マルチプロセッサ環境でのアロケーションベンチマークにおいて顕著な速度改善が見られます。
コミット
commit 3b5278fca624e802ae71351626719ba262d0c5d1
Author: Keith Randall <khr@golang.org>
Date: Wed Feb 26 15:52:58 2014 -0800
runtime: get rid of the settype buffer and lock.
MCaches now hold a MSpan for each sizeclass which they have
exclusive access to allocate from, so no lock is needed.
Modifying the heap bitmaps also no longer requires a cas.
runtime.free gets more expensive. But we don't use it
much any more.
It's not much faster on 1 processor, but it's a lot
faster on multiple processors.
benchmark old ns/op new ns/op delta
BenchmarkSetTypeNoPtr1 24 23 -0.42%
BenchmarkSetTypeNoPtr2 33 34 +0.89%
BenchmarkSetTypePtr1 51 49 -3.72%
BenchmarkSetTypePtr2 55 54 -1.98%
benchmark old ns/op new ns/op delta
BenchmarkAllocation 52739 50770 -3.73%
BenchmarkAllocation-2 33957 34141 +0.54%
BenchmarkAllocation-3 33326 29015 -12.94%
BenchmarkAllocation-4 38105 25795 -32.31%
BenchmarkAllocation-5 68055 24409 -64.13%
BenchmarkAllocation-6 71544 23488 -67.17%
BenchmarkAllocation-7 68374 23041 -66.30%
BenchmarkAllocation-8 70117 20758 -70.40%
LGTM=rsc, dvyukov
R=dvyukov, bradfitz, khr, rsc
CC=golang-codereviews
https://golang.org/cl/46810043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3b5278fca624e802ae71351626719ba262d0c5d1
元コミット内容
runtime: get rid of the settype buffer and lock.
MCaches now hold a MSpan for each sizeclass which they have
exclusive access to allocate from, so no lock is needed.
Modifying the heap bitmaps also no longer requires a cas.
runtime.free gets more expensive. But we don't use it
much any more.
It's not much faster on 1 processor, but it's a lot
faster on multiple processors.
benchmark old ns/op new ns/op delta
BenchmarkSetTypeNoPtr1 24 23 -0.42%
BenchmarkSetTypeNoPtr2 33 34 +0.89%
BenchmarkSetTypePtr1 51 49 -3.72%
BenchmarkSetTypePtr2 55 54 -1.98%
benchmark old ns/op new ns/op delta
BenchmarkAllocation 52739 50770 -3.73%
BenchmarkAllocation-2 33957 34141 +0.54%
BenchmarkAllocation-3 33326 29015 -12.94%
BenchmarkAllocation-4 38105 25795 -32.31%
BenchmarkAllocation-5 68055 24409 -64.13%
BenchmarkAllocation-6 71544 23488 -67.17%
BenchmarkAllocation-7 68374 23041 -66.30%
BenchmarkAllocation-8 70117 20758 -70.40%
LGTM=rsc, dvyukov
R=dvyukov, bradfitz, khr, rsc
CC=golang-codereviews
https://golang.org/cl/46810043
変更の背景
このコミットの主な目的は、Goランタイムのメモリ割り当てにおける並行処理の効率を向上させることです。特に、マルチコアプロセッサ環境でのパフォーマンスボトルネックとなっていたsettype
バッファとそれに関連するロックの競合を解消することに焦点を当てています。
以前のGoランタイムでは、オブジェクトの型情報をヒープに記録する際に、settype_buf
というバッファとsettype_lock
というグローバルロックを使用していました。これは、複数のゴルーチンが同時にメモリを割り当てる際に、このロックを巡って競合が発生し、並行処理の効率を低下させる原因となっていました。
また、ヒープのビットマップ(メモリのどの部分が割り当てられているか、ポインタが含まれているかなどを追跡するためのデータ構造)の変更も、アトミック操作(CAS: Compare-And-Swap)を必要としており、これもまた並行処理のオーバーヘッドとなっていました。
このコミットは、これらの競合ポイントを排除することで、特に並行性の高いワークロードにおいて、メモリ割り当てのパフォーマンスを劇的に改善することを目指しています。ベンチマーク結果が示すように、特にプロセッサ数が増えるにつれて、BenchmarkAllocation
の性能が大幅に向上しています。
前提知識の解説
このコミットを理解するためには、Goランタイムのメモリ管理の基本的な概念と、ガベージコレクション(GC)の仕組みについて理解しておく必要があります。
-
Goのメモリ管理の階層構造:
- MHeap: Goランタイム全体のヒープを管理する最上位の構造体です。ページ(通常4KB)単位でメモリを管理します。
- MSpan: 連続したメモリページのブロックを表します。MHeapから取得され、特定のサイズのオブジェクトを割り当てるために使用されます。各MSpanは、そのMSpanから割り当てられたオブジェクトの型情報(ポインタの有無など)を追跡します。
- MCentral: 特定のサイズクラス(オブジェクトのサイズに応じた分類)のMSpanを管理する共有フリーリストです。MCacheがMSpanを要求する際に、MCentralから取得します。
- MCache: 各P(プロセッサ、Goスケジューラがゴルーチンを実行する論理的なコンテキスト)に紐付けられたローカルなキャッシュです。小さなオブジェクトの割り当てを高速化するために使用されます。MCacheは、MCentralからMSpanを取得し、そこからオブジェクトを割り当てます。これにより、グローバルなロックの競合を減らします。
- P (Processor): Goランタイムのスケジューラがゴルーチンを実行するための論理的なプロセッサです。各Pは独自のMCacheを持ちます。
- M (Machine): OSのスレッドを表します。MはPと関連付けられ、P上でゴルーチンを実行します。
-
ガベージコレクション (GC): Goはトレース型ガベージコレクタを使用しており、到達可能なオブジェクトを特定し、到達不能なオブジェクトを解放します。GCはヒープ上のオブジェクトの型情報を利用して、ポインタを正確にスキャンし、到達可能なオブジェクトをマークします。
-
ヒープビットマップ: Goランタイムは、ヒープ上のメモリブロックに関するメタデータをビットマップとして管理しています。これには、メモリブロックが割り当てられているか、ポインタが含まれているか、GCによってマークされているかなどの情報が含まれます。これらのビットマップは、GCの効率的な動作に不可欠です。
-
settype
: Goランタイムがヒープにオブジェクトを割り当てる際に、そのオブジェクトの型情報をMSpanに記録する処理です。この型情報は、GCがヒープをスキャンする際に、どの部分がポインタであり、どの部分がそうでないかを判断するために使用されます。 -
CAS (Compare-And-Swap): アトミック操作の一種で、メモリ位置の現在の値が期待される値と等しい場合にのみ、そのメモリ位置を新しい値に更新します。マルチスレッド環境での競合を避けるために使用されますが、頻繁な使用はオーバーヘッドとなる可能性があります。
技術的詳細
このコミットの技術的な核心は、Goランタイムのメモリ割り当てパスからグローバルなロックとアトミック操作を排除し、並行性を高めることにあります。
-
settype
バッファとロックの排除:- 以前は、
M
構造体(OSスレッドを表す)内にsettype_buf
というバッファと、それを保護するためのグローバルなsettype_lock
が存在しました。オブジェクトが割り当てられるたびに、その型情報がこのバッファに一時的に格納され、バッファが満杯になるとsettype_flush
関数がロックを取得してバッファの内容を処理していました。 - このコミットでは、
settype_buf
とsettype_lock
が完全に削除されました。代わりに、settype
関数は、オブジェクトが割り当てられた直後に、そのオブジェクトが属するMSpan
に直接型情報を記録するように変更されました。 - この変更の鍵は、
MCache
が特定のsizeclass
(オブジェクトのサイズカテゴリ)に対応するMSpan
を排他的に所有するようになったことです。これにより、settype
関数がMSpan
の型情報を更新する際に、他のゴルーチンとの競合を心配する必要がなくなり、ロックが不要になりました。
- 以前は、
-
ヒープビットマップの変更におけるCASの排除:
- 以前は、
markscan
(GCがオブジェクトをスキャンする際にビットマップを更新する)やmarkfreed
(オブジェクトが解放された際にビットマップを更新する)のような操作で、ヒープビットマップを変更する際にCAS操作を使用していました。これは、複数のゴルーチンが同時にビットマップを更新しようとする際の競合を避けるためでした。 - このコミットでは、
markscan
とmarkfreed
関数内のCASループが削除され、直接的なビットマップの書き込みに置き換えられました。これは、MCache
がMSpan
を排他的に所有するようになったことで、ビットマップの変更がローカルな操作となり、競合の可能性が大幅に減少したためです。
- 以前は、
-
MCache
とMSpan
の関連性の強化:MCache
構造体は、MCacheList list[NumSizeClasses]
からMSpan* alloc[NumSizeClasses]
とMCacheList free[NumSizeClasses]
に変更されました。alloc[sizeclass]
は、そのMCache
が現在割り当てを行っているMSpan
へのポインタを保持します。このMSpan
は、そのMCache
によって排他的に所有されます。free[sizeclass]
は、明示的に解放されたオブジェクトのリストを保持します。これらのオブジェクトは、後でMCentralに返されるか、同じMCacheによって再利用されます。MCache_Refill
関数は、MCentralから新しいMSpanを取得し、それをc->alloc[sizeclass]
に設定するように変更されました。MCache_Free
関数は、オブジェクトをc->free[sizeclass]
リストに追加するように変更されました。もし、そのオブジェクトが現在MCacheが所有しているMSpanに属している場合は、そのMSpanのフリーリストに直接追加されます。
-
runtime.free
のコスト増加:- コミットメッセージには「
runtime.free
gets more expensive. But we don't use it much any more.」とあります。これは、オブジェクトの解放パスが以前よりも複雑になったことを示唆しています。特に、MCache_Free
のロジックが変更され、オブジェクトがMCacheが所有するMSpanに属しているかどうかをチェックし、そうでない場合はフリーキューに追加するなどの処理が加わったためと考えられます。しかし、Goのランタイムでは、GCがほとんどのメモリ解放を処理するため、runtime.free
が直接呼び出される頻度は低く、このコスト増加は全体的なパフォーマンスに大きな影響を与えないと判断されています。
- コミットメッセージには「
これらの変更により、Goランタイムのメモリ割り当ては、特にマルチコア環境において、よりスケーラブルで効率的になりました。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
src/pkg/runtime/gc_test.go
: 新しいベンチマークが追加されました。BenchmarkSetTypeNoPtrX
,BenchmarkSetTypePtrX
,BenchmarkAllocation
など、メモリ割り当てと型設定のパフォーマンスを測定するためのテストです。src/pkg/runtime/malloc.goc
: メモリ割り当ての主要なロジックが含まれるファイルです。runtime·mallocgc
関数内で、settype_buf
とsettype_bufsize
に関連するコードが削除されました。MCacheList
の代わりにMSpan
を直接扱うように変更されました。largealloc
関数の戻り値がvoid*
からMSpan*
に変更されました。settype
関数の呼び出しが変更され、MSpan
を直接渡すようになりました。runtime·free
関数内で、MCache_Free
の呼び出しロジックが変更されました。
src/pkg/runtime/malloc.h
: メモリ管理関連の構造体と関数の宣言が含まれるヘッダファイルです。MCache
構造体の定義が大幅に変更され、MCacheList list[NumSizeClasses]
がMSpan* alloc[NumSizeClasses]
とMCacheList free[NumSizeClasses]
に置き換えられました。MSpan
構造体にincache
フィールドとfreebuf
フィールドが追加されました。MCache_Refill
とMCache_Free
の関数シグネチャが変更されました。MCentral
構造体のコメントが更新され、nonempty
とempty
リストの役割が明確化されました。runtime·markfreed
のシグネチャからuintptr n
引数が削除されました。runtime·settype_flush
とruntime·settype_sysfree
の宣言が削除されました。
src/pkg/runtime/mcache.c
:MCache
の実装ファイルです。runtime·allocmcache
関数が変更され、MCache
の初期化時にalloc
配列がemptymspan
で初期化されるようになりました。runtime·MCache_Refill
関数が大幅に書き換えられ、MCentralからMSpanを取得し、それをMCacheのalloc
配列に設定するようになりました。runtime·MCache_Free
関数が変更され、オブジェクトをfree
リストに追加し、必要に応じてMCentralに返すようになりました。runtime·MCache_ReleaseAll
関数が変更され、alloc
配列内のMSpanもMCentralに返すようになりました。
src/pkg/runtime/mcentral.c
:MCentral
の実装ファイルです。runtime·MCentral_AllocList
がruntime·MCentral_CacheSpan
に名称変更され、MCacheに割り当てるためのMSpanを返すように変更されました。runtime·MCentral_UncacheSpan
関数が追加され、MCacheから返されたMSpanを処理するようになりました。MCentral_Free
関数内で、s->incache
のチェックが追加され、MCacheが使用中のMSpanへの解放はfreebuf
に格納されるようになりました。MCentral_FreeSpan
関数内で、s->incache
のチェックが追加され、sweepgen
の更新タイミングが変更されました。MCentral_ReturnToHeap
というヘルパー関数が追加され、MSpanをヒープに返すロジックがカプセル化されました。
src/pkg/runtime/mgc0.c
: ガベージコレクション関連のコードが含まれるファイルです。gc
関数からruntime·settype_flush(mp)
の呼び出しが削除されました。runtime·markscan
とruntime·markfreed
関数からCAS操作が削除され、直接的なビットマップの書き込みに置き換えられました。runtime·MSpan_Sweep
関数内で、sweepgen
の更新ロジックが変更されました。
src/pkg/runtime/mheap.c
:MHeap
の実装ファイルです。runtime·MSpan_Init
関数内で、span->incache = false;
とspan->freebuf = nil;
の初期化が追加されました。
src/pkg/runtime/runtime.h
: ランタイムのグローバルな定義が含まれるヘッダファイルです。M
構造体からsettype_buf
とsettype_bufsize
フィールドが削除されました。
コアとなるコードの解説
このコミットの主要な変更は、Goランタイムのメモリ割り当てとGCの連携方法を根本的に見直した点にあります。
1. settype
の変更 (malloc.goc
, mgc0.c
, runtime.h
)
以前は、オブジェクトの型情報をM
(OSスレッド)に紐付けられたsettype_buf
に一時的にバッファリングし、バッファが満杯になったりGCがトリガーされたりした際に、グローバルロックsettype_lock
を取得してsettype_flush
関数でまとめて処理していました。このグローバルロックが並行処理のボトルネックでした。
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -175,21 +177,12 @@ runtime·mallocgc(uintptr size, uintptr typ, uint32 flag)
if(DebugTypeAtBlockEnd)
*(uintptr*)((uintptr)v+size-sizeof(uintptr)) = typ;
+ m->mallocing = 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;
- i = m->settype_bufsize;
- buf[i++] = (uintptr)v;
- buf[i++] = typ;
- m->settype_bufsize = i;
- }
+ if(UseSpanType && !(flag & FlagNoScan) && typ != 0)
+ settype(s, v, typ);
- m->mallocing = 0;
- if(UseSpanType && !(flag & FlagNoScan) && typ != 0 && m->settype_bufsize == nelem(m->settype_buf))
- runtime·settype_flush(m);
if(raceenabled)
runtime·racemalloc(v, size);
この変更により、runtime·mallocgc
内でsettype_buf
への書き込みとsettype_flush
の呼び出しが削除され、代わりにsettype(s, v, typ)
が直接呼び出されるようになりました。ここでs
は割り当てられたオブジェクトv
が属するMSpan
です。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -2302,9 +2310,6 @@ gc(struct gc_args *args)
if(CollectStats)
runtime·memclr((byte*)&gcstats, sizeof(gcstats));
- for(mp=runtime·allm; mp; mp=mp->alllink)
- runtime·settype_flush(mp);
-
m->locks++; // disable gc during mallocs in parforalloc
if(work.markfor == nil)
work.markfor = runtime·parforalloc(MaxGcproc);
GCの開始時に全てのMに対してsettype_flush
を呼び出す処理も削除されました。これは、型情報が即座にMSpanに記録されるようになったため、GC開始時にバッファをフラッシュする必要がなくなったことを意味します。
2. MCache
とMSpan
の排他的所有権 (malloc.h
, mcache.c
, mcentral.c
)
この変更の核心は、MCache
が特定のsizeclass
に対してMSpan
を排他的に所有するようになったことです。
--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -301,7 +301,8 @@ struct MCache
byte* tiny;
uintptr tinysize;
// The rest is not accessed on every malloc.
- MCacheList list[NumSizeClasses];
+ MSpan* alloc[NumSizeClasses]; // spans to allocate from
+ MCacheList free[NumSizeClasses];// lists of explicitly freed objects
// Local allocator stats, flushed during GC.
uintptr local_nlookup; // number of pointer lookups
uintptr local_largefree; // bytes freed for large objects (>MaxSmallSize)
@@ -309,8 +310,9 @@ struct MCache
uintptr local_nsmallfree[NumSizeClasses]; // number of frees for small objects (<=MaxSmallSize)
};
-void runtime·MCache_Refill(MCache *c, int32 sizeclass);
-void runtime·MCache_Free(MCache *c, void *p, int32 sizeclass, uintptr size);
+MSpan* runtime·MCache_Refill(MCache *c, int32 sizeclass);
+void runtime·MCache_Free(MCache *c, MLink *p, int32 sizeclass, uintptr size);
void runtime·MCache_ReleaseAll(MCache *c);
MCache
構造体は、list
配列の代わりにalloc
とfree
配列を持つようになりました。alloc[sizeclass]
は、そのMCache
が現在割り当てを行っているMSpan
へのポインタを保持します。
--- a/src/pkg/runtime/mcache.c
+++ b/src/pkg/runtime/mcache.c
@@ -10,69 +10,119 @@
#include "arch_GOARCH.h"
#include "malloc.h"
+extern volatile intgo runtime·MemProfileRate;
+
+// dummy MSpan that contains no free objects.
+static MSpan emptymspan;
+
+MCache*
+runtime·allocmcache(void)
+{
+ intgo rate;
+ MCache *c;
+ int32 i;
+
+ runtime·lock(&runtime·mheap);
+ c = runtime·FixAlloc_Alloc(&runtime·mheap.cachealloc);
+ runtime·unlock(&runtime·mheap);
+ runtime·memclr((byte*)c, sizeof(*c));
+ for(i = 0; i < NumSizeClasses; i++)
+ c->alloc[i] = &emptymspan;
+
+ // Set first allocation sample size.
+ rate = runtime·MemProfileRate;
+ if(rate > 0x3fffffff) // make 2*rate not overflow
+ rate = 0x3fffffff;
+ if(rate != 0)
+ c->next_sample = runtime·fastrand1() % (2*rate);
+
+ return c;
+}
+
void
+runtime·freemcache(MCache *c)
+{
+ runtime·MCache_ReleaseAll(c);
+ runtime·lock(&runtime·mheap);
+ runtime·purgecachedstats(c);
+ runtime·FixAlloc_Free(&runtime·mheap.cachealloc, c);
+ runtime·unlock(&runtime·mheap);
+}
+
+// Gets a span that has a free object in it and assigns it
+// to be the cached span for the given sizeclass. Returns this span.
+MSpan*
runtime·MCache_Refill(MCache *c, int32 sizeclass)
{
MCacheList *l;
+ MSpan *s;
- // Replenish using central lists.
- l = &c->list[sizeclass];
- if(l->list)
- runtime·throw("MCache_Refill: the list is not empty");
- l->nlist = runtime·MCentral_AllocList(&runtime·mheap.central[sizeclass], &l->list);
- if(l->list == nil)
- runtime·throw("out of memory");
-}
+ m->locks++;
+ // Return the current cached span to the central lists.
+ s = c->alloc[sizeclass];
+ if(s->freelist != nil)
+ runtime·throw("refill on a nonempty span");
+ if(s != &emptymspan)
+ runtime·MCentral_UncacheSpan(&runtime·mheap.central[sizeclass], s);
-// Take n elements off l and return them to the central free list.
-static void
-ReleaseN(MCacheList *l, int32 n, int32 sizeclass)
-{
- MLink *first, **lp;
- int32 i;
+ // Push any explicitly freed objects to the central lists.
+ // Not required, but it seems like a good time to do it.
+ l = &c->free[sizeclass];
+ if(l->nlist > 0) {
+ runtime·MCentral_FreeList(&runtime·mheap.central[sizeclass], l->list);
+ l->list = nil;
+ l->nlist = 0;
+ }
- // Cut off first n elements.
- first = l->list;
- lp = &l->list;
- for(i=0; i<n; i++)
- lp = &(*lp)->next;
- l->list = *lp;
- *lp = nil;
- l->nlist -= n;
-
- // Return them to central free list.
- runtime·MCentral_FreeList(&runtime·mheap.central[sizeclass], first);
-}
+ // Get a new cached span from the central lists.
+ s = runtime·MCentral_CacheSpan(&runtime·mheap.central[sizeclass]);
+ if(s == nil)
+ runtime·throw("out of memory");
+ if(s->freelist == nil) {
+ runtime·printf("%d %d\\n", s->ref, (int32)((s->npages << PageShift) / s->elemsize));
+ runtime·throw("empty span");
+ }
+ c->alloc[sizeclass] = s;
+ m->locks--;
+ return s;
}
runtime·MCache_Refill
は、MCentralから新しいMSpan
を取得し、それをc->alloc[sizeclass]
に設定するようになりました。これにより、MCache
は特定のMSpan
を排他的に所有し、そのMSpan
からの割り当てはロックフリーで行えるようになります。
--- a/src/pkg/runtime/mcentral.c
+++ b/src/pkg/runtime/mcentral.c
@@ -30,12 +31,9 @@ runtime·MCentral_Init(MCentral *c, int32 sizeclass)
runtime·MSpanList_Init(&c->empty);
}
-// Allocate a list of objects from the central free list.
-// Return the number of objects allocated.
-// The objects are linked together by their first words.
-// On return, *pfirst points at the first object.
-int32
-runtime·MCentral_AllocList(MCentral *c, MLink **pfirst)
+// Allocate a span to use in an MCache.
+MSpan*
+runtime·MCentral_CacheSpan(MCentral *c)
{
MSpan *s;
int32 cap, n;
@@ -85,25 +83,63 @@ retry:
// Replenish central list if empty.
if(!MCentral_Grow(c)) {
runtime·unlock(c);
- *pfirst = nil;
- return 0;
+ return nil;
}
- s = c->nonempty.next;
+ goto retry;
havespan:
cap = (s->npages << PageShift) / s->elemsize;
n = cap - s->ref;
- *pfirst = s->freelist;
- s->freelist = nil;
- s->ref += n;
+ if(n == 0)
+ runtime·throw("empty span");
+ if(s->freelist == nil)
+ runtime·throw("freelist empty");
c->nfree -= n;
runtime·MSpanList_Remove(s);
runtime·MSpanList_InsertBack(&c->empty, s);
+ s->incache = true;
+ runtime·unlock(c);
+ return s;
+}
+
+// Return span from an MCache.
+void
+runtime·MCentral_UncacheSpan(MCentral *c, MSpan *s)
+{
+ MLink *v;
+ int32 cap, n;
+
+ runtime·lock(c);
+
+ s->incache = false;
+
+ // Move any explicitly freed items from the freebuf to the freelist.
+ while((v = s->freebuf) != nil) {
+ s->freebuf = v->next;
+ runtime·markfreed(v);
+ v->next = s->freelist;
+ s->freelist = v;
+ s->ref--;
+ }
+
+ if(s->ref == 0) {
+ // Free back to heap. Unlikely, but possible.
+ MCentral_ReturnToHeap(c, s); // unlocks c
+ return;
+ }
+
+ cap = (s->npages << PageShift) / s->elemsize;
+ n = cap - s->ref;
+ if(n > 0) {
+ c->nfree += n;
+ runtime·MSpanList_Remove(s);
+ runtime·MSpanList_Insert(&c->nonempty, s);
+ }
runtime·unlock(c);
- return n;
}
runtime·MCentral_AllocList
がruntime·MCentral_CacheSpan
に名称変更され、MSpan*
を直接返すようになりました。また、runtime·MCentral_UncacheSpan
が追加され、MCacheから返されたMSpanをMCentralに戻す処理を担います。この関数は、MCacheが所有していたMSpanのfreebuf
(MCacheが所有中に明示的に解放されたオブジェクトのバッファ)を処理し、必要に応じてMSpanをヒープに返します。
3. ヒープビットマップ操作の簡素化 (mgc0.c
)
markscan
とmarkfreed
関数は、ヒープビットマップを更新する際にCAS操作を使用する必要がなくなりました。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -2617,59 +2622,30 @@ runtime·marknogc(void *v)
void
runtime·markscan(void *v)
{
- uintptr *b, obits, bits, off, shift;
+ uintptr *b, off, shift;
off = (uintptr*)v - (uintptr*)runtime·mheap.arena_start; // word offset
b = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
shift = off % wordsPerBitmapWord;
-
- for(;;) {
- obits = *b;
- if((obits>>shift & bitMask) != bitAllocated)
- runtime·throw("bad initial state for markscan");
- bits = obits | bitScan<<shift;
- if(runtime·gomaxprocs == 1) {
- *b = bits;
- break;
- } else {
- // more than one goroutine is potentially running: use atomic op
- if(runtime·casp((void**)b, (void*)obits, (void*)bits))
- break;
- }
- }
+ *b |= bitScan<<shift;
}
-// mark the block at v of size n as freed.
+// mark the block at v as freed.
void
-runtime·markfreed(void *v, uintptr n)
+runtime·markfreed(void *v)
{
- uintptr *b, obits, bits, off, shift;
+ uintptr *b, off, shift;
if(0)
- runtime·printf("markfreed %p+%p\\n", v, n);
+ runtime·printf("markfreed %p\\n", v);
- if((byte*)v+n > (byte*)runtime·mheap.arena_used || (byte*)v < runtime·mheap.arena_start)
+ if((byte*)v > (byte*)runtime·mheap.arena_used || (byte*)v < runtime·mheap.arena_start)
runtime·throw("markfreed: bad pointer");
off = (uintptr*)v - (uintptr*)runtime·mheap.arena_start; // word offset
b = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
shift = off % wordsPerBitmapWord;
-
- for(;;) {
- obits = *b;
- // This could be a free of a gc-eligible object (bitAllocated + others) or
- // a FlagNoGC object (bitBlockBoundary set). In either case, we revert to
- // a simple no-scan allocated object because it is going on a free list.
- bits = (obits & ~(bitMask<<shift)) | (bitAllocated<<shift);
- if(runtime·gomaxprocs == 1) {
- *b = bits;
- break;
- } else {
- // more than one goroutine is potentially running: use atomic op
- if(runtime·casp((void**)b, (void*)obits, (void*)bits))
- break;
- }
- }
+ *b = (*b & ~(bitMask<<shift)) | (bitAllocated<<shift);
}
markscan
とmarkfreed
からCASループが削除され、直接的なビット操作に置き換えられました。これは、MCache
がMSpan
を排他的に所有することで、これらのビットマップ操作が競合しないことが保証されるためです。これにより、これらの操作のオーバーヘッドが削減されます。
これらの変更は、Goランタイムのメモリ割り当てとGCの並行性を大幅に向上させ、特にマルチコア環境でのアプリケーションのパフォーマンスを改善する上で重要な役割を果たしています。
関連リンク
- Goのメモリ管理に関する公式ドキュメントやブログ記事 (当時のものがあれば)
- Goのガベージコレクションに関する詳細な解説
- Goのスケジューラに関する情報
参考にした情報源リンク
- https://golang.org/cl/46810043 (このコミットのGo Code Reviewサイトのリンク)
- Goのソースコード (特に
src/runtime
ディレクトリ) - Goのメモリ管理に関する一般的な知識と資料
- Goのガベージコレクションに関する一般的な知識と資料
- Goのスケジューラに関する一般的な知識と資料