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

[インデックス 17954] ファイルの概要

このコミットは、Go言語のランタイムにおけるガベージコレクション(GC)のマークフェーズに関連する src/pkg/runtime/mgc0.c ファイルに対する変更です。mgc0.c は、Goランタイムのマーク&スイープ型ガベージコレクタの主要な部分を構成しており、特にオブジェクトの到達可能性を追跡し、マークする処理を担当しています。このファイルは、GCの効率性と正確性に直接影響を与える低レベルのメモリ管理ロジックを含んでいます。

コミット

このコミットは、Goランタイムのガベージコレクタコードから、古くなったコメントとそれに関連するインデントを削除することを目的としています。これにより、コードの可読性が向上し、もはや存在しない、または計画が変更された機能に関する誤解を招く情報が排除されます。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c69402d82b5860df253b77ad07b6aa572c9f87cd

元コミット内容

runtime: remove outdated comment and related indentation

R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/39810043

変更の背景

この変更の背景には、Goランタイムのガベージコレクタの進化があります。削除されたコメント // TODO(atom): This block is a branch of an if-then-else statement. // The single-threaded branch may be added in a next CL. は、かつてこのコードブロックが、シングルスレッド版とマルチスレッド版のGC処理を切り替える if-then-else 文の一部として計画されていたことを示唆しています。しかし、GoのGCは並行処理と並列処理を重視して設計されており、特定の時点でシングルスレッドのGCパスが不要になったか、あるいはその計画が変更されたと考えられます。

このコメントが残っていると、開発者がコードを読んだ際に、存在しないシングルスレッドのGCパスを探したり、コードの意図を誤解したりする可能性があります。また、コメントによって導入された余分なインデントは、コードの構造を不必要に複雑に見せていました。このコミットは、このような古くなった、あるいは誤解を招く情報を削除し、コードベースをクリーンアップすることで、保守性と可読性を向上させるためのものです。これは、大規模なプロジェクトにおける継続的なコード品質改善の一環として行われる典型的なクリーンアップ作業です。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムとガベージコレクションに関する前提知識が必要です。

  • Go言語のガベージコレクション (GC) の基本: Goは自動メモリ管理を採用しており、ガベージコレクタが不要になったメモリを自動的に解放します。GoのGCは、並行(concurrent)かつ低遅延(low-latency)であることを目標として設計されています。これは、GCがアプリケーションの実行と並行して動作し、アプリケーションの実行を長時間停止させないことを意味します。
  • マーク&スイープGCのマークフェーズ: GoのGCは基本的にマーク&スイープアルゴリズムを採用しています。
    • マークフェーズ: GCのルート(グローバル変数、スタック上の変数など)から到達可能なすべてのオブジェクトを「生きている(live)」とマークします。このフェーズでは、オブジェクトグラフをトラバースしてポインタをたどります。
    • スイープフェーズ: マークされなかった(到達不可能な)オブジェクトが占めるメモリを解放し、再利用可能にします。 mgc0.c は主にこのマークフェーズ、特にポインタの走査とオブジェクトのマークに関連する処理を担当します。
  • Goランタイムにおけるメモリ管理の概念:
    • mheap: Goランタイムが管理するヒープ全体を表す構造体です。
    • spans: ヒープは「スパン(span)」と呼ばれる連続したメモリブロックに分割されます。各スパンは特定のサイズのオブジェクトを格納するために使用されます。
    • arena: ヒープの仮想アドレス空間の領域を指します。
  • ビットマップ: GoのGCは、メモリ上のオブジェクトの状態(割り当て済み、マーク済み、ポインタの有無など)を効率的に管理するためにビットマップを使用します。
    • bitAllocated: メモリがオブジェクトに割り当てられていることを示すビット。
    • bitMarked: オブジェクトがGCのマークフェーズで到達可能とマークされたことを示すビット。
    • bitBlockBoundary: オブジェクトの開始位置を示すビット。
    • bitNoScan: オブジェクトがポインタを含まない(スキャン不要な)ことを示すビット。
  • flushptrbuf 関数の目的: flushptrbuf は、GCのマークフェーズ中に見つかったポインタ(オブジェクトへの参照)を処理するためのバッファ(ptrbuf)をフラッシュ(処理)する関数です。この関数は、バッファ内のポインタを一つずつ取り出し、それが指すオブジェクトをマークし、必要に応じてそのオブジェクトが参照する他のオブジェクトをスキャンキューに追加します。
  • 並行処理とアトミック操作: GoのGCは並行に動作するため、複数のゴルーチン(またはOSスレッド)が同時にメモリを操作する可能性があります。
    • runtime·casp: Compare-And-Swap Pointer。アトミックな比較と交換操作で、複数のスレッドが同時に同じメモリ位置を更新しようとしたときに競合状態を防ぎます。
    • runtime·xadd64: アトミックな加算操作。カウンタなどを複数のスレッドから安全に更新するために使用されます。 これらのアトミック操作は、GCのマークビットを安全に設定するために不可欠です。
  • その他の関連概念:
    • PageShift: メモリページサイズに関連するシフト値。
    • PtrSize: ポインタのサイズ(通常4バイトまたは8バイト)。
    • arena_start: メモリアリーナの開始アドレス。
    • wordsPerBitmapWord: ビットマップの1ワードあたりのビット数。
    • gcstats: GCの統計情報を収集するための構造体。
    • CollectStats: GC統計の収集が有効かどうかを示すフラグ。
    • handoffThreshold: ワークバッファを他のプロセッサに引き渡す閾値。
    • work.nproc: 利用可能なプロセッサの数。
    • work.nwait: ワークを待っているプロセッサの数。
    • work.full: ワークバッファが満杯かどうかを示すフラグ。
    • Obj: オブジェクトのポインタ、サイズ、型情報を含む構造体。
    • Scanbuf, WorkBuf: GCスキャン中にポインタを一時的に保持するためのバッファ。
    • PREFETCH: CPUのキャッシュにデータを事前にロードする命令(パフォーマンス最適化のため)。

