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

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

このコミットでは、Goランタイムのメモリ管理に関連する以下のファイルが変更されています。

  • src/pkg/runtime/malloc.goc
  • src/pkg/runtime/malloc.h
  • src/pkg/runtime/mgc0.c
  • src/pkg/runtime/mheap.c

コミット

commit e17281b39779c18fc73779c81a3741b05ea85485
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Thu May 30 17:09:58 2013 +0400

    runtime: rename mheap.maps to mheap.spans
    as was dicussed in cl/9791044
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/9853046

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

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

元コミット内容

runtime: rename mheap.maps to mheap.spans as was dicussed in cl/9791044

変更の背景

このコミットは、Goランタイムのメモリ管理における重要なデータ構造の名称変更を目的としています。背景には、cl/9791044 で議論された、ページテーブルの遅延割り当て(lazy allocation)に関する変更があります。

元々、Goランタイムは起動時に256MBものメモリをページテーブルのために事前に割り当てていました。これは、ulimit のようなシステムリソース制限と競合する可能性があり、特にリソースが限られた環境では問題となることがありました。この事前割り当てをなくし、必要に応じてページテーブルを割り当てる「遅延割り当て」を導入することで、起動時のメモリフットプリントを削減し、より柔軟なメモリ運用を目指しました。

この遅延割り当ての議論の中で、ページテーブルを管理するデータ構造の命名について混乱が生じました。当初は「MapMap」や「map_mapped」といった名称が提案されましたが、これらはその機能や役割を正確に反映しておらず、誤解を招く可能性がありました。レビューアからのフィードバックを受け、より適切で分かりやすい名称として「spans」が選ばれました。

したがって、このコミットの主な目的は、メモリ管理の内部構造の名称を、その機能と役割をより明確に表す「spans」に変更することです。これは、コードの可読性と保守性を向上させるためのクリーンアップ作業の一環であり、より大規模なメモリ管理の改善(ページテーブルの遅延割り当て)と密接に関連しています。

前提知識の解説

このコミットを理解するためには、Goランタイムのメモリ管理、特にヒープ構造とページテーブルに関する基本的な知識が必要です。

  1. Goランタイムのメモリ管理: Goは独自のガベージコレクタ(GC)を持つため、OSからメモリを直接要求し、それを独自のヒープとして管理します。このヒープは、ユーザープログラムがオブジェクトを割り当てるために使用されます。
  2. mheap 構造体: mheap はGoランタイムのグローバルなヒープ構造体であり、Goプログラムが使用するすべてのメモリを管理します。これには、空きメモリのリスト、割り当てられたメモリの追跡、およびメモリのページ(OSが管理する最小単位のメモリブロック)へのマッピングが含まれます。
  3. MSpan 構造体: MSpan は、Goランタイムが管理する連続したメモリページのブロックを表す構造体です。ヒープはこれらの MSpan の集合として構成されます。MSpan は、そのブロックがどの程度のサイズのオブジェクトを格納するために使用されているか(例えば、小さなオブジェクト用、大きなオブジェクト用など)、または空き状態であるかといった情報を含みます。
  4. ページテーブル(Page Table): 一般的なオペレーティングシステムでは、仮想メモリと物理メモリのマッピングを管理するためにページテーブルを使用します。Goランタイムの文脈では、mheap 内の「マップ」または「スパン」は、仮想アドレス空間の特定のページがどの MSpan に属しているかを高速にルックアップするためのデータ構造として機能します。これにより、任意のメモリアドレスがどの MSpan に対応しているかを効率的に判断できます。
  5. mheap.maps (変更前): 変更前は mheap.maps というフィールドが、仮想アドレス空間のページと MSpan のマッピングを管理していました。これは、特定の仮想アドレスがどの MSpan に属しているかを迅速に特定するためのルックアップテーブルとして機能していました。
  6. mheap.spans (変更後): このコミットによって mheap.mapsmheap.spans に名称変更されました。機能的には同じですが、その役割をより正確に表現しています。つまり、これはメモリの「スパン」(MSpan)へのマッピングを管理するテーブルである、ということを示唆しています。

この名称変更は、単なる変数名の変更以上の意味を持ちます。それは、Goランタイムのメモリ管理の内部構造と概念をより明確にし、将来的な改善(特にページテーブルの遅延割り当て)のための基盤を固めるものです。

