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

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

このコミットは、Goランタイムにおいて、アロケートされたオブジェクトの型情報を保存する機能を追加するものです。これにより、ガベージコレクション(GC)の精度向上や、ランタイムでの型情報の利用が強化されます。

コミット

commit 4a191c2c1b3fe1325ab8617472aef628fd494076
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date:   Sun Oct 21 17:41:32 2012 -0400

    runtime: store types of allocated objects
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6569057

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

https://github.com/golang/go/commit/4a191c2c1b3fe1325ab8617472aef628fd494076

元コミット内容

Goランタイムにおいて、アロケートされたオブジェクトの型情報を保存するように変更。

変更の背景

Go言語は、メモリ管理にガベージコレクション(GC)を採用しています。Go 1.0がリリースされた2012年当時、Goランタイムは「保守的(conservative)」なストップ・ザ・ワールド方式のマーク&スイープGCを使用していました。保守的GCは、ポインタのように見える値はすべてポインタとして扱うため、実際にはポインタではないデータも参照として誤認識し、到達不能なオブジェクトが解放されない「メモリリーク」のような状況を引き起こす可能性がありました。

より効率的で正確なGCを実現するためには、アロケートされたメモリ領域がどのような型のオブジェクトを保持しているかをランタイムが正確に把握する必要があります。これにより、GCはポインタを正確に識別し、到達不能なオブジェクトを確実に回収できるようになります。このコミットは、そのための基盤を構築するもので、アロケート時にオブジェクトの型情報を関連付けるメカニズムを導入しています。これは、Goランタイムが「より正確な(precise)」GCへと進化していく過程の重要な一歩となります。

前提知識の解説

  • ガベージコレクション (GC): プログラムが動的に確保したメモリ領域のうち、もはや使用されなくなった領域(到達不能なオブジェクト)を自動的に解放する仕組みです。Go言語ではランタイムがGCを担当します。
    • 保守的GC (Conservative GC): メモリ上の任意のビットパターンをポインタとして解釈する可能性があるGCです。これにより、誤って非ポインタ値をポインタとみなし、本来解放されるべきメモリを保持し続けることがあります。
    • 正確なGC (Precise GC): メモリ上のどの値がポインタであるかを正確に識別できるGCです。これにより、到達不能なオブジェクトを確実に解放し、メモリ利用効率を最大化できます。GoはGo 1.3で完全に正確なGCに移行しました。
  • ランタイム型情報 (Runtime Type Information): プログラムの実行時に利用可能な型に関する情報です。Goではreflectパッケージを通じてこの情報にアクセスできますが、その基盤はランタイム内部にあります。この情報には、型のサイズ、アライメント、フィールド構成などが含まれます。
  • runtime.settype: Goランタイム内部で使用される関数で、アロケートされたメモリブロックにそのメモリが保持するオブジェクトの型情報を関連付ける役割を担います。このコミットで導入される主要なメカニズムの一つです。
  • UseSpanType: Goランタイムの内部変数で、ヒーププロファイリングやトレースに関連する古いバージョンのGoソースコードで主に使用されていました。これは低レベルの実装詳細であり、Go開発者がアプリケーションで直接使用したり設定したりするための機能やAPIではありません。このコミットでは、UseSpanTypeが有効な場合に型情報を設定する条件分岐が追加されています。これは、特定のデバッグやプロファイリングのシナリオで型情報が必要とされることを示唆しています。

技術的詳細

このコミットの核心は、Goランタイムがメモリをアロケートする際に、そのアロケートされたメモリブロックに、格納されるオブジェクトの型情報を付加するメカニズムを導入することです。これにより、GCがメモリをスキャンする際に、どのメモリ領域がポインタを含み、どの領域がポインタを含まないかを正確に判断できるようになります。