技術的詳細

flushptrbuf 関数は、GCのマークフェーズにおいて、スキャン対象のオブジェクトから見つかったポインタを処理する中心的なロジックを含んでいます。この関数は、ptrbuf というバッファに格納されたポインタを順次取り出し、それらが指すオブジェクトがまだマークされていない場合はマークし、そのオブジェクトがさらにポインタを含む場合は、それらのポインタもスキャン対象としてキューに追加します。

変更前のコードには、// TODO(atom): This block is a branch of an if-then-else statement. // The single-threaded branch may be added in a next CL. というコメントと、それに続くコードブロック全体を囲む {} とインデントがありました。これは、将来的にシングルスレッドのGCパスが導入される可能性を考慮して、コードが if-then-else 構造の「マルチスレッド版」として書かれていたことを示しています。

しかし、この if-then-else 構造は実際には実装されず、常にマルチスレッド版のロジックが実行される状態でした。そのため、コメントは古くなり、コードブロックを囲む {} とそのインデントは冗長になっていました。

このコミットでは、以下の点が技術的に重要です。

  1. デッドコメントの削除: TODO コメントは、もはや関連性のない開発計画を示しており、コードの理解を妨げていました。これを削除することで、コードベースがクリーンになり、現在の実装がより明確になります。
  2. 冗長なインデントの解消: コメントに関連して導入されていた余分なインデントが削除されました。これにより、flushptrbuf 関数の主要なループ(while(ptrbuf < ptrbuf_end))のスコープが明確になり、コードの視覚的な構造が改善されます。インデントの修正は、コードの論理的な構造を正確に反映させる上で重要です。
  3. コードの簡素化: 見た目上は小さな変更ですが、このようなクリーンアップは、特に低レベルのランタイムコードにおいて、将来のメンテナンスやデバッグの際に大きな影響を与えます。不要な複雑さを取り除くことで、開発者がコードの真の意図をより迅速に把握できるようになります。