技術的詳細

このコミットの技術的詳細は、Goランタイムのメモリ管理における mheap 構造体の map フィールドを spans にリネームすることに集約されます。この変更は、コードの可読性と、Goのメモリ管理の概念との整合性を高めることを目的としています。

Goランタイムのヒープマネージャである mheap は、仮想アドレス空間を MSpan と呼ばれる連続したメモリページのブロックに分割して管理します。MSpan は、ヒープ内のメモリ領域の基本的な管理単位です。

変更前、mheap 構造体には map というフィールドがありました。これは MSpan** 型であり、仮想アドレスのページ番号から対応する MSpan へのポインタをルックアップするための配列として機能していました。つまり、特定のメモリアドレスがどの MSpan に属しているかを効率的に見つけるための「ページテーブル」のような役割を担っていました。

しかし、「map」という名称は、Goの組み込み型である map(ハッシュマップ)と混同される可能性があり、また、その機能が「メモリページを MSpan にマッピングする」というよりも「MSpan の配列」であるという実態を正確に表していませんでした。

cl/9791044 での議論では、このデータ構造が「ページテーブルの遅延割り当て」という文脈で検討され、その役割をより適切に表現する名称が求められました。最終的に、「spans」という名称が選ばれました。これは、この配列が MSpan オブジェクトへのポインタを格納しているという事実を直接的に示しており、その役割をより明確に伝えます。

このリネームは、mheap 構造体の定義だけでなく、malloc.goc, mgc0.c, mheap.c など、この map フィールドを参照していたすべての箇所にわたって行われました。これにより、コードベース全体で一貫性が保たれ、将来の開発者がメモリ管理コードを理解しやすくなります。

具体的な変更は以下の通りです。

  • struct MHeap 内の MSpan** map;MSpan** spans; に変更されました。
  • malloc.goc では、spans_size の計算において sizeof(runtime·mheap.map[0])sizeof(runtime·mheap.spans[0]) に変更され、runtime·mheap.map = (MSpan**)p;runtime·mheap.spans = (MSpan**)p; に変更されました。また、runtime·mheap.map[p] の参照も runtime·mheap.spans[p] に変更されました。
  • mgc0.c および mheap.c でも同様に、runtime·mheap.map へのすべての参照が runtime·mheap.spans に変更されました。

この変更自体は、ランタイムの動作に機能的な影響を与えるものではありません。しかし、コードのセマンティクスを改善し、Goのメモリ管理の内部構造をより直感的に理解できるようにすることで、長期的な保守性と拡張性を向上させています。特に、ページテーブルの遅延割り当てのような複雑な変更を導入する際に、明確な命名規則は混乱を避ける上で不可欠です。

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

このコミットにおけるコアとなるコードの変更は、mheap 構造体内の map フィールドの名称を spans に変更し、それに伴い関連するすべての参照を更新することです。

1. src/pkg/runtime/malloc.h (MHeap 構造体の定義)

--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -411,7 +411,7 @@ struct MHeap
 	uint32	nspancap;
 
 	// span lookup
-	MSpan**	map;
+	MSpan**	spans;
 	uintptr	spans_mapped;
 
 	// range of addresses we might see in the heap

2. src/pkg/runtime/malloc.goc (メモリ初期化とルックアップ)

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -374,7 +374,7 @@ runtime·mallocinit(void)\
 		// If this fails we fall back to the 32 bit memory mechanism
 		arena_size = MaxMem;
 		bitmap_size = arena_size / (sizeof(void*)*8/4);\