具体的には、以下の変更が行われています。

  1. runtime.settype関数の導入と利用: 新たにruntime.settype(void*, uintptr)関数が追加され、runtime·makemap_c (マップ作成)、reflect·unsafe_New (単一オブジェクトの新規作成)、reflect·unsafe_NewArray (配列の新規作成)、runtime·cnew (Cから呼び出し可能な新規作成)、runtime·makeslice (スライス作成) といったメモリ割り当てを行う箇所で呼び出されるようになります。この関数は、割り当てられたメモリのアドレスと、そのメモリに格納されるオブジェクトの型情報(Type構造体へのポインタと、オブジェクトの種類を示すフラグの組み合わせ)を受け取ります。
  2. TypeInfoフラグの導入: TypeInfo_Map, TypeInfo_SingleObject, TypeInfo_Arrayといった新しいフラグが導入され、runtime.settypeに渡される型情報の一部として使用されます。これにより、アロケートされたメモリがマップ、単一のオブジェクト、または配列のいずれであるかを区別できるようになります。
  3. runtime.cnewの追加: CコードからGoの型に基づいて新しいオブジェクトをアロケートするためのruntime.cnew(Type *typ)関数が追加されました。これは、特にランタイム内部でGoの型を扱う必要がある場合に便利です。
  4. runtime.zerobaseの外部化: ゼロ長のスライスや配列の基底ポインタとして使用されるzerobaseが、staticからexternに変更され、runtime.hで宣言されるようになりました。これにより、複数のファイルからアクセス可能になります。
  5. PtrType構造体の追加: ポインタ型を表すPtrType構造体がtype.hに追加されました。これは、ポインタが指す要素の型(elem)を保持します。
  6. runtime.newmでのruntime.cnewの利用: 新しいM(Machine、OSスレッドを表すランタイムの構造体)をアロケートするruntime.newm関数が、runtime.mallocの代わりにruntime.cnewを使用するように変更されました。これにより、Mオブジェクトも型情報と共にアロケートされるようになります。
  7. mgc0.goの新規追加: gc_m_ptr関数を定義するmgc0.goが追加されました。この関数はCから呼び出され、Goの*m型(M構造体へのポインタ型)を返します。これは、runtime.newmでMの型情報を取得するために使用されます。

これらの変更により、Goランタイムはヒープ上の各オブジェクトがどの型であるかを正確に追跡できるようになり、将来的な正確なGCの実装に向けた重要なステップとなります。

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

  • src/pkg/runtime/hashmap.c: runtime·makemap_c関数で、新しく作成されるマップオブジェクトに型情報を設定するロジックを追加。
  • src/pkg/runtime/iface.c: reflect·unsafe_Newおよびreflect·unsafe_NewArray関数で、リフレクションを通じてアロケートされるオブジェクトに型情報を設定するロジックを追加。
  • src/pkg/runtime/malloc.goc: runtime·cnew関数を新規追加。これはCから呼び出し可能なruntime·newの同等機能で、アロケートされたオブジェクトに型情報を設定する。
  • src/pkg/runtime/malloc.h: runtime·cnew関数のプロトタイプ宣言と、runtime·gc_m_ptrの宣言を追加。
  • src/pkg/runtime/mgc0.go: gc_m_ptr関数を定義する新しいファイル。
  • src/pkg/runtime/proc.c: runtime·newm関数で、M構造体のアロケーションにruntime·cnewを使用し、Mの型情報を取得するロジックを追加。
  • src/pkg/runtime/runtime.h: runtime·zerobaseextern宣言に変更。
  • src/pkg/runtime/slice.c: runtime·makeslice関数で、新しく作成されるスライス配列に型情報を設定するロジックを追加。zerobaseの定義をstaticからuintptr runtime·zerobase;に変更。
  • src/pkg/runtime/type.h: PtrType構造体を新規追加。TypeInfo_Map, TypeInfo_SingleObject, TypeInfo_Arrayなどの型情報フラグを定義。

コアとなるコードの解説

このコミットの主要な変更は、メモリ割り当てを行う様々なランタイム関数にruntime·settypeの呼び出しを追加したことです。

