[インデックス 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ソースコード)