-		spans_size = arena_size / PageSize * sizeof(runtime·mheap.map[0]);
+		spans_size = arena_size / PageSize * sizeof(runtime·mheap.spans[0]);
 		p = runtime·SysReserve((void*)(0x00c0ULL<<32), bitmap_size + spans_size + arena_size);\
 	}\
 	if (p == nil) {\
@@ -397,11 +397,11 @@ runtime·mallocinit(void)\
 		// of address space, which is probably too much in a 32-bit world.\
 		bitmap_size = MaxArena32 / (sizeof(void*)*8/4);\
 		arena_size = 512<<20;\
-		spans_size = MaxArena32 / PageSize * sizeof(runtime·mheap.map[0]);
+		spans_size = MaxArena32 / PageSize * sizeof(runtime·mheap.spans[0]);
 		if(limit > 0 && arena_size+bitmap_size+spans_size > limit) {\
 			bitmap_size = (limit / 9) & ~((1<<PageShift) - 1);\
 			arena_size = bitmap_size * 8;\
-			spans_size = arena_size / PageSize * sizeof(runtime·mheap.map[0]);
+			spans_size = arena_size / PageSize * sizeof(runtime·mheap.spans[0]);
 		}\
 
 		// SysReserve treats the address we ask for, end, as a hint,\
@@ -424,7 +424,7 @@ runtime·mallocinit(void)\
 	if((uintptr)p & (((uintptr)1<<PageShift)-1))\
 		runtime·throw(\"runtime: SysReserve returned unaligned address\");\
 
-	runtime·mheap.map = (MSpan**)p;
+	runtime·mheap.spans = (MSpan**)p;
 	runtime·mheap.bitmap = p + spans_size;
 	runtime·mheap.arena_start = p + spans_size + bitmap_size;
 	runtime·mheap.arena_used = runtime·mheap.arena_start;
@@ -532,7 +532,7 @@ runtime·settype_flush(M *mp, bool sysalloc)\
 		p = (uintptr)v>>PageShift;\
 		if(sizeof(void*) == 8)\
 			p -= (uintptr)runtime·mheap.arena_start >> PageShift;\
-		s = runtime·mheap.map[p];
+		s = runtime·mheap.spans[p];
 
 		if(s->sizeclass == 0) {\
 			s->types.compression = MTypes_Single;\

3. src/pkg/runtime/mgc0.c (ガベージコレクション関連)

--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -230,7 +230,7 @@ markonly(void *obj)\
 	x = k;\
 	if(sizeof(void*) == 8)\
 		x -= (uintptr)runtime·mheap.arena_start>>PageShift;\
-	s = runtime·mheap.map[x];
+	s = runtime·mheap.spans[x];
 	if(s == nil || k < s->start || k - s->start >= s->npages || s->state != MSpanInUse)\
 		return false;\
 	p = (byte*)((uintptr)s->start<<PageShift);\
@@ -410,7 +410,7 @@ flushptrbuf(PtrTarget *ptrbuf, PtrTarget **ptrbufpos, Obj **_wp, Workbuf **_wbuf\
 		x = k;\
 		if(sizeof(void*) == 8)\
 			x -= (uintptr)arena_start>>PageShift;\
-		s = runtime·mheap.map[x];
+		s = runtime·mheap.spans[x];
 		if(s == nil || k < s->start || k - s->start >= s->npages || s->state != MSpanInUse)\
 			continue;\
 		p = (byte*)((uintptr)s->start<<PageShift);\
@@ -458,7 +458,7 @@ flushptrbuf(PtrTarget *ptrbuf, PtrTarget **ptrbufpos, Obj **_wp, Workbuf **_wbuf\
 		x = (uintptr)obj >> PageShift;\
 		if(sizeof(void*) == 8)\
 			x -= (uintptr)arena_start>>PageShift;\
-		s = runtime·mheap.map[x];
+		s = runtime·mheap.spans[x];
 
 		PREFETCH(obj);\
 
@@ -575,7 +575,7 @@ checkptr(void *obj, uintptr objti)\
 	x = (uintptr)obj >> PageShift;\
 	if(sizeof(void*) == 8)\
 		x -= (uintptr)(runtime·mheap.arena_start)>>PageShift;\
-	s = runtime·mheap.map[x];
+	s = runtime·mheap.spans[x];
 	objstart = (byte*)((uintptr)s->start<<PageShift);\
 	if(s->sizeclass != 0) {\
 		i = ((byte*)obj - objstart)/s->elemsize;\

4. src/pkg/runtime/mheap.c (ヒープ管理ロジック)

--- a/src/pkg/runtime/mheap.c
+++ b/src/pkg/runtime/mheap.c
@@ -75,11 +75,11 @@ runtime·MHeap_MapSpans(MHeap *h)\
 	if(sizeof(void*) == 8)\
 		n -= (uintptr)h->arena_start;\
 	// Coalescing code reads spans past the end of mapped arena, thus +1.\
-	n = (n / PageSize + 1) * sizeof(h->map[0]);
+	n = (n / PageSize + 1) * sizeof(h->spans[0]);
 	n = ROUND(n, PageSize);\
 	if(h->spans_mapped >= n)\
 		return;\
-	runtime·SysMap((byte*)h->map + h->spans_mapped, n - h->spans_mapped);
+	runtime·SysMap((byte*)h->spans + h->spans_mapped, n - h->spans_mapped);
 	h->spans_mapped = n;\
 }\
 
@@ -172,9 +172,9 @@ HaveSpan:\
 	if(sizeof(void*) == 8)\
 		p -= ((uintptr)h->arena_start>>PageShift);\
 	if(p > 0)\
-		h->map[p-1] = s;
-		h->map[p] = t;
-		h->map[p+t->npages-1] = t;
+		h->spans[p-1] = s;
+		h->spans[p] = t;
+		h->spans[p+t->npages-1] = t;
 		*(uintptr*)(t->start<<PageShift) = *(uintptr*)(s->start<<PageShift);  // copy \"needs zeroing\" mark\
 		t->state = MSpanInUse;\
 		MHeap_FreeLocked(h, t);\
@@ -191,7 +191,7 @@ HaveSpan:\
 	if(sizeof(void*) == 8)\
 		p -= ((uintptr)h->arena_start>>PageShift);\
 	for(n=0; n<npage; n++)\
-		h->map[p+n] = s;
+		h->spans[p+n] = s;
 	return s;\
 }\
 
@@ -262,8 +262,8 @@ MHeap_Grow(MHeap *h, uintptr npage)\
 	p = s->start;\
 	if(sizeof(void*) == 8)\
 		p -= ((uintptr)h->arena_start>>PageShift);\
-	h->map[p] = s;
-	h->map[p + s->npages - 1] = s;
+	h->spans[p] = s;
+	h->spans[p + s->npages - 1] = s;
 	s->state = MSpanInUse;\
 	MHeap_FreeLocked(h, s);\
 	return true;\
@@ -280,7 +280,7 @@ runtime·MHeap_Lookup(MHeap *h, void *v)\
 	p = (uintptr)v;\
 	if(sizeof(void*) == 8)\
 		p -= (uintptr)h->arena_start;\
-	return h->map[p >> PageShift];
+	return h->spans[p >> PageShift];
 }\
 
 // Look up the span at the given address.\
@@ -302,7 +302,7 @@ runtime·MHeap_LookupMaybe(MHeap *h, void *v)\
 	q = p;\
 	if(sizeof(void*) == 8)\
 		q -= (uintptr)h->arena_start >> PageShift;\
-	s = h->map[q];
+	s = h->spans[q];
 	if(s == nil || p < s->start || p - s->start >= s->npages)\
 		return nil;\
 	if(s->state != MSpanInUse)\
@@ -354,26 +354,26 @@ MHeap_FreeLocked(MHeap *h, MSpan *s)\
 	p = s->start;\
 	if(sizeof(void*) == 8)\
 		p -= (uintptr)h->arena_start >> PageShift;\
-	if(p > 0 && (t = h->map[p-1]) != nil && t->state != MSpanInUse) {\
+	if(p > 0 && (t = h->spans[p-1]) != nil && t->state != MSpanInUse) {\
 		tp = (uintptr*)(t->start<<PageShift);\
 		*tp |= *sp;\t// propagate \"needs zeroing\" mark\
 		s->start = t->start;\
 		s->npages += t->npages;\
 		s->npreleased = t->npreleased; // absorb released pages\
 		p -= t->npages;\
-		h->map[p] = s;
+		h->spans[p] = s;
 		runtime·MSpanList_Remove(t);\
 		t->state = MSpanDead;\
 		runtime·FixAlloc_Free(&h->spanalloc, t);\
 		mstats.mspan_inuse = h->spanalloc.inuse;\
 		mstats.mspan_sys = h->spanalloc.sys;\
 	}\
-	if(p+s->npages < nelem(h->map) && (t = h->map[p+s->npages]) != nil && t->state != MSpanInUse) {\
+	if(p+s->npages < nelem(h->spans) && (t = h->spans[p+s->npages]) != nil && t->state != MSpanInUse) {\
 		tp = (uintptr*)(t->start<<PageShift);\
 		*sp |= *tp;\t// propagate \"needs zeroing\" mark\
 		s->npages += t->npages;\
 		s->npreleased += t->npreleased;\
-		h->map[p + s->npages - 1] = s;
+		h->spans[p + s->npages - 1] = s;\
 		runtime·MSpanList_Remove(t);\
 		t->state = MSpanDead;\
 		runtime·FixAlloc_Free(&h->spanalloc, t);\

コアとなるコードの解説

上記のコード変更は、Goランタイムのメモリヒープ (mheap) における MSpan のルックアップテーブルの名称を map から spans へと一貫して変更するものです。

  1. struct MHeap の定義変更:

    • src/pkg/runtime/malloc.h において、MHeap 構造体の MSpan** map; フィールドが MSpan** spans; に変更されました。これは、このポインタ配列が MSpan オブジェクトを指し示していることをより明確に示します。
  2. runtime·mallocinit 関数内の変更:

    • src/pkg/runtime/malloc.goc 内の runtime·mallocinit 関数は、Goランタイムのメモリ管理システムを初期化する役割を担っています。
    • spans_size の計算において、sizeof(runtime·mheap.map[0])sizeof(runtime·mheap.spans[0]) に変更されました。これは、spans 配列の各要素のサイズに基づいて、必要なメモリ量を正確に計算するためです。
    • runtime·mheap.map = (MSpan**)p;runtime·mheap.spans = (MSpan**)p; に変更されました。これは、システムから予約されたメモリ領域 pmheap.spans フィールドに割り当てる部分です。
  3. runtime·settype_flush 関数内の変更:

    • src/pkg/runtime/malloc.goc 内の runtime·settype_flush 関数は、型情報に関連するメモリのフラッシュ処理を行います。
    • s = runtime·mheap.map[p];s = runtime·mheap.spans[p]; に変更されました。これは、特定のメモリページ p に対応する MSpanspans 配列からルックアップする処理です。
  4. markonly, flushptrbuf, checkptr 関数内の変更:

    • src/pkg/runtime/mgc0.c 内のこれらの関数は、ガベージコレクションのマーキングフェーズやポインタのチェックに関連しています。
    • これらの関数内で runtime·mheap.map[x] または runtime·mheap.map[q] の形式で MSpan をルックアップしていた箇所が、すべて runtime·mheap.spans[x] または runtime·mheap.spans[q] に変更されました。これは、ガベージコレクタがメモリを走査し、オブジェクトがどの MSpan に属しているかを判断する際に、新しい名称のルックアップテーブルを使用するように更新されたことを意味します。
  5. runtime·MHeap_MapSpans, HaveSpan, MHeap_Grow, runtime·MHeap_Lookup, runtime·MHeap_LookupMaybe, MHeap_FreeLocked 関数内の変更:

    • src/pkg/runtime/mheap.c 内のこれらの関数は、ヒープの拡張、MSpan の割り当てと解放、およびアドレスから MSpan をルックアップするなどの、ヒープ管理の核心的なロジックを実装しています。
    • これらの関数内で h->map[...] の形式で MSpan のルックアップや設定を行っていたすべての箇所が、h->spans[...] に変更されました。これには、spans 配列のサイズ計算、SysMap によるメモリマッピング、MSpan の結合・分割、およびアドレスからの MSpan 検索などが含まれます。

これらの変更は、機能的には既存の動作を維持しつつ、コードベース全体で MSpan ルックアップテーブルの名称を統一し、その役割をより明確にすることで、Goランタイムのメモリ管理コードの可読性と保守性を大幅に向上させています。これは、将来的なメモリ管理の最適化や機能追加のための堅牢な基盤を築く上で重要なステップです。

関連リンク

参考にした情報源リンク

  • Go Code Review (CL 9791044): runtime: lazily allocate page table の議論
  • Go Code Review (CL 9853046): このコミットの直接的な変更内容
  • Go言語のランタイムソースコード (特に src/pkg/runtime/mheap.c, src/pkg/runtime/malloc.h, src/pkg/runtime/malloc.goc, src/pkg/runtime/mgc0.c)
  • Goのメモリ管理に関する一般的なドキュメントや解説記事 (Goのヒープ、ガベージコレクション、MSpan の概念について)