flushptrbuf 内のロジックは、ポインタが指すアドレスからオブジェクトの先頭を見つけ出し、そのオブジェクトのビットマップ情報を読み取り、マークビットを設定するという複雑な処理を含んでいます。この処理は、オブジェクトがヒープのどこに割り当てられているか(mheap.arena_startmheap.spans)、そしてそのオブジェクトがポインタを含むかどうか(bitNoScan)を判断するために、様々なビット操作やメモリマップのルックアップ(MHeap_LookupMaybe のインライン化されたコピー)を行います。並行GCの文脈では、runtime·casp を用いてマークビットをアトミックに設定することで、複数のGCワーカーが同時に同じオブジェクトをマークしようとした際の競合を防いでいます。

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

変更は src/pkg/runtime/mgc0.c ファイルの flushptrbuf 関数内で行われています。

--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -409,131 +409,125 @@ flushptrbuf(Scanbuf *sbuf)
 	\t\truntime·throw("ptrbuf has to be smaller than WorkBuf");
 	}\n
 \n-\t// TODO(atom): This block is a branch of an if-then-else statement.\n-\t//             The single-threaded branch may be added in a next CL.\n-\t{\n-\t\t// Multi-threaded version.\n-\n-\t\twhile(ptrbuf < ptrbuf_end) {\n-\t\t\tobj = ptrbuf->p;\n-\t\t\tti = ptrbuf->ti;\n-\t\t\tptrbuf++;\n-\n-\t\t\t// obj belongs to interval [mheap.arena_start, mheap.arena_used).\n-\t\t\tif(Debug > 1) {\n-\t\t\t\tif(obj < runtime·mheap.arena_start || obj >= runtime·mheap.arena_used)\n-\t\t\t\t\truntime·throw("object is outside of mheap");\n-\t\t\t}\n+\twhile(ptrbuf < ptrbuf_end) {\n+\t\tobj = ptrbuf->p;\n+\t\tti = ptrbuf->ti;\n+\t\tptrbuf++;\n \n-\t\t\t// obj may be a pointer to a live object.\n-\t\t\t// Try to find the beginning of the object.\n+\t\t// obj belongs to interval [mheap.arena_start, mheap.arena_used).\n+\t\tif(Debug > 1) {\n+\t\t\tif(obj < runtime·mheap.arena_start || obj >= runtime·mheap.arena_used)\n+\t\t\t\truntime·throw("object is outside of mheap");\n+\t\t}\n \n-\t\t\t// Round down to word boundary.\n-\t\t\tif(((uintptr)obj & ((uintptr)PtrSize-1)) != 0) {\n-\t\t\t\tobj = (void*)((uintptr)obj & ~((uintptr)PtrSize-1));\n-\t\t\t\tti = 0;\n-\t\t\t}\n+\t\t// obj may be a pointer to a live object.\n+\t\t// Try to find the beginning of the object.\n \n-\t\t\t// Find bits for this word.\n-\t\t\toff = (uintptr*)obj - (uintptr*)arena_start;\n-\t\t\tbitp = (uintptr*)arena_start - off/wordsPerBitmapWord - 1;\n-\t\t\tshift = off % wordsPerBitmapWord;\n-\t\t\txbits = *bitp;\n-\t\t\tbits = xbits >> shift;\n+\t\t// Round down to word boundary.\n+\t\tif(((uintptr)obj & ((uintptr)PtrSize-1)) != 0) {\n+\t\t\tobj = (void*)((uintptr)obj & ~((uintptr)PtrSize-1));\n+\t\t\tti = 0;\n+\t\t}\n \n-\t\t\t// Pointing at the beginning of a block?\n-\t\t\tif((bits & (bitAllocated|bitBlockBoundary)) != 0) {\n-\t\t\t\tif(CollectStats)\n-\t\t\t\t\truntime·xadd64(&gcstats.flushptrbuf.foundbit, 1);\n-\t\t\t\tgoto found;\n-\t\t\t}\n+\t\t// Find bits for this word.\n+\t\toff = (uintptr*)obj - (uintptr*)arena_start;\n+\t\tbitp = (uintptr*)arena_start - off/wordsPerBitmapWord - 1;\n+\t\tshift = off % wordsPerBitmapWord;\n+\t\txbits = *bitp;\n+\t\tbits = xbits >> shift;\n \n-\t\t\tti = 0;\n+\t\t// Pointing at the beginning of a block?\n+\t\tif((bits & (bitAllocated|bitBlockBoundary)) != 0) {\n+\t\t\tif(CollectStats)\n+\t\t\t\truntime·xadd64(&gcstats.flushptrbuf.foundbit, 1);\n+\t\t\t\tgoto found;\n+\t\t}\n \n-\t\t\t// Pointing just past the beginning?\n-\t\t\t// Scan backward a little to find a block boundary.\n-\t\t\tfor(j=shift; j-->0; ) {\n-\t\t\t\tif(((xbits>>j) & (bitAllocated|bitBlockBoundary)) != 0) {\n-\t\t\t\t\tobj = (byte*)obj - (shift-j)*PtrSize;\n-\t\t\t\t\tshift = j;\n-\t\t\t\t\tbits = xbits>>shift;\n-\t\t\t\t\tif(CollectStats)\n-\t\t\t\t\t\truntime·xadd64(&gcstats.flushptrbuf.foundword, 1);\n-\t\t\t\t\tgoto found;\n-\t\t\t\t}\n-\t\t\t}\n+\t\tti = 0;\n \n-\t\t\t// Otherwise consult span table to find beginning.\n-\t\t\t// (Manually inlined copy of MHeap_LookupMaybe.)\n-\t\t\tk = (uintptr)obj>>PageShift;\n-\t\t\tx = k;\n-\t\t\tif(sizeof(void*) == 8)\n-\t\t\t\tx -= (uintptr)arena_start>>PageShift;\n-\t\t\ts = runtime·mheap.spans[x];\n-\t\t\tif(s == nil || k < s->start || obj >= s->limit || s->state != MSpanInUse)\n-\t\t\t\tcontinue;\n-\t\t\tp = (byte*)((uintptr)s->start<<PageShift);\n-\t\t\tif(s->sizeclass == 0) {\n-\t\t\t\tobj = p;\n-\t\t\t} else {\n-\t\t\t\tsize = s->elemsize;\n-\t\t\t\tint32 i = ((byte*)obj - p)/size;\n-\t\t\t\tobj = p+i*size;\n+\t\t// Pointing just past the beginning?\n+\t\t// Scan backward a little to find a block boundary.\n+\t\tfor(j=shift; j-->0; ) {\n+\t\t\tif(((xbits>>j) & (bitAllocated|bitBlockBoundary)) != 0) {\n+\t\t\t\tobj = (byte*)obj - (shift-j)*PtrSize;\n+\t\t\t\tshift = j;\n+\t\t\t\tbits = xbits>>shift;\n+\t\t\t\tif(CollectStats)\n+\t\t\t\t\truntime·xadd64(&gcstats.flushptrbuf.foundword, 1);\n+\t\t\t\tgoto found;\n \t\t\t}\n+\t\t}\n \n-\t\t\t// Now that we know the object header, reload bits.\n-\t\t\toff = (uintptr*)obj - (uintptr*)arena_start;\n-\t\t\tbitp = (uintptr*)arena_start - off/wordsPerBitmapWord - 1;\n-\t\t\tshift = off % wordsPerBitmapWord;\n-\t\t\txbits = *bitp;\n-\t\t\tbits = xbits >> shift;\n-\t\t\tif(CollectStats)\n-\t\t\t\truntime·xadd64(&gcstats.flushptrbuf.foundspan, 1);\n+\t\t// Otherwise consult span table to find beginning.\n+\t\t// (Manually inlined copy of MHeap_LookupMaybe.)\n+\t\tk = (uintptr)obj>>PageShift;\n+\t\tx = k;\n+\t\tif(sizeof(void*) == 8)\n+\t\t\tx -= (uintptr)arena_start>>PageShift;\n+\t\ts = runtime·mheap.spans[x];\n+\t\tif(s == nil || k < s->start || obj >= s->limit || s->state != MSpanInUse)\n+\t\t\tcontinue;\n+\t\tp = (byte*)((uintptr)s->start<<PageShift);\n+\t\tif(s->sizeclass == 0) {\n+\t\t\tobj = p;\n+\t\t} else {\n+\t\t\tsize = s->elemsize;\n+\t\t\tint32 i = ((byte*)obj - p)/size;\n+\t\t\tobj = p+i*size;\n+\t\t}\n \n-\t\tfound:\n-\t\t\t// Now we have bits, bitp, and shift correct for\n-\t\t\t// obj pointing at the base of the object.\n-\t\t\t// Only care about allocated and not marked.\n-\t\t\tif((bits & (bitAllocated|bitMarked)) != bitAllocated)\n-\t\t\t\tcontinue;\n-\t\t\tif(work.nproc == 1)\n-\t\t\t\t*bitp |= bitMarked<<shift;\n-\t\t\telse {\n-\t\t\t\tfor(;;) {\n-\t\t\t\t\tx = *bitp;\n-\t\t\t\t\tif(x & (bitMarked<<shift))\n-\t\t\t\t\t\tgoto continue_obj;\n-\t\t\t\t\tif(runtime·casp((void**)bitp, (void*)x, (void*)(x|(bitMarked<<shift))))\n-\t\t\t\t\t\tbreak;\n-\t\t\t\t}\n+\t\t// Now that we know the object header, reload bits.\n+\t\toff = (uintptr*)obj - (uintptr*)arena_start;\n+\t\tbitp = (uintptr*)arena_start - off/wordsPerBitmapWord - 1;\n+\t\tshift = off % wordsPerBitmapWord;\n+\t\txbits = *bitp;\n+\t\tbits = xbits >> shift;\n+\t\tif(CollectStats)\n+\t\t\truntime·xadd64(&gcstats.flushptrbuf.foundspan, 1);\n+\n+\tfound:\n+\t\t// Now we have bits, bitp, and shift correct for\n+\t\t// obj pointing at the base of the object.\n+\t\t// Only care about allocated and not marked.\n+\t\tif((bits & (bitAllocated|bitMarked)) != bitAllocated)\n+\t\t\tcontinue;\n+\t\tif(work.nproc == 1)\n+\t\t\t*bitp |= bitMarked<<shift;\n+\t\telse {\n+\t\t\tfor(;;) {\n+\t\t\t\tx = *bitp;\n+\t\t\t\tif(x & (bitMarked<<shift))\n+\t\t\t\t\tgoto continue_obj;\n+\t\t\t\tif(runtime·casp((void**)bitp, (void*)x, (void*)(x|(bitMarked<<shift))))\n+\t\t\t\t\t\tbreak;\n \t\t\t}\n+\t\t}\n \n-\t\t\t// If object has no pointers, don\'t need to scan further.\n-\t\t\tif((bits & bitNoScan) != 0)\n-\t\t\t\tcontinue;\n+\t\t// If object has no pointers, don\'t need to scan further.\n+\t\tif((bits & bitNoScan) != 0)\n+\t\t\tcontinue;\n \n-\t\t\t// Ask span about size class.\n-\t\t\t// (Manually inlined copy of MHeap_Lookup.)\n-\t\t\tx = (uintptr)obj >> PageShift;\n-\t\t\tif(sizeof(void*) == 8)\n-\t\t\t\tx -= (uintptr)arena_start>>PageShift;\n-\t\t\ts = runtime·mheap.spans[x];\n+\t\t// Ask span about size class.\n+\t\t// (Manually inlined copy of MHeap_Lookup.)\n+\t\tx = (uintptr)obj >> PageShift;\n+\t\tif(sizeof(void*) == 8)\n+\t\t\tx -= (uintptr)arena_start>>PageShift;\n+\t\ts = runtime·mheap.spans[x];\n \n-\t\t\tPREFETCH(obj);\n+\t\tPREFETCH(obj);\n \n-\t\t\t*wp = (Obj){obj, s->elemsize, ti};\n-\t\t\twp++;\n-\t\t\tnobj++;\n-\t\tcontinue_obj:;\n-\t\t}\n+\t\t*wp = (Obj){obj, s->elemsize, ti};\n+\t\twp++;\n+\t\tnobj++;\n+\tcontinue_obj:;\n+\t}\n \n-\t\t// If another proc wants a pointer, give it some.\n-\t\tif(work.nwait > 0 && nobj > handoffThreshold && work.full == 0) {\n-\t\t\twbuf->nobj = nobj;\n-\t\t\twbuf = handoff(wbuf);\n-\t\t\tnobj = wbuf->nobj;\n-\t\t\twp = wbuf->obj + nobj;\n-\t\t}\n+\t// If another proc wants a pointer, give it some.\n+\tif(work.nwait > 0 && nobj > handoffThreshold && work.full == 0) {\n+\t\twbuf->nobj = nobj;\n+\t\twbuf = handoff(wbuf);\n+\t\tnobj = wbuf->nobj;\n+\t\twp = wbuf->obj + nobj;\n \t}\n \n \tsbuf->wp = wp;\n```