src/pkg/runtime/hashmap.c の変更点:

 if(UseSpanType) {
  if(false) {
   runtime·printf("makemap %S: %p\n", *typ->string, h);
  }
  runtime·settype(h, (uintptr)typ | TypeInfo_Map);
 }

runtime·makemap_c関数内で、UseSpanTypeが有効な場合に、新しくアロケートされたマップのヘッダhに対してruntime·settypeを呼び出しています。typはマップの型情報であり、TypeInfo_MapフラグとOR演算で結合され、マップであることを示しています。

src/pkg/runtime/iface.c の変更点:

 flag = t->kind&KindNoPointers ? FlagNoPointers : 0;
 ret = runtime·mallocgc(t->size, flag, 1, 1);

 if(UseSpanType && !flag) {
  if(false) {
   runtime·printf("unsafe_New %S: %p\n", *t->string, ret);
  }
  runtime·settype(ret, (uintptr)t | TypeInfo_SingleObject);
 }

reflect·unsafe_New関数では、リフレクションを通じて単一のオブジェクトがアロケートされる際に、ポインタを含まない型でない限り(!flag)、runtime·settypeが呼び出され、TypeInfo_SingleObjectフラグと共に型情報が設定されます。reflect·unsafe_NewArrayも同様に、配列のアロケーション時にTypeInfo_Arrayフラグと共に型情報を設定します。

src/pkg/runtime/malloc.goc の変更点:

void*
runtime·cnew(Type *typ)
{
	uint32 flag;
	void *ret;

	m->racepc = runtime·getcallerpc(&typ);
	flag = typ->kind&KindNoPointers ? FlagNoPointers : 0;
	ret = runtime·mallocgc(typ->size, flag, 1, 1);

	if(UseSpanType && !flag) {
		if(false) {
			runtime·printf("new %S: %p\n", *typ->string, ret);
		}
		runtime·settype(ret, (uintptr)typ | TypeInfo_SingleObject);
	}
	return ret;
}

新しく追加されたruntime·cnew関数は、CコードからGoの型に基づいてオブジェクトをアロケートするためのものです。この関数も、アロケートされたオブジェクトにruntime·settypeを呼び出し、TypeInfo_SingleObjectフラグと共に型情報を設定します。

src/pkg/runtime/proc.c の変更点:

 if(mtype == nil) {
  Eface e;
  runtime·gc_m_ptr(&e);
  mtype = ((PtrType*)e.type)->elem;
 }

 mp = runtime·cnew(mtype);

runtime·newm関数では、M構造体(OSスレッドを表すランタイムの構造体)をアロケートする際に、runtime·mallocの代わりにruntime·cnewを使用するように変更されました。これにより、Mオブジェクトも型情報と共にアロケートされるようになります。mtypeは、新しく追加されたmgc0.gogc_m_ptr関数を通じて取得されます。

src/pkg/runtime/slice.c の変更点:

 else {
  ret->array = runtime·mallocgc(size, 0, 1, 1);

  if(UseSpanType) {
   if(false) {
    runtime·printf("new slice [%D]%S: %p\n", (int64)cap, *t->elem->string, ret->array);
   }
   runtime·settype(ret->array, (uintptr)t->elem | TypeInfo_Array);
  }
 }

runtime·makeslice関数では、スライスのアロケーション時に、要素がポインタを含まない型でない限り、runtime·settypeが呼び出され、TypeInfo_Arrayフラグと共に要素の型情報が設定されます。

これらの変更は、Goランタイムがヒープ上のメモリブロックと、そこに格納されているGoの型情報を密接に結びつけるための重要なステップです。これにより、GCはより正確にポインタを識別し、メモリを効率的に管理できるようになります。また、ランタイムがオブジェクトの型情報を利用する他の機能(例えば、デバッグツールやプロファイリングツール)の基盤も強化されます。

関連リンク

参考にした情報源リンク