[インデックス 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ランタイムのメモリ管理、特にヒープ構造とページテーブルに関する基本的な知識が必要です。
- Goランタイムのメモリ管理: Goは独自のガベージコレクタ(GC)を持つため、OSからメモリを直接要求し、それを独自のヒープとして管理します。このヒープは、ユーザープログラムがオブジェクトを割り当てるために使用されます。
mheap
構造体:mheap
はGoランタイムのグローバルなヒープ構造体であり、Goプログラムが使用するすべてのメモリを管理します。これには、空きメモリのリスト、割り当てられたメモリの追跡、およびメモリのページ(OSが管理する最小単位のメモリブロック)へのマッピングが含まれます。MSpan
構造体:MSpan
は、Goランタイムが管理する連続したメモリページのブロックを表す構造体です。ヒープはこれらのMSpan
の集合として構成されます。MSpan
は、そのブロックがどの程度のサイズのオブジェクトを格納するために使用されているか(例えば、小さなオブジェクト用、大きなオブジェクト用など)、または空き状態であるかといった情報を含みます。- ページテーブル(Page Table): 一般的なオペレーティングシステムでは、仮想メモリと物理メモリのマッピングを管理するためにページテーブルを使用します。Goランタイムの文脈では、
mheap
内の「マップ」または「スパン」は、仮想アドレス空間の特定のページがどのMSpan
に属しているかを高速にルックアップするためのデータ構造として機能します。これにより、任意のメモリアドレスがどのMSpan
に対応しているかを効率的に判断できます。 mheap.maps
(変更前): 変更前はmheap.maps
というフィールドが、仮想アドレス空間のページとMSpan
のマッピングを管理していました。これは、特定の仮想アドレスがどのMSpan
に属しているかを迅速に特定するためのルックアップテーブルとして機能していました。mheap.spans
(変更後): このコミットによってmheap.maps
はmheap.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
へと一貫して変更するものです。
-
struct MHeap
の定義変更:src/pkg/runtime/malloc.h
において、MHeap
構造体のMSpan** map;
フィールドがMSpan** spans;
に変更されました。これは、このポインタ配列がMSpan
オブジェクトを指し示していることをより明確に示します。
-
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;
に変更されました。これは、システムから予約されたメモリ領域p
をmheap.spans
フィールドに割り当てる部分です。
-
runtime·settype_flush
関数内の変更:src/pkg/runtime/malloc.goc
内のruntime·settype_flush
関数は、型情報に関連するメモリのフラッシュ処理を行います。s = runtime·mheap.map[p];
がs = runtime·mheap.spans[p];
に変更されました。これは、特定のメモリページp
に対応するMSpan
をspans
配列からルックアップする処理です。
-
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
に属しているかを判断する際に、新しい名称のルックアップテーブルを使用するように更新されたことを意味します。
-
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ランタイムのメモリ管理コードの可読性と保守性を大幅に向上させています。これは、将来的なメモリ管理の最適化や機能追加のための堅牢な基盤を築く上で重要なステップです。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/e17281b39779c18fc73779c81a3741b05ea85485
- Go Code Review (CL 9791044): https://golang.org/cl/9791044
- Go Code Review (CL 9853046): https://golang.org/cl/9853046
参考にした情報源リンク
- 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
の概念について)