## コアとなるコードの解説

このコミットの主要な変更は、`flushptrbuf` 関数内の以下の部分です。

1.  **コメントの削除**:
    `-	// TODO(atom): This block is a branch of an if-then-else statement.`
    `-	//             The single-threaded branch may be added in a next CL.`
    `-	{`
    `-		// Multi-threaded version.`
    これらの行が削除されました。これは、かつて計画されていたシングルスレッドGCパスのコメントと、そのために導入された冗長なブロック開始の波括弧 `{` および「Multi-threaded version.」というコメントが不要になったためです。

2.  **インデントの修正**:
    削除されたコメントと波括弧によって導入されていた、その後のコードブロック全体のインデントが解除されました。具体的には、`while(ptrbuf < ptrbuf_end)` ループとその内部のすべてのコード行が、1レベル外側にシフトされています。

    変更前は、`while(ptrbuf < ptrbuf_end)` ループが余分なインデントレベルを持っていました。変更後は、このループが `flushptrbuf` 関数の直下の適切なインデントレベルに戻されています。

この変更は、コードの機能的な動作には影響を与えません。`flushptrbuf` のロジック自体は変更されていません。これは純粋にコードのクリーンアップであり、古くなった情報を取り除き、コードの視覚的な構造を改善することで、可読性と保守性を向上させるものです。Goのランタイムコードは非常に低レベルであり、このような小さな変更でも、コードの意図を明確にすることは非常に重要です。

