[インデックス 12844] ファイルの概要
このコミットは、Go言語のランタイムにおけるガベージコレクション(GC)のコードベースに対するマイナーなリファクタリングを導入しています。具体的には、sweep
関数から sweepspan
関数を分離し、将来的な並行GCの実装に向けた準備を行っています。論理的な変更は含まれておらず、コードの構造改善が主目的です。
コミット
commit 9903d6870f75f7c174ceb1bb8ea67e303920c8e5
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Apr 5 21:02:20 2012 +0400
runtime: minor refactoring in preparation for parallel GC
factor sweepspan() out of sweep(), no logical changes
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5991047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9903d6870f75f7c174ceb1bb8ea67e303920c8e5
元コミット内容
runtime: minor refactoring in preparation for parallel GC
factor sweepspan() out of sweep(), no logical changes
変更の背景
このコミットの主な背景は、Go言語のガベージコレクション(GC)メカニズムを将来的に並行化するための準備です。当時のGoのGCは、主にストップ・ザ・ワールド(STW)方式を採用しており、GC実行中にアプリケーションの実行が一時停止する時間が存在しました。このSTW時間を短縮し、GCの効率とスループットを向上させるために、並行GCへの移行が計画されていました。
並行GCを導入するためには、既存のGCコードベースをモジュール化し、並行処理に適した粒度で関数を分割する必要があります。sweep
関数はGCの「スイープフェーズ」を担当する重要な関数であり、その内部で複数の処理(メモリ領域の走査、マークビットのクリア、解放処理など)を行っていました。この複雑な関数をより小さな、独立した関数に分割することで、将来的に各処理を並行して実行しやすくするための基盤を築くことが目的でした。
このリファクタリングは、直接的な機能変更やバグ修正ではなく、コードの保守性、可読性、そして将来の拡張性を高めるための「内部品質改善」の一環として行われました。
前提知識の解説
ガベージコレクション (GC)
ガベージコレクションは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されなくなった(到達不能になった)領域を自動的に解放し、再利用可能にする仕組みです。これにより、プログラマは手動でのメモリ管理から解放され、メモリリークなどの問題を軽減できます。
Go言語のGCは、主に以下のフェーズで構成されます(当時のバージョンにおける一般的な概念):
- マークフェーズ (Mark Phase): プログラムが使用しているオブジェクト(到達可能なオブジェクト)を特定し、マーク(印付け)します。GoのGCは、通常、ルート(グローバル変数、スタック上の変数など)から到達可能なオブジェクトを辿ってマークします。
- スイープフェーズ (Sweep Phase): マークされなかったオブジェクト(到達不能なオブジェクト、つまりガベージ)をメモリから解放し、再利用可能な状態にします。このフェーズでは、マークビットをクリアして次のGCサイクルに備える作業も行われます。
ストップ・ザ・ワールド (Stop-The-World, STW)
STWは、GCの特定のフェーズ(特にマークフェーズの初期やスイープフェーズの一部)において、アプリケーションの実行を完全に一時停止させるGC方式です。これにより、GCがメモリの状態を安全かつ一貫性のある形で操作できるようになります。しかし、STW時間はアプリケーションの応答性やスループットに直接影響を与えるため、GCの進化はSTW時間の短縮を目指す傾向にあります。
並行GC (Concurrent GC)
並行GCは、GC処理の一部または大部分を、アプリケーションの実行と並行して(同時に)行うGC方式です。これにより、STW時間を大幅に短縮し、アプリケーションの停止時間を最小限に抑えることができます。並行GCの実装は複雑であり、GCとアプリケーションが同時にメモリを操作する際のデータ競合や一貫性の問題を解決するための高度なアルゴリズムが必要となります。
MSpan
と MCache
Goのランタイムにおけるメモリ管理は、MSpan
と MCache
という概念に基づいています。
MSpan
: 連続したページ(メモリブロックの単位)の集合を表します。Goのヒープは、これらのMSpan
の集まりとして管理されます。MSpan
は、特定のサイズのオブジェクト(スモールオブジェクト、ラージオブジェクト)を格納するために使用されます。MCache
: 各M(OSスレッドに対応するGoの論理プロセッサ)にローカルなキャッシュです。これにより、頻繁に割り当てられるスモールオブジェクトの割り当てと解放を高速化し、グローバルなヒープロックの競合を減らします。
mgc0.c
src/pkg/runtime/mgc0.c
は、Goランタイムのガベージコレクションの初期実装(または主要な部分)を含むC言語のソースファイルです。Goランタイムの一部は、パフォーマンスと低レベルのメモリ操作のためにC言語で書かれていました(後にGo言語自体で書き直される部分も増えます)。
技術的詳細
このコミットは、src/pkg/runtime/mgc0.c
ファイル内の sweep
関数から、sweepspan
という新しい静的関数を抽出しています。
元の sweep
関数は、GCのスイープフェーズにおいて、ヒープ全体を走査し、マークされていないメモリブロックを解放する役割を担っていました。この処理は、MSpan
(メモリ領域の単位)ごとにループし、各MSpan
内のオブジェクトを個別に処理していました。
抽出された sweepspan
関数は、単一の MSpan
を受け取り、その MSpan
内のすべてのオブジェクトに対してスイープ処理(マークビットのクリア、ガベージの解放、ファイナライザの処理など)を実行します。
この変更により、sweep
関数は、MSpan
のリストをイテレートし、各 MSpan
に対して sweepspan
を呼び出すという、より高レベルなロジックに簡素化されました。これにより、sweep
関数自体の複雑性が軽減され、各 MSpan
の処理が独立した関数としてカプセル化されました。
このリファクタリングは、並行GCの文脈で非常に重要です。将来的に、複数のゴルーチン(またはOSスレッド)が並行してスイープ処理を行う場合、各ゴルーチンが独立した MSpan
を取得し、その MSpan
に対して sweepspan
を並行して呼び出すことが可能になります。これにより、スイープフェーズ全体のスループットが向上し、STW時間の短縮に貢献できます。
コードの変更点を見ると、sweep
関数から、cl
, n
, npages
, size
, p
, c
, arena_start
といったローカル変数の宣言と、for(; n > 0; n--, p += size)
ループ内の処理が sweepspan
関数に移動していることがわかります。これにより、sweep
関数は for
ループ内で sweepspan(s);
を呼び出すだけのシンプルな構造になっています。
コアとなるコードの変更箇所
変更は src/pkg/runtime/mgc0.c
ファイルに集中しています。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -719,22 +719,17 @@ handlespecial(byte *p, uintptr size)
return true;
}
+static void sweepspan(MSpan *s);
+
// Sweep frees or collects finalizers for blocks not marked in the mark phase.
// It clears the mark bits in preparation for the next GC round.
static void
sweep(void)
{
MSpan *s;
- int32 cl, n, npages;
- uintptr size;
- byte *p;
- MCache *c;
- byte *arena_start;
int64 now;
- arena_start = runtime·mheap.arena_start;
now = runtime·nanotime();
-
for(;;) {
s = work.spans;
if(s == nil)
@@ -750,69 +745,82 @@ sweep(void)
if(s->state != MSpanInUse)
continue;
- p = (byte*)(s->start << PageShift);
- cl = s->sizeclass;
- if(cl == 0) {
- size = s->npages<<PageShift;
- n = 1;
- } else {
- // Chunk full of small blocks.
- size = runtime·class_to_size[cl];
- npages = runtime·class_to_allocnpages[cl];
- n = (npages << PageShift) / size;
- }
+ sweepspan(s);
+ }
+}
- // Sweep through n objects of given size starting at p.
- // This thread owns the span now, so it can manipulate
- // the block bitmap without atomic operations.
- for(; n > 0; n--, p += size) {
- uintptr off, *bitp, shift, bits;
+static void
+sweepspan(MSpan *s)
+{
+\tint32 cl, n, npages;
+\tuintptr size;
+\tbyte *p;
+\tMCache *c;
+\tbyte *arena_start;
- off = (uintptr*)p - (uintptr*)arena_start;
- bitp = (uintptr*)arena_start - off/wordsPerBitmapWord - 1;
- shift = off % wordsPerBitmapWord;
- bits = *bitp>>shift;
+ arena_start = runtime·mheap.arena_start;
+\tp = (byte*)(s->start << PageShift);\n+\tcl = s->sizeclass;
+\tif(cl == 0) {
+\t\tsize = s->npages<<PageShift;
+\t\tn = 1;
+\t} else {
+\t\t// Chunk full of small blocks.
+\t\tsize = runtime·class_to_size[cl];
+\t\tnpages = runtime·class_to_allocnpages[cl];
+\t\tn = (npages << PageShift) / size;
+\t}
- if((bits & bitAllocated) == 0)
- continue;
+ // Sweep through n objects of given size starting at p.
+ // This thread owns the span now, so it can manipulate
+ // the block bitmap without atomic operations.
+ for(; n > 0; n--, p += size) {
+\t\tuintptr off, *bitp, shift, bits;
- if((bits & bitMarked) != 0) {
- if(DebugMark) {
- if(!(bits & bitSpecial))
- runtime·printf("found spurious mark on %p\n", p);
- *bitp &= ~(bitSpecial<<shift);
- }
- *bitp &= ~(bitMarked<<shift);
- continue;
- }
+\t\toff = (uintptr*)p - (uintptr*)arena_start;
+\t\tbitp = (uintptr*)arena_start - off/wordsPerBitmapWord - 1;
+\t\tshift = off % wordsPerBitmapWord;
+\t\tbits = *bitp>>shift;
- // Special means it has a finalizer or is being profiled.
- // In DebugMark mode, the bit has been coopted so
- // we have to assume all blocks are special.
- if(DebugMark || (bits & bitSpecial) != 0) {
- if(handlespecial(p, size))
- continue;
- }
+\t\tif((bits & bitAllocated) == 0)
+\t\t\tcontinue;
- // Mark freed; restore block boundary bit.
- *bitp = (*bitp & ~(bitMask<<shift)) | (bitBlockBoundary<<shift);
+\t\tif((bits & bitMarked) != 0) {
+\t\t\tif(DebugMark) {
+\t\t\t\tif(!(bits & bitSpecial))
+\t\t\t\t\truntime·printf("found spurious mark on %p\\n", p);
+\t\t\t\t*bitp &= ~(bitSpecial<<shift);
+\t\t\t}
+\t\t\t*bitp &= ~(bitMarked<<shift);
+\t\t\tcontinue;
+\t\t}
- c = m->mcache;
- if(s->sizeclass == 0) {
- // Free large span.
- runtime·unmarkspan(p, 1<<PageShift);
- *(uintptr*)p = 1; // needs zeroing
- runtime·MHeap_Free(&runtime·mheap, s, 1);
- } else {
- // Free small object.
- if(size > sizeof(uintptr))
- ((uintptr*)p)[1] = 1; // mark as "needs to be zeroed"
- c->local_by_size[s->sizeclass].nfree++;
- runtime·MCache_Free(c, p, s->sizeclass, size);
- }
- c->local_alloc -= size;
- c->local_nfree++;
+\t\t// Special means it has a finalizer or is being profiled.
+\t\t// In DebugMark mode, the bit has been coopted so
+\t\t// we have to assume all blocks are special.
+\t\tif(DebugMark || (bits & bitSpecial) != 0) {
+\t\t\tif(handlespecial(p, size))
+\t\t\t\tcontinue;
+\t\t}
- }
+\t\t// Mark freed; restore block boundary bit.
+\t\t*bitp = (*bitp & ~(bitMask<<shift)) | (bitBlockBoundary<<shift);
+\n+\t\tc = m->mcache;
+\t\tif(s->sizeclass == 0) {
+\t\t\t// Free large span.
+\t\t\truntime·unmarkspan(p, 1<<PageShift);
+\t\t\t*(uintptr*)p = 1;\t// needs zeroing
+\t\t\truntime·MHeap_Free(&runtime·mheap, s, 1);
+\t\t} else {
+\t\t\t// Free small object.
+\t\t\tif(size > sizeof(uintptr))
+\t\t\t\t((uintptr*)p)[1] = 1;\t// mark as "needs to be zeroed"
+\t\t\tc->local_by_size[s->sizeclass].nfree++;
+\t\t\truntime·MCache_Free(c, p, s->sizeclass, size);
+\t\t}
+\t\tc->local_alloc -= size;
+\t\tc->local_nfree++;
+\t}
}
コアとなるコードの解説
sweep
関数の変更
変更前:
sweep
関数は、work.spans
リストをイテレートし、各 MSpan
に対して直接スイープ処理を行っていました。この処理には、MSpan
のサイズクラスに応じたオブジェクトサイズの計算、各オブジェクトのマークビットのチェックとクリア、ガベージの解放(MCache_Free
や MHeap_Free
の呼び出し)、ファイナライザの処理などが含まれていました。
変更後:
sweep
関数は大幅に簡素化されました。work.spans
リストをイテレートするループはそのままですが、各 MSpan
s
に対して、新しく抽出された sweepspan(s);
を呼び出すだけになりました。これにより、sweep
関数は「どの MSpan
をスイープするか」という高レベルな制御に特化し、「どのようにスイープするか」という詳細なロジックは sweepspan
に委譲されました。
sweepspan
関数の追加
新しく追加された static void sweepspan(MSpan *s)
関数は、単一の MSpan
s
を引数に取り、その MSpan
内のすべてのメモリブロックに対してスイープ処理を実行します。
この関数は、元の sweep
関数から移動してきた以下の主要なロジックを含んでいます。
MSpan
の情報取得:MSpan
のsizeclass
に基づいて、オブジェクトのサイズ (size
) とMSpan
内のオブジェクト数 (n
) を計算します。- オブジェクトの走査:
for(; n > 0; n--, p += size)
ループを使って、MSpan
内の各オブジェクトを走査します。 - マークビットのチェックとクリア: 各オブジェクトのメモリブロックに対応するビットマップを操作し、
bitAllocated
、bitMarked
、bitSpecial
などのビットをチェックします。bitMarked
が設定されている場合(オブジェクトがまだ使用されている場合)、bitMarked
をクリアして次のGCサイクルに備えます。bitMarked
が設定されていない場合(オブジェクトがガベージである場合)、handlespecial
を呼び出してファイナライザの処理などを行います。
- メモリの解放: ガベージと判断されたオブジェクトは、そのサイズクラスに応じて
runtime·MCache_Free
(スモールオブジェクトの場合)またはruntime·MHeap_Free
(ラージオブジェクトの場合)を呼び出して解放されます。また、解放されたメモリブロックの先頭にゼロを書き込むことで、再利用時の安全性を確保しています。 MCache
の更新: ローカルキャッシュm->mcache
の統計情報(local_alloc
,local_nfree
)を更新します。
この分離により、sweepspan
は MSpan
レベルでのスイープ処理の完全なカプセル化を提供します。これは、将来的に複数のスレッドが異なる MSpan
を並行してスイープする際に、各スレッドが独立して sweepspan
を呼び出すことができるため、並行GCの実装を容易にします。
関連リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事(当時の情報にアクセスするのは難しい場合がありますが、GoのGCの進化を追うことで理解が深まります)。
- Goのランタイムソースコード(特に
src/runtime/mgc.go
やsrc/runtime/mheap.go
など、メモリ管理とGCに関連するファイル)。
参考にした情報源リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Goのガベージコレクションに関する一般的な情報源(例: GoのGCの歴史や進化に関するブログ記事、論文など)。
- Goのランタイムの内部構造に関する技術解説記事。
- Goのコードレビューシステム (Gerrit) のCL (Change List) リンク: https://golang.org/cl/5991047 (これはコミットメッセージに記載されているもので、当時のコードレビューの議論を辿るのに役立ちます)
[インデックス 12844] ファイルの概要
このコミットは、Go言語のランタイムにおけるガベージコレクション(GC)のコードベースに対するマイナーなリファクタリングを導入しています。具体的には、sweep
関数から sweepspan
関数を分離し、将来的な並行GCの実装に向けた準備を行っています。論理的な変更は含まれておらず、コードの構造改善が主目的です。
コミット
commit 9903d6870f75f7c174ceb1bb8ea67e303920c8e5
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Apr 5 21:02:20 2012 +0400
runtime: minor refactoring in preparation for parallel GC
factor sweepspan() out of sweep(), no logical changes
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5991047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9903d6870f75f7c174ceb1bb8ea67e303920c8e5
元コミット内容
runtime: minor refactoring in preparation for parallel GC
factor sweepspan() out of sweep(), no logical changes
変更の背景
このコミットの主な背景は、Go言語のガベージコレクション(GC)メカニズムを将来的に並行化するための準備です。当時のGoのGCは、主にストップ・ザ・ワールド(STW)方式を採用しており、GC実行中にアプリケーションの実行が一時停止する時間が存在しました。このSTW時間を短縮し、GCの効率とスループットを向上させるために、並行GCへの移行が計画されていました。
並行GCを導入するためには、既存のGCコードベースをモジュール化し、並行処理に適した粒度で関数を分割する必要があります。sweep
関数はGCの「スイープフェーズ」を担当する重要な関数であり、その内部で複数の処理(メモリ領域の走査、マークビットのクリア、解放処理など)を行っていました。この複雑な関数をより小さな、独立した関数に分割することで、将来的に各処理を並行して実行しやすくするための基盤を築くことが目的でした。
このリファクタリングは、直接的な機能変更やバグ修正ではなく、コードの保守性、可読性、そして将来の拡張性を高めるための「内部品質改善」の一環として行われました。
前提知識の解説
ガベージコレクション (GC)
ガベージコレクションは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されなくなった(到達不能になった)領域を自動的に解放し、再利用可能にする仕組みです。これにより、プログラマは手動でのメモリ管理から解放され、メモリリークなどの問題を軽減できます。
Go言語のGCは、主に以下のフェーズで構成されます(当時のバージョンにおける一般的な概念):
- マークフェーズ (Mark Phase): プログラムが使用しているオブジェクト(到達可能なオブジェクト)を特定し、マーク(印付け)します。GoのGCは、通常、ルート(グローバル変数、スタック上の変数など)から到達可能なオブジェクトを辿ってマークします。
- スイープフェーズ (Sweep Phase): マークされなかったオブジェクト(到達不能なオブジェクト、つまりガベージ)をメモリから解放し、再利用可能な状態にします。このフェーズでは、マークビットをクリアして次のGCサイクルに備える作業も行われます。
ストップ・ザ・ワールド (Stop-The-World, STW)
STWは、GCの特定のフェーズ(特にマークフェーズの初期やスイープフェーズの一部)において、アプリケーションの実行を完全に一時停止させるGC方式です。これにより、GCがメモリの状態を安全かつ一貫性のある形で操作できるようになります。しかし、STW時間はアプリケーションの応答性やスループットに直接影響を与えるため、GCの進化はSTW時間の短縮を目指す傾向にあります。
並行GC (Concurrent GC)
並行GCは、GC処理の一部または大部分を、アプリケーションの実行と並行して(同時に)行うGC方式です。これにより、STW時間を大幅に短縮し、アプリケーションの停止時間を最小限に抑えることができます。並行GCの実装は複雑であり、GCとアプリケーションが同時にメモリを操作する際のデータ競合や一貫性の問題を解決するための高度なアルゴリズムが必要となります。
MSpan
と MCache
Goのランタイムにおけるメモリ管理は、MSpan
と MCache
という概念に基づいています。
MSpan
: 連続したページ(メモリブロックの単位)の集合を表します。Goのヒープは、これらのMSpan
の集まりとして管理されます。MSpan
は、特定のサイズのオブジェクト(スモールオブジェクト、ラージオブジェクト)を格納するために使用されます。MCache
: 各M(OSスレッドに対応するGoの論理プロセッサ)にローカルなキャッシュです。これにより、頻繁に割り当てられるスモールオブジェクトの割り当てと解放を高速化し、グローバルなヒープロックの競合を減らします。
mgc0.c
src/pkg/runtime/mgc0.c
は、Goランタイムのガベージコレクションの初期実装(または主要な部分)を含むC言語のソースファイルです。Goランタイムの一部は、パフォーマンスと低レベルのメモリ操作のためにC言語で書かれていました(後にGo言語自体で書き直される部分も増えます)。
技術的詳細
このコミットは、src/pkg/runtime/mgc0.c
ファイル内の sweep
関数から、sweepspan
という新しい静的関数を抽出しています。
元の sweep
関数は、GCのスイープフェーズにおいて、ヒープ全体を走査し、マークされていないメモリブロックを解放する役割を担っていました。この処理は、MSpan
(メモリ領域の単位)ごとにループし、各MSpan
内のオブジェクトを個別に処理していました。
抽出された sweepspan
関数は、単一の MSpan
を受け取り、その MSpan
内のすべてのオブジェクトに対してスイープ処理(マークビットのクリア、ガベージの解放、ファイナライザの処理など)を実行します。
この変更により、sweep
関数は、MSpan
のリストをイテレートし、各 MSpan
に対して sweepspan
を呼び出すという、より高レベルなロジックに簡素化されました。これにより、sweep
関数自体の複雑性が軽減され、各 MSpan
の処理が独立した関数としてカプセル化されました。
このリファクタリングは、並行GCの文脈で非常に重要です。将来的に、複数のゴルーチン(またはOSスレッド)が並行してスイープ処理を行う場合、各ゴルーチンが独立した MSpan
を取得し、その MSpan
に対して sweepspan
を並行して呼び出すことが可能になります。これにより、スイープフェーズ全体のスループットが向上し、STW時間の短縮に貢献できます。
コードの変更点を見ると、sweep
関数から、cl
, n
, npages
, size
, p
, c
, arena_start
といったローカル変数の宣言と、for(; n > 0; n--, p += size)
ループ内の処理が sweepspan
関数に移動していることがわかります。これにより、sweep
関数は for
ループ内で sweepspan(s);
を呼び出すだけのシンプルな構造になっています。
コアとなるコードの変更箇所
変更は src/pkg/runtime/mgc0.c
ファイルに集中しています。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -719,22 +719,17 @@ handlespecial(byte *p, uintptr size)
return true;
}
+static void sweepspan(MSpan *s);
+
// Sweep frees or collects finalizers for blocks not marked in the mark phase.
// It clears the mark bits in preparation for the next GC round.
static void
sweep(void)
{
MSpan *s;
- int32 cl, n, npages;
- uintptr size;
- byte *p;
- MCache *c;
- byte *arena_start;
int64 now;
- arena_start = runtime·mheap.arena_start;
now = runtime·nanotime();
-
for(;;) {
s = work.spans;
if(s == nil)
@@ -750,69 +745,82 @@ sweep(void)
if(s->state != MSpanInUse)
continue;
- p = (byte*)(s->start << PageShift);
- cl = s->sizeclass;
- if(cl == 0) {
- size = s->npages<<PageShift;
- n = 1;
- } else {
- // Chunk full of small blocks.
- size = runtime·class_to_size[cl];
- npages = runtime·class_to_allocnpages[cl];
- n = (npages << PageShift) / size;
- }
+ sweepspan(s);
+ }
+}
- // Sweep through n objects of given size starting at p.
- // This thread owns the span now, so it can manipulate
- // the block bitmap without atomic operations.
- for(; n > 0; n--, p += size) {
- uintptr off, *bitp, shift, bits;
+static void
+sweepspan(MSpan *s)
+{
+\tint32 cl, n, npages;
+\tuintptr size;
+\tbyte *p;
+\tMCache *c;
+\tbyte *arena_start;
- off = (uintptr*)p - (uintptr*)arena_start;
- bitp = (uintptr*)arena_start - off/wordsPerBitmapWord - 1;
- shift = off % wordsPerBitmapWord;
- bits = *bitp>>shift;
+ arena_start = runtime·mheap.arena_start;
+\tp = (byte*)(s->start << PageShift);\n+\tcl = s->sizeclass;
+\tif(cl == 0) {
+\t\tsize = s->npages<<PageShift;
+\t\tn = 1;
+\t} else {
+\t\t// Chunk full of small blocks.
+\t\tsize = runtime·class_to_size[cl];
+\t\tnpages = runtime·class_to_allocnpages[cl];
+\t\tn = (npages << PageShift) / size;
+\t}
- if((bits & bitAllocated) == 0)
- continue;
+ // Sweep through n objects of given size starting at p.
+ // This thread owns the span now, so it can manipulate
+ // the block bitmap without atomic operations.
+ for(; n > 0; n--, p += size) {
+\t\tuintptr off, *bitp, shift, bits;
- if((bits & bitMarked) != 0) {
- if(DebugMark) {
- if(!(bits & bitSpecial))
- runtime·printf("found spurious mark on %p\n", p);
- *bitp &= ~(bitSpecial<<shift);
- }
- *bitp &= ~(bitMarked<<shift);
- continue;
- }
+\t\toff = (uintptr*)p - (uintptr*)arena_start;
+\t\tbitp = (uintptr*)arena_start - off/wordsPerBitmapWord - 1;
+\t\tshift = off % wordsPerBitmapWord;
+\t\tbits = *bitp>>shift;
- // Special means it has a finalizer or is being profiled.
- // In DebugMark mode, the bit has been coopted so
- // we have to assume all blocks are special.
- if(DebugMark || (bits & bitSpecial) != 0) {
- if(handlespecial(p, size))
- continue;
- }
+\t\tif((bits & bitAllocated) == 0)
+\t\t\tcontinue;
- // Mark freed; restore block boundary bit.
- *bitp = (*bitp & ~(bitMask<<shift)) | (bitBlockBoundary<<shift);
+\t\tif((bits & bitMarked) != 0) {
+\t\t\tif(DebugMark) {
+\t\t\t\tif(!(bits & bitSpecial))
+\t\t\t\t\truntime·printf("found spurious mark on %p\\n", p);
+\t\t\t\t*bitp &= ~(bitSpecial<<shift);
+\t\t\t}
+\t\t\t*bitp &= ~(bitMarked<<shift);
+\t\t\tcontinue;
+\t\t}
- c = m->mcache;
- if(s->sizeclass == 0) {
- // Free large span.
- runtime·unmarkspan(p, 1<<PageShift);
- *(uintptr*)p = 1; // needs zeroing
- runtime·MHeap_Free(&runtime·mheap, s, 1);
- } else {
- // Free small object.
- if(size > sizeof(uintptr))
- ((uintptr*)p)[1] = 1; // mark as "needs to be zeroed"
- c->local_by_size[s->sizeclass].nfree++;
- runtime·MCache_Free(c, p, s->sizeclass, size);
- }
- c->local_alloc -= size;
- c->local_nfree++;
+\t\t// Special means it has a finalizer or is being profiled.
+\t\t// In DebugMark mode, the bit has been coopted so
+\t\t// we have to assume all blocks are special.
+\t\tif(DebugMark || (bits & bitSpecial) != 0) {
+\t\t\tif(handlespecial(p, size))
+\t\t\t\tcontinue;
+\t\t}
- }
+\t\t// Mark freed; restore block boundary bit.
+\t\t*bitp = (*bitp & ~(bitMask<<shift)) | (bitBlockBoundary<<shift);
+\n+\t\tc = m->mcache;
+\t\tif(s->sizeclass == 0) {
+\t\t\t// Free large span.
+\t\t\truntime·unmarkspan(p, 1<<PageShift);
+\t\t\t*(uintptr*)p = 1;\t// needs zeroing
+\t\t\truntime·MHeap_Free(&runtime·mheap, s, 1);
+\t\t} else {
+\t\t\t// Free small object.
+\t\t\tif(size > sizeof(uintptr))
+\t\t\t\t((uintptr*)p)[1] = 1;\t// mark as "needs to be zeroed"
+\t\t\tc->local_by_size[s->sizeclass].nfree++;
+\t\t\truntime·MCache_Free(c, p, s->sizeclass, size);
+\t\t}
+\t\tc->local_alloc -= size;
+\t\tc->local_nfree++;
+\t}
}
コアとなるコードの解説
sweep
関数の変更
変更前:
sweep
関数は、work.spans
リストをイテレートし、各 MSpan
に対して直接スイープ処理を行っていました。この処理には、MSpan
のサイズクラスに応じたオブジェクトサイズの計算、各オブジェクトのマークビットのチェックとクリア、ガベージの解放(MCache_Free
や MHeap_Free
の呼び出し)、ファイナライザの処理などが含まれていました。
変更後:
sweep
関数は大幅に簡素化されました。work.spans
リストをイテレートするループはそのままですが、各 MSpan
s
に対して、新しく抽出された sweepspan(s);
を呼び出すだけになりました。これにより、sweep
関数は「どの MSpan
をスイープするか」という高レベルな制御に特化し、「どのようにスイープするか」という詳細なロジックは sweepspan
に委譲されました。
sweepspan
関数の追加
新しく追加された static void sweepspan(MSpan *s)
関数は、単一の MSpan
s
を引数に取り、その MSpan
内のすべてのメモリブロックに対してスイープ処理を実行します。
この関数は、元の sweep
関数から移動してきた以下の主要なロジックを含んでいます。
MSpan
の情報取得:MSpan
のsizeclass
に基づいて、オブジェクトのサイズ (size
) とMSpan
内のオブジェクト数 (n
) を計算します。- オブジェクトの走査:
for(; n > 0; n--, p += size)
ループを使って、MSpan
内の各オブジェクトを走査します。 - マークビットのチェックとクリア: 各オブジェクトのメモリブロックに対応するビットマップを操作し、
bitAllocated
、bitMarked
、bitSpecial
などのビットをチェックします。bitMarked
が設定されている場合(オブジェクトがまだ使用されている場合)、bitMarked
をクリアして次のGCサイクルに備えます。bitMarked
が設定されていない場合(オブジェクトがガベージである場合)、handlespecial
を呼び出してファイナライザの処理などを行います。
- メモリの解放: ガベージと判断されたオブジェクトは、そのサイズクラスに応じて
runtime·MCache_Free
(スモールオブジェクトの場合)またはruntime·MHeap_Free
(ラージオブジェクトの場合)を呼び出して解放されます。また、解放されたメモリブロックの先頭にゼロを書き込むことで、再利用時の安全性を確保しています。 MCache
の更新: ローカルキャッシュm->mcache
の統計情報(local_alloc
,local_nfree
)を更新します。
この分離により、sweepspan
は MSpan
レベルでのスイープ処理の完全なカプセル化を提供します。これは、将来的に複数のスレッドが異なる MSpan
を並行してスイープする際に、各スレッドが独立して sweepspan
を呼び出すことができるため、並行GCの実装を容易にします。
関連リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事(当時の情報にアクセスするのは難しい場合がありますが、GoのGCの進化を追うことで理解が深まります)。
- Goのランタイムソースコード(特に
src/runtime/mgc.go
やsrc/runtime/mheap.go
など、メモリ管理とGCに関連するファイル)。
参考にした情報源リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Goのガベージコレクションに関する一般的な情報源(例: GoのGCの歴史や進化に関するブログ記事、論文など)。
- Goのランタイムの内部構造に関する技術解説記事。
- Goのコードレビューシステム (Gerrit) のCL (Change List) リンク: https://golang.org/cl/5991047 (これはコミットメッセージに記載されているもので、当時のコードレビューの議論を辿るのに役立ちます)