[インデックス 14660] ファイルの概要
このコミットは、Goランタイムのガベージコレクタ(GC)の中核部分である src/pkg/runtime/mgc0.c
ファイルに大幅な変更を加えるものです。主な目的は、GCの精度を向上させるための基盤を構築することにあります。具体的には、メモリブロックを表す Obj
構造体の定義を拡張し、scanblock
関数におけるポインタ走査のメカニズムを刷新しています。これにより、将来的にオブジェクトの型情報を利用したより精密なGCを実現するための準備が行われています。
コミット
commit 013fa63c901e4a08548821bcc46393584b8c701e
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Sun Dec 16 19:32:12 2012 -0500
runtime: struct Obj in mgc0.c and buffers in scanblock()
Details:
- This CL is the conceptual skeleton of code found in CL 6114046
- The garbage collector uses struct Obj to specify memory blocks
- scanblock() is putting found memory blocks into an intermediate buffer
(xbuf) before adding/flushing them to the main work buffer (wbuf)
- The main loop in scanblock() is replaced with a skeleton code that
in the future will be able to recognize the type of objects and
thus will improve the garbage collector's precision.
For now, all objects are simply sequences of pointers so
the precision of the garbage collector remains unchanged.
- The code plugs .gcdata and .gcbss sections into the garbage collector.
scanblock() in this CL is unable to make any use of this.
R=rsc, dvyukov, remyoudompheng
CC=dave, golang-dev, minux.ma
https://golang.org/cl/6856121
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/013fa63c901e4a08548821bcc46393584b8c701e
元コミット内容
上記の「コミット」セクションに記載されている内容が元コミット内容です。
変更の背景
このコミットの背景には、Go言語のガベージコレクタの進化があります。当時のGoのGCは、主に「マーク&スイープ」方式を採用していましたが、その精度(どのメモリがポインタを含み、どのメモリがそうでないかを正確に識別する能力)には改善の余地がありました。特に、オブジェクトの内部構造に関する型情報がGCに十分に活用されていませんでした。
このコミットは、より「正確な(precise)」GCを実現するための重要なステップです。正確なGCは、ポインタではないデータ(例えば、整数や文字列のバイト列)をポインタとして誤って解釈し、到達可能なオブジェクトとしてマークしてしまう「保守的な(conservative)」GCの問題を解決します。保守的なGCは、誤ってメモリを解放しないという安全性は高いものの、不要なメモリを保持し続けることでメモリ使用量が増加する可能性があります。
この変更では、Obj
構造体に型情報(ti
フィールド)を導入し、scanblock
関数がこの型情報を利用して、メモリブロック内のどこにポインタが存在するかを正確に識別できるようにするための基盤を構築しています。また、.gcdata
および .gcbss
セクション(コンパイル時に生成される、ポインタの配置に関するメタデータを含むセクション)をGCに統合することで、プログラムの静的な型情報をGCプロセスに提供する道を開いています。
コミットメッセージに記載されている「CL 6114046の概念的な骨格」という記述は、この変更がより大きなGC改善計画の一部であることを示唆しています。ただし、このCL 6114046に関する具体的な情報は、現在の検索では見つかりませんでした。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムおよびガベージコレクションに関する前提知識が必要です。
-
Goのガベージコレクション (GC):
- GoのGCは、主に「マーク&スイープ」アルゴリズムに基づいています。これは、プログラムが使用しているオブジェクト(到達可能なオブジェクト)をマークし、マークされていないオブジェクト(到達不可能なオブジェクト、つまり不要なメモリ)を解放するプロセスです。
- マークフェーズ: ルート(グローバル変数、スタック、レジスタなど)から開始し、到達可能なすべてのオブジェクトをたどってマークします。
- スイープフェーズ: ヒープ全体を走査し、マークされていないオブジェクトを解放します。
- 並行GC: GoのGCは、アプリケーションの実行と並行して動作するように設計されており、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えることを目指しています。
-
src/pkg/runtime/mgc0.c
:- GoランタイムのC言語で書かれた部分で、ガベージコレクタの主要なロジックが含まれています。
mgc
は "memory garbage collection" を意味します。
- GoランタイムのC言語で書かれた部分で、ガベージコレクタの主要なロジックが含まれています。
-
scanblock
関数:- GCのマークフェーズにおいて、特定のメモリブロックを走査し、その中に含まれるポインタを識別して、それらが指すオブジェクトをマークするための関数です。この関数がGCの精度に直接影響を与えます。
-
Workbuf
(Work Buffer):- GCのマークフェーズ中に、走査すべきオブジェクトのポインタを一時的に格納するためのバッファです。GCワーカーゴルーチンは、このバッファからオブジェクトを取り出し、走査し、新しい到達可能なオブジェクトをバッファに追加します。
-
.gcdata
および.gcbss
セクション:- Goのコンパイラは、実行可能ファイル内に特別なセクションを生成します。
.gcdata
: データセクション(初期化されたグローバル変数など)内のポインタに関するメタデータを含みます。.gcbss
: BSSセクション(初期化されていないグローバル変数など)内のポインタに関するメタデータを含みます。- これらのセクションは、GCがプログラムの静的な部分(グローバル変数など)を正確に走査するために使用されます。
-
ポインタサイズ (
PtrSize
):- システムアーキテクチャ(32ビットまたは64ビット)に応じて、ポインタのサイズ(4バイトまたは8バイト)を定義する定数です。
-
MSpan
:- Goのメモリ管理において、ヒープメモリはページ(通常8KB)の連続したブロックである「スパン(span)」に分割されます。
MSpan
構造体は、これらのスパンに関する情報(開始アドレス、ページ数、サイズクラスなど)を保持します。
- Goのメモリ管理において、ヒープメモリはページ(通常8KB)の連続したブロックである「スパン(span)」に分割されます。
-
runtime·casp
(Compare-and-Swap Pointer):- アトミック操作の一つで、特定のメモリ位置の現在の値が期待する値と一致する場合にのみ、そのメモリ位置を新しい値で更新します。並行処理において、ロックなしで共有データを安全に操作するために使用されます。
技術的詳細
このコミットは、Goのガベージコレクタの内部構造にいくつかの重要な変更を導入しています。
-
Obj
構造体の導入とGcRoot
の置き換え:- 以前は
GcRoot
構造体がポインタとサイズ (p
,n
) を保持していましたが、これをObj
構造体に置き換えました。 Obj
構造体には新たにti
(type info) フィールドが追加されました。これはuintptr
型で、将来的にオブジェクトの型情報を格納するために使用されます。これにより、GCがオブジェクトの内部構造を理解し、どこにポインタがあるかを正確に判断できるようになります。
- 以前は
-
中間バッファ (
PtrTarget
,BitTarget
,BufferList
) の導入:scanblock
関数がポインタを走査する際に、直接Workbuf
に追加するのではなく、PtrTarget
とBitTarget
という2種類の中間バッファを使用するようになりました。PtrTarget
: ポインタとその型情報 (p
,ti
) を保持します。BitTarget
: ポインタ、型情報、およびそのポインタがヒープビットマップのどこに対応するかを示す情報 (bitp
,shift
) を保持します。BufferList
: これらのPtrTarget
とBitTarget
の配列を保持し、GCワーカー間でバッファを再利用するためのリンクリストとして機能します。- これらのバッファは
IntermediateBufferCapacity
(64要素) という小さなサイズで定義されており、GCの効率を向上させるための工夫です。
-
flushptrbuf
関数の追加:- この新しい関数は、
PtrTarget
バッファからBitTarget
バッファへ、そして最終的にメインのWorkbuf
へとポインタを移動させる役割を担います。 PtrTarget
バッファ内のポインタを走査し、それがヒープ内の有効なオブジェクトの開始点であるかを判断します。- 有効なオブジェクトであれば、そのオブジェクトに対応するヒープビットマップのビットをマークし、
Workbuf
に追加します。 - この関数は、ポインタのマークとキューイングのロジックをカプセル化し、
scanblock
の複雑さを軽減しています。
- この新しい関数は、
-
scanblock
関数の大幅な改修:- 関数のシグネチャが変更され、直接メモリブロック (
byte *b, uintptr n
) を受け取るのではなく、Workbuf
の状態 (Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking
) を受け取るようになりました。これは、scanblock
がより高レベルのGCロジックの一部として機能するようになったことを示しています。 - 内部の走査ループが完全に書き直され、
PtrTarget
とBitTarget
を使用した新しい中間バッファリングメカニズムが導入されました。 GC_DEFAULT_PTR
とdefaultProg
という概念が導入されていますが、コミットメッセージにあるように、この時点ではまだ「スケルトンコード」であり、オブジェクトの型を認識する機能は将来のCLで拡張される予定です。flush_buffers
というラベルと、そこでflushptrbuf
が呼び出されることで、中間バッファのフラッシュ処理が明確に定義されています。
- 関数のシグネチャが変更され、直接メモリブロック (
-
enqueue
関数の追加:Obj
をWorkbuf
に追加するためのヘルパー関数です。ポインタのアライメント調整や、handoff
(他のGCワーカーへの作業の引き渡し) のロジックを含んでいます。
-
ルート走査の変更 (
addroot
,addstackroots
,addfinroots
,addroots
):- すべてのルート(スタック、ファイナライザ、グローバル変数など)が
Obj
構造体を使用してGCに登録されるようになりました。 - 特に、
addroots
関数では、data
セクションとbss
セクションがObj
として追加され、それぞれgcdata
とgcbss
のアドレスが型情報 (ti
) として渡されています。これは、これらの静的メモリ領域のポインタ情報をGCが利用できるようにするための重要な変更です。
- すべてのルート(スタック、ファイナライザ、グローバル変数など)が
これらの変更は、GoのGCがより正確なポインタ識別を行い、メモリ使用量を最適化し、将来的なGCアルゴリズムの改善(例えば、コンカレントなスイープや世代別GCなど)のための強固な基盤を築くことを目的としています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルとセクションに集中しています。
src/pkg/runtime/mgc0.c
:Obj
構造体の定義(新規追加)Workbuf
構造体の変更(obj
フィールドの型変更)PtrTarget
,BitTarget
,BufferList
構造体の定義(新規追加)flushptrbuf
関数の定義(新規追加)scanblock
関数のシグネチャと内部ロジックの大幅な変更enqueue
関数の定義(新規追加)addroot
関数のシグネチャと内部ロジックの変更addstackroots
,addfinroots
,addroots
関数におけるObj
構造体の使用
コアとなるコードの解説
以下に、主要な変更点とそのコードを抜粋して解説します。
-
Obj
構造体の定義:typedef struct Obj Obj; struct Obj { byte *p; // data pointer uintptr n; // size of data in bytes uintptr ti; // type info };
この新しい構造体は、GCが扱うメモリブロックの基本単位となります。
p
はデータへのポインタ、n
はデータのサイズ、そして最も重要なのがti
(type info) です。このti
フィールドは、そのメモリブロックがどのような型情報を持っているかを示すもので、将来的にGCがポインタの場所を正確に特定するために使用されます。 -
Workbuf
構造体の変更:typedef struct Workbuf Workbuf; struct Workbuf { #define SIZE (2*PageSize-sizeof(LFNode)-sizeof(uintptr)) LFNode node; // must be first uintptr nobj; Obj obj[SIZE/sizeof(Obj) - 1]; // byte* から Obj に変更 uint8 _padding[SIZE%sizeof(Obj) + sizeof(Obj)]; #undef SIZE };
Workbuf
はGCが走査すべきオブジェクトのキューとして機能します。以前はbyte *obj[]
で生ポインタを格納していましたが、この変更によりObj obj[]
となり、ポインタだけでなくサイズや型情報もまとめて管理できるようになりました。 -
中間バッファ構造体 (
PtrTarget
,BitTarget
,BufferList
):struct PtrTarget { void *p; uintptr ti; }; struct BitTarget { void *p; uintptr ti; uintptr *bitp, shift; }; struct BufferList { struct PtrTarget ptrtarget[IntermediateBufferCapacity]; struct BitTarget bittarget[IntermediateBufferCapacity]; struct BufferList *next; };
これらの中間バッファは、
scanblock
がポインタを走査する際の効率を向上させます。PtrTarget
は見つかったポインタとその型情報を一時的に保持し、BitTarget
はさらにヒープビットマップ上の位置情報も追加します。BufferList
はこれらのバッファを管理し、再利用を可能にします。 -
flushptrbuf
関数の追加:static void flushptrbuf(struct PtrTarget *ptrbuf, uintptr n, Obj **_wp, Workbuf **_wbuf, uintptr *_nobj, struct BitTarget *bitbuf) { // ... (省略) ... // Multi-threaded version. bitbufpos = bitbuf; while(ptrbuf < ptrbuf_end) { obj = ptrbuf->p; ti = ptrbuf->ti; ptrbuf++; // ... (オブジェクトの開始位置とビットマップ情報の取得ロジック) ... // Mark the block *bt->bitp = xbits | (bitMarked << bt->shift); // If object has no pointers, don't need to scan further. if((bits & bitNoPointers) != 0) continue; obj = bt->p; *wp = (Obj){obj, s->elemsize, bt->ti}; // Obj 構造体として Workbuf に追加 wp++; nobj++; } // ... (省略) ... }
この関数は、
PtrTarget
バッファからポインタを取り出し、それが有効なオブジェクトの開始点であるかを判断し、ヒープビットマップにマークを付け、最終的にObj
構造体としてWorkbuf
に追加する一連の処理を行います。これにより、ポインタのマークとキューイングのロジックがscanblock
から分離され、モジュール性が向上しています。 -
scanblock
関数の変更:static void scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking) { // ... (省略) ... // Allocate ptrbuf, bitbuf // ... (BufferList から中間バッファを取得するロジック) ... // ... (省略) ... for(;;) { // ... (省略) ... // TODO(atom): to be replaced in a next CL pc = defaultProg; // 現時点ではデフォルトの走査プログラムを使用 // ... (省略) ... case GC_DEFAULT_PTR: while(true) { // ... (ポインタの読み取りと arena_start/arena_used の範囲チェック) ... if(obj >= arena_start && obj < arena_used) { *ptrbufpos = (struct PtrTarget){obj, 0}; // PtrTarget に追加 ptrbufpos++; if(ptrbufpos == ptrbuf_end) goto flush_buffers; // バッファが満杯になったらフラッシュ } } // ... (省略) ... flush_buffers: flushptrbuf(ptrbuf, ptrbufpos-ptrbuf, &wp, &wbuf, &nobj, bitbuf); // 中間バッファをフラッシュ ptrbufpos = ptrbuf; goto next_instr; next_block: // ... (Workbuf から次の Obj を取得するロジック) ... --wp; b = wp->p; n = wp->n; nobj--; } // ... (省略) ... }
scanblock
は、Workbuf
からObj
を取得し、そのメモリブロックを走査します。走査中に見つかったポインタは、まずPtrTarget
中間バッファに追加され、バッファが満杯になるとflushptrbuf
を呼び出してWorkbuf
にフラッシュされます。この構造は、将来的にdefaultProg
の部分がオブジェクトの型情報 (ti
) に基づいてより賢い走査ロジックに置き換えられることを想定しています。 -
addroots
関数の変更:static void addroots(void) { // ... (省略) ... // data & bss // TODO(atom): load balancing addroot((Obj){data, edata - data, (uintptr)gcdata}); // gcdata を ti として追加 addroot((Obj){bss, ebss - bss, (uintptr)gcbss}); // gcbss を ti として追加 // ... (省略) ... // stacks for(gp=runtime·allg; gp!=nil; gp=gp->alllink) { // ... (省略) ... addroot((Obj){sp, (byte*)stk - sp, 0}); // スタックルートも Obj として追加 } // ... (省略) ... }
addroots
関数は、GCのマークフェーズの開始点となる「ルート」を登録します。この変更により、data
セクションとbss
セクションがObj
構造体として登録され、それぞれのti
フィールドにgcdata
およびgcbss
のアドレスが渡されるようになりました。これは、これらの静的メモリ領域内のポインタをGCが正確に識別するために非常に重要です。スタックルートやファイナライザのルートも同様にObj
として登録されます。
これらの変更は、GoのGCがより詳細な型情報を利用して、メモリをより効率的かつ正確に管理するための基盤を構築するものです。
関連リンク
- Go Gerrit Change-ID: https://golang.org/cl/6856121
- GitHub上のコミットページ: https://github.com/golang/go/commit/013fa63c901e4a08548821bcc46393584b8c701e
参考にした情報源リンク
- Go言語のソースコード (
src/pkg/runtime/mgc0.c
) - Go言語のガベージコレクションに関する一般的な知識
- コミットメッセージに記載されている情報
(注: コミットメッセージに記載されていた「CL 6114046」については、現在の検索では具体的な情報を見つけることができませんでした。)