## 関連リンク

*   Go言語のガベージコレクションに関する公式ドキュメントやブログ記事:
    *   [Go's Garbage Collector: A Brief History - The Go Programming Language](https://go.dev/blog/go15gc) (Go 1.5 GCに関する記事ですが、Go GCの進化の背景を理解するのに役立ちます)
    *   [Go runtime source code on GitHub](https://github.com/golang/go/tree/master/src/runtime)

## 参考にした情報源リンク

*   Go言語の公式ドキュメント
*   Go言語のランタイムソースコード (特に `src/runtime/mgc.go` や `src/runtime/mgc.c` 関連ファイル)
*   Go言語のガベージコレクションに関する技術ブログや論文
*   GitHubのコミット履歴と関連するコードレビュー (CL: Change List)
*   Go言語のガベージコレクションの仕組みに関する一般的な知識
*   Go言語のランタイムにおけるメモリ管理の概念に関する一般的な知識
*   C言語におけるビット操作とポインタ演算に関する一般的な知識
*   並行プログラミングにおけるアトミック操作に関する一般的な知識
*   [Go's Garbage Collector: A Brief History - The Go Programming Language](https://go.dev/blog/go15gc) (Go 1.5 GCに関する記事ですが、Go GCの進化の背景を理解するのに役立ちました)
*   [Go runtime source code on GitHub](https://github.com/golang/go/tree/master/src/runtime) (特に `mgc0.c` の周辺コードの理解に役立ちました)
*   Go言語のガベージコレクションに関する一般的な解説記事(例: Qiita, Zennなどの技術ブログ)
*   C言語のポインタとメモリ操作に関する一般的な知識