[インデックス 13945] ファイルの概要
このコミットでは、Goランタイムのメモリ管理におけるMSpan
構造体に型情報を追加する変更が行われています。具体的には以下のファイルが変更されました。
src/pkg/runtime/malloc.goc
: メモリ割り当てと型情報の設定・取得に関する主要なロジックが含まれています。runtime·mallocgc
、runtime·mlookup
、runtime·settype_flush
、runtime·settype
、runtime·settype_sysfree
、runtime·gettype
、new
関数が変更または追加されています。src/pkg/runtime/malloc.h
:MSpan
構造体と関連する型情報(MTypes
)の定義、および新しい関数のプロトタイプ宣言が追加されています。src/pkg/runtime/mgc0.c
: ガベージコレクション(GC)のルート追加とスイープ処理において、MSpan
の型情報を考慮するように変更されています。src/pkg/runtime/mheap.c
:MHeap
におけるMSpan
の初期化と解放時に型情報を扱うように変更されています。src/pkg/runtime/runtime.h
:M
構造体にsettype_buf
とsettype_bufsize
が追加され、UseSpanType
とDebugTypeAtBlockEnd
という新しい列挙型が定義されています。
コミット
commit f8c58373e53a9399a9ea75f744c717fc59be3839
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Mon Sep 24 20:08:05 2012 -0400
runtime: add types to MSpan
R=rsc
CC=golang-dev
https://golang.org/cl/6554060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f8c58373e53a9399a9ea75f744c717fc59be3839
元コミット内容
runtime: add types to MSpan
R=rsc
CC=golang-dev
https://golang.org/cl/6554060
変更の背景
このコミットの主な背景は、Goランタイムのガベージコレクション(GC)の効率と正確性を向上させるために、メモリ領域(MSpan
)に割り当てられたオブジェクトの型情報を関連付ける必要があったことです。
GoのGCは、ヒープ上のオブジェクトがまだ使用されているかどうかを判断するために、ポインタをたどる必要があります。しかし、メモリ領域が単なるバイトの塊として扱われるだけでは、どの部分がポインタであり、どの部分が非ポインタデータであるかをGCが正確に識別することが困難になります。特に、異なる型のオブジェクトが同じMSpan
内に混在する場合、GCは誤ってポインタではないデータをポインタとして解釈したり、逆にポインタであるデータを見落としたりする可能性があります。
MSpan
に型情報を直接持たせることで、GCは特定のメモリブロックがどのような型のオブジェクトを格納しているかを把握できるようになります。これにより、GCはポインタの走査をより正確に行い、不要なメモリを確実に解放し、メモリリークや誤ったメモリ解放によるクラッシュを防ぐことができます。また、型情報に基づいてGCの動作を最適化する可能性も開かれます。
この変更は、Goのメモリ管理システムがより堅牢で効率的になるための重要なステップであり、特にポインタを含むオブジェクトの正確なGCを保証するために不可欠です。
前提知識の解説
このコミットを理解するためには、Goランタイムのメモリ管理とガベージコレクションに関するいくつかの基本的な概念を理解しておく必要があります。
Goのメモリ管理階層
Goのメモリ管理は、主に以下の3つの階層で構成されています。
mcache
(Memory Cache): 各論理プロセッサ(P)に紐付けられたローカルキャッシュです。ゴルーチンは、ロックなしで高速に小さなオブジェクトを割り当てるために、まずこのmcache
からメモリを要求します。mcentral
(Memory Central): 特定のサイズクラス(後述)のMSpan
を管理するグローバルなリストです。mcache
がメモリを使い果たした場合、mcentral
から新しいMSpan
を取得します。この操作にはロックが必要になります。mheap
(Memory Heap): Goのヒープ全体の最上位の管理者です。オペレーティングシステムからメモリページを取得し、それをmcentral
に提供してMSpan
を作成します。
MSpan
(Memory Span)
MSpan
は、Goランタイムのメモリ管理における基本的な単位です。これは、1つまたは複数の連続した8KBのメモリページ(Goランタイムの内部的なページサイズであり、OSのページサイズとは異なる場合があります)の塊を表します。
- サイズクラス:
MSpan
は、特定の「サイズクラス」のオブジェクトを割り当てるために使用されます。Goは、メモリ要求を約67種類の異なるサイズクラスに分類し、効率的な割り当てを実現しています。例えば、144バイトのオブジェクトを割り当てるためのMSpan
は、特定のサイズクラスに属します。 - 構造:
MSpan
は通常、二重リンクリストのオブジェクトとして表現され、startAddr
(メモリブロックの開始アドレス)、npages
(管理する8KBページの数)、sizeclass
(サイズクラス)、next
、prev
などのフィールドを持ちます。 - 状態:
MSpan
は、mSpanDead
(未使用)、mSpanInUse
(ヒープオブジェクトが割り当てられている)、mSpanManual
(ゴルーチンのスタックなど、手動で管理される)などの状態を持つことができます。
Goのガベージコレクション (GC)
GoのGCは、主に並行トライカラーマーク&スイープアルゴリズムを採用しています。これは、アプリケーションの実行と並行してGCの大部分の作業を行うことで、アプリケーションの一時停止時間(Stop-the-World: STW)を最小限に抑えることを目的としています。
- マークフェーズ: プログラムがまだ使用している「ライブ」なオブジェクトを識別します。グローバル変数やアクティブなスタックフレームなどの「ルート」からオブジェクトグラフをたどり、到達可能なオブジェクトをマークします。このフェーズの大部分はアプリケーションと並行して実行されます。
- スイープフェーズ: マークされなかったオブジェクト(つまり、ガベージ)からメモリを回収します。このフェーズもアプリケーションと並行して実行されます。
- トライカラー抽象化: マークフェーズ中に、オブジェクトは以下の3つの「色」に分類されます。
- 白 (White): GCによってまだ訪問されていないオブジェクト。回収の候補。
- 灰 (Gray): 到達可能と識別されたが、その参照(子)がまだ完全にスキャンされていないオブジェクト。
- 黒 (Black): GCによって完全に処理されたオブジェクト。到達可能であり、そのすべての参照もスキャン済み。
- ライトバリア: 並行実行中にGCの正確性を保証するために、Goはライトバリアを使用します。これは、GCが実行中にアプリケーションがオブジェクト参照を変更するのを追跡し、ライブオブジェクトが誤って回収されないようにするメカニズムです。
型情報とGCの関連性
GCがヒープ上のオブジェクトを正確に走査し、ポインタを識別するためには、各メモリブロックに格納されているデータの型情報が必要です。特に、ポインタを含むオブジェクト(scan
オブジェクト)とポインタを含まないオブジェクト(noscan
オブジェクト)を区別し、scan
オブジェクト内のどのオフセットにポインタが存在するかを知る必要があります。
これまでのGoランタイムでは、MSpan
自体が型情報を直接保持する仕組みは限定的でした。このコミットは、MSpan
に割り当てられたオブジェクトの型情報をより詳細に、かつ効率的に管理するための新しいメカニズムを導入することで、GCの精度と性能を向上させることを目指しています。
技術的詳細
このコミットの核心は、MSpan
構造体にMTypes
という新しいフィールドを追加し、メモリブロックに割り当てられたオブジェクトの型情報を効率的に格納・取得するメカニズムを導入したことです。
MTypes
構造体
MTypes
は、MSpan
内のブロックに割り当てられたオブジェクトの型を記述するための構造体です。compression
フィールドによって、データのレイアウトが異なります。
struct MTypes
{
byte compression; // one of MTypes_*
bool sysalloc; // whether (void*)data is from runtime·SysAlloc
uintptr data;
};
compression
フィールドは以下の4つの値を取ります。
MTypes_Empty
(0):- すべてのブロックがフリーであるか、割り当てられたブロックに対して型情報が利用できない状態を示します。
data
フィールドは意味を持ちません。
MTypes_Single
(1):MSpan
が単一のブロックのみを含んでいる場合に使用されます。data
フィールドに直接型情報が格納されます。sysalloc
フィールドは意味を持ちません。
MTypes_Words
(2):MSpan
が複数のブロックを含んでいる場合に使用されます。data
フィールドは、[NumBlocks]uintptr
型の配列へのポインタを保持します。- この配列の各要素は、対応するブロックの型情報を保持します。
MTypes_Bytes
(3):MSpan
が最大7種類の異なる型のブロックを含んでいる場合に使用されます。これは、型情報の種類が少ない場合にメモリを節約するための圧縮形式です。data
フィールドは以下の構造体へのポインタを保持します。struct { uintptr type[8]; // type[0] is always 0 byte index[NumBlocks]; }
type
配列には、MSpan
内に存在する最大7種類の型情報が格納されます(type[0]
は常に0で未使用)。index
配列は、各ブロックがtype
配列のどのインデックスの型に対応するかを示します。- i番目のブロックの型は、
data.type[data.index[i]]
として取得されます。
型情報を操作する新しい関数
このコミットでは、MSpan
の型情報を設定・取得・解放するための新しいランタイム関数が導入されています。
runtime·settype(void *v, uintptr t)
:- 指定されたメモリブロック
v
に型情報t
を設定します。 - この関数は、
M
構造体内のsettype_buf
というバッファに、設定すべき型情報を一時的に格納します。 - バッファが満杯になった場合、またはGCが開始される前に、
runtime·settype_flush
が呼び出されます。 DebugTypeAtBlockEnd
が有効な場合、デバッグ目的でブロックの末尾に型情報が書き込まれます。
- 指定されたメモリブロック
runtime·settype_flush(M *m, bool sysalloc)
:m->settype_buf
に蓄積された型情報を、対応するMSpan
に実際に書き込みます。MSpan
のtypes.compression
の状態に応じて、MTypes_Empty
からMTypes_Bytes
、またはMTypes_Words
への変換ロジックが含まれています。- 特に
MTypes_Bytes
からMTypes_Words
への変換は、MSpan
内の型情報の種類が8種類以上になった場合に発生し、より多くのメモリを消費しますが、より柔軟な型表現を可能にします。 sysalloc
引数は、型情報格納用のメモリをシステムから直接割り当てるかどうかを制御します。
runtime·settype_sysfree(MSpan *s)
:MSpan
が解放される際に、そのMTypes
構造体がシステムから割り当てたメモリ(s->types.sysalloc
がtrueの場合)を解放します。MTypes_Words
またはMTypes_Bytes
圧縮形式の場合に、対応するメモリ領域を解放します。
runtime·gettype(void *v)
:- 指定されたメモリブロック
v
の型情報を取得します。 runtime·MHeap_LookupMaybe
を使用してMSpan
を検索し、そのMSpan
のtypes.compression
に基づいて型情報をデコードして返します。
- 指定されたメモリブロック
DebugTypeAtBlockEnd
このコミットでは、DebugTypeAtBlockEnd
というデバッグフラグが導入されています。これが有効な場合、runtime·mallocgc
で割り当てられたブロックの末尾に、そのブロックのサイズや型情報が書き込まれます。これは、デバッグ時にメモリの破損などを検出するのに役立つ可能性があります。
UseSpanType
UseSpanType
というフラグが導入され、これが有効な場合にnew
関数がruntime·settype
を呼び出して、新しく割り当てられたオブジェクトの型情報をMSpan
に設定するようになります。これにより、MSpan
レベルでの型情報の管理が有効化されます。
これらの変更により、Goランタイムはヒープ上の各メモリブロックの型情報をより詳細に追跡できるようになり、ガベージコレクタがポインタを正確に識別し、メモリ管理の効率と信頼性を向上させる基盤が強化されました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なのは以下の点です。
-
src/pkg/runtime/malloc.h
におけるMTypes
構造体とMSpan
構造体の変更:MTypes
構造体が新しく定義され、compression
、sysalloc
、data
フィールドを持つ。MSpan
構造体にMTypes types;
フィールドが追加され、elemsize
フィールドも追加された。MTypes_Empty
,MTypes_Single
,MTypes_Words
,MTypes_Bytes
という列挙型が定義された。runtime·settype
,runtime·settype_flush
,runtime·settype_sysfree
,runtime·gettype
関数のプロトタイプ宣言が追加された。TypeInfo_SingleObject
,TypeInfo_Array
,TypeInfo_Map
およびDebugTypeAtBlockEnd
,UseSpanType
という列挙型が追加された。
-
src/pkg/runtime/malloc.goc
における型情報設定・取得ロジックの追加:runtime·mallocgc
関数にDebugTypeAtBlockEnd
フラグに応じたサイズ調整と型情報の書き込みロジックが追加された。runtime·mlookup
関数でs->elemsize
を使用するように変更された。runtime·settype_flush
関数が新しく追加され、MTypes
の圧縮形式に応じた型情報の書き込みロジックが実装された。runtime·settype
関数が新しく追加され、型情報をバッファリングし、必要に応じてsettype_flush
を呼び出す。runtime·settype_sysfree
関数が新しく追加され、MTypes
がシステムから割り当てたメモリを解放する。runtime·gettype
関数が新しく追加され、MSpan
の型情報からブロックの型を取得する。new
関数において、UseSpanType
が有効な場合にruntime·settype
を呼び出すロジックが追加された。
-
src/pkg/runtime/mgc0.c
におけるGCの変更:addroots
関数において、MSpan.types.data
がポインタである場合にGCルートとして追加するロジックが追加された。sweepspan
関数において、MSpan
の型情報(compression
、type_data
、type_data_inc
)を考慮して、解放されるオブジェクトの型情報をクリアするロジックが追加された。
-
src/pkg/runtime/mheap.c
におけるMSpan
の初期化と解放の変更:MHeap_Alloc
関数において、新しく割り当てられたMSpan
のelemsize
とtypes.compression
(MTypes_Empty
に初期化)を設定するようになった。MHeap_FreeLocked
関数において、MSpan
が解放される際にruntime·settype_sysfree
を呼び出し、types.compression
をMTypes_Empty
にリセットするようになった。runtime·MSpan_Init
関数において、elemsize
とtypes.compression
を初期化するようになった。
-
src/pkg/runtime/runtime.h
におけるM
構造体の変更:M
構造体にsettype_buf
とsettype_bufsize
が追加され、型情報のバッファリングを可能にした。
これらの変更は、Goランタイムのメモリ管理とGCの低レベルな動作に深く関わっており、ヒープ上のオブジェクトの型情報をより正確に追跡し、GCの効率と信頼性を向上させるための基盤を構築しています。
コアとなるコードの解説
MTypes
と圧縮形式
MTypes
構造体は、MSpan
内の各オブジェクトの型情報を効率的に格納するための新しい抽象化です。compression
フィールドは、型情報の格納方法を決定します。
MTypes_Empty
:MSpan
が空であるか、型情報が不要な場合。最もシンプルな状態。MTypes_Single
:MSpan
が単一のオブジェクトのみを保持する場合。型情報はdata
フィールドに直接格納され、オーバーヘッドが最小限に抑えられます。MTypes_Bytes
:MSpan
内のオブジェクトの型が最大7種類に限定される場合に使用されます。data
フィールドは、8つのuintptr
(型情報のルックアップテーブル)と、各ブロックの型インデックスを示すバイト配列を指します。これにより、型情報の種類が少ない場合にメモリ使用量を削減できます。MTypes_Words
:MSpan
内のオブジェクトの型が8種類を超える場合、またはMTypes_Bytes
では表現しきれない複雑な型情報が必要な場合に使用されます。data
フィールドは、各ブロックの型情報を直接格納するuintptr
の配列を指します。これは最も柔軟な形式ですが、MTypes_Bytes
よりも多くのメモリを消費する可能性があります。
この圧縮メカニズムは、メモリ使用量と型情報アクセスのパフォーマンスのバランスを取るために設計されています。
runtime·settype
とruntime·settype_flush
runtime·settype
は、特定のメモリブロックに型情報を設定するためのエントリポイントです。この関数は、型情報をM
構造体内のsettype_buf
という一時バッファに格納します。これは、頻繁な型情報の設定によるロックの競合を避けるための最適化です。
settype_buf
が満杯になった場合、またはGCが開始される前など、適切なタイミングでruntime·settype_flush
が呼び出されます。runtime·settype_flush
は、バッファに蓄積された型情報を実際に対応するMSpan
のMTypes
構造体に書き込みます。この際、MSpan
の現在のcompression
状態と、設定される型情報の種類に基づいて、最適な圧縮形式への変換が行われます。例えば、MTypes_Bytes
で型情報の種類が8種類を超えた場合、MTypes_Words
に昇格(アップグレード)されます。
runtime·gettype
runtime·gettype
は、特定のメモリブロックの型情報を取得するための関数です。この関数は、まずメモリブロックが属するMSpan
を特定し、そのMSpan
のMTypes
構造体のcompression
フィールドに基づいて、格納されている型情報をデコードして返します。これにより、GCやその他のランタイムコンポーネントは、ヒープ上の任意のオブジェクトの型情報を効率的に取得できるようになります。
GCとの連携
この変更は、GoのGCに直接影響を与えます。
- ルートの追加:
mgc0.c
のaddroots
関数では、MSpan.types.data
がポインタである場合に、GCのルートとして追加されるようになりました。これは、型情報自体がヒープ上に割り当てられている場合(MTypes_Words
やMTypes_Bytes
の場合)に、そのメモリがGCによって誤って回収されないようにするために重要です。 - スイープ処理:
mgc0.c
のsweepspan
関数では、MSpan
内のオブジェクトが解放される際に、そのオブジェクトの型情報もクリアされるようになりました。これにより、解放されたメモリブロックに古い型情報が残存することを防ぎ、GCの正確性を保ちます。
new
関数とUseSpanType
new
組み込み関数は、Goプログラムで新しいオブジェクトを割り当てる際に使用されます。このコミットでは、new
関数がUseSpanType
フラグが有効な場合にruntime·settype
を呼び出すように変更されました。これは、Goプログラムがヒープにオブジェクトを割り当てる際に、そのオブジェクトの型情報が自動的にMSpan
に記録されることを意味します。これにより、GCがより正確な型情報に基づいて動作できるようになります。
これらの変更は、Goランタイムのメモリ管理とGCの内部動作を大幅に強化し、より複雑なデータ構造や並行処理環境においても、メモリの安全性と効率性を高めるための重要な基盤を提供します。
関連リンク
- Goのガベージコレクションに関する公式ドキュメント: https://golang.org/doc/gc-guide (このコミット時点では存在しない可能性が高いですが、現在の情報として)
- Goのメモリ管理に関するブログ記事や解説 (一般的な情報源):
- The Go Memory Model: https://go.dev/ref/mem
- Go's Memory Allocator: https://go.dev/blog/go-memory-management (このコミット時点では存在しない可能性が高いですが、現在の情報として)
参考にした情報源リンク
- https://sobyte.net/post/2022-03/go-memory-management/
- https://medium.com/@ankur_anand/go-garbage-collector-a-comprehensive-guide-d52222222222
- https://leapcell.io/blog/go-garbage-collection
- https://andrestc.com/post/go-memory-management/
- https://povilasv.me/go-memory-management/
- https://dev.to/aickin/go-s-garbage-collector-in-detail-300k
- https://github.com/golang/go/blob/master/src/runtime/mheap.go (現在のGoソースコード)
- https://go.dev/src/runtime/mheap.go (現在のGoソースコード)
- https://go.dev/src/runtime/mgc.go (現在のGoソースコード)
- https://go.dev/src/runtime/malloc.go (現在のGoソースコード)
- https://go.dev/src/runtime/runtime.go (現在のGoソースコード)
- https://go.dev/src/runtime/mcache.go (現在のGoソースコード)
- https://go.dev/src/runtime/mcentral.go (現在のGoソースコード)