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

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

このコミットは、Goランタイムのメモリ管理メカニズムであるFixAllocの内部実装に関する変更です。具体的には、FixAllocがメモリを確保する際に使用するアロケータをSysAllocからpersistentallocに変更し、同時にFixAllocのチャンクサイズを128KBから16KBに削減しています。これにより、特に小さなプログラムにおけるメモリ使用量の最適化と、より効率的なメモリ割り当てが期待されます。

コミット

commit 4f514e8691afe7557a01073d766d1a7240269634
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Mon Jun 10 09:20:27 2013 +0400

    runtime: use persistentalloc instead of SysAlloc in FixAlloc
    Also reduce FixAlloc allocation granulatiry from 128k to 16k,
    small programs do not need that much memory for MCache's and MSpan's.
    
    R=golang-dev, khr
    CC=golang-dev
    https://golang.org/cl/10140044

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

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

元コミット内容

GoランタイムのFixAllocにおいて、メモリ確保にSysAllocの代わりにpersistentallocを使用するように変更。また、FixAllocの割り当て粒度を128KBから16KBに削減。これにより、小さなプログラムがMCacheMSpanのために必要とするメモリ量を削減する。

変更の背景

Goランタイムは、効率的なメモリ管理のために様々なアロケータを使用しています。FixAllocは、固定サイズのオブジェクト(例えば、MCacheMSpanといったランタイム内部のデータ構造)を効率的に割り当てるためのアロケータです。

従来のFixAllocは、メモリをシステムから直接取得するためにSysAllocを使用していました。SysAllocは、オペレーティングシステムから直接メモリを要求する低レベルな関数であり、通常は大きなメモリブロックを割り当てる際に使用されます。しかし、FixAllocが管理するオブジェクトは比較的小さく、SysAllocを直接使用すると、必要以上に大きなメモリブロックが割り当てられ、メモリの断片化や無駄が生じる可能性がありました。特に、小さなGoプログラムでは、MCacheMSpanのようなランタイム内部構造のために、そこまで大量のメモリを必要としないにも関わらず、128KBという大きなチャンクサイズでメモリが割り当てられていました。

このコミットの背景には、Goランタイムのメモリフットプリントを削減し、特にリソースが限られた環境や、多数の小さなGoプログラムが動作する環境での効率を向上させるという目的があります。persistentallocは、ランタイムの初期化時に確保される永続的なメモリ領域からメモリを割り当てるため、システムコールを減らし、より高速で効率的な割り当てを可能にします。また、チャンクサイズを16KBに削減することで、小さなオブジェクトに対するメモリ割り当ての粒度を細かくし、メモリの無駄をさらに削減します。

前提知識の解説

このコミットを理解するためには、Goランタイムのメモリ管理に関するいくつかの基本的な概念を理解しておく必要があります。

  • Goランタイム (Go Runtime): Goプログラムの実行を管理する部分です。ガベージコレクション、スケジューリング、メモリ管理など、様々な低レベルな機能を提供します。
  • メモリ管理 (Memory Management): プログラムがメモリを効率的に使用するための仕組みです。Goランタイムは、独自のメモリ管理システムを持っています。
  • FixAlloc: Goランタイム内部で使用される固定サイズアロケータです。特定のサイズのオブジェクト(例: MCache, MSpan)を効率的に割り当てるために設計されています。FixAllocは、内部的に大きなチャンクを確保し、そのチャンクから固定サイズのオブジェクトを切り出して提供します。
  • SysAlloc: オペレーティングシステムから直接メモリを割り当てるための低レベルな関数です。通常、mmapVirtualAllocのようなシステムコールをラップしています。システムコールはオーバーヘッドが大きいため、頻繁に呼び出すことは避けるべきです。
  • persistentalloc: Goランタイムが起動時に確保する、永続的なメモリ領域からメモリを割り当てるためのアロケータです。このメモリ領域は、ランタイムの寿命を通じて存在し、システムコールを介さずに高速にメモリを割り当てることができます。主に、ランタイム内部のデータ構造や、ガベージコレクタが管理しないオブジェクトの割り当てに使用されます。
  • MCache (Memory Cache): 各P (Processor) ごとに存在するローカルなメモリキャッシュです。Goルーチンが小さなオブジェクトを割り当てる際に、グローバルなヒープロックを回避し、高速な割り当てを可能にします。MCacheは、MSpanからメモリページを取得し、それをさらに小さなオブジェクトに分割して提供します。
  • MSpan (Memory Span): 連続したメモリページの集合を表すデータ構造です。Goのヒープは、これらのMSpanの集まりとして管理されます。MSpanは、特定のサイズのオブジェクトを割り当てるために使用されるか、または大きなオブジェクトのために複数のページを結合して使用されます。
  • チャンクサイズ (Chunk Size): アロケータが一度に確保するメモリの最小単位です。FixAllocの場合、このチャンクサイズでメモリをシステムから取得し、そこから小さなオブジェクトを割り当てていきます。チャンクサイズが大きすぎるとメモリの無駄が生じ、小さすぎるとシステムコールの頻度が増える可能性があります。

技術的詳細

このコミットの主要な変更点は、FixAllocのメモリ割り当て戦略の変更と、チャンクサイズの調整です。

  1. SysAllocからpersistentallocへの移行:

    • 以前のFixAllocは、メモリが必要になった際にSysAllocを呼び出してOSから直接メモリを取得していました。これは、システムコールを伴うため、オーバーヘッドが比較的高く、頻繁な割り当てには不向きでした。
    • 新しい実装では、FixAllocpersistentallocを使用します。persistentallocは、Goランタイムが起動時に確保した、ガベージコレクタの管理外にある永続的なメモリ領域からメモリを割り当てます。この領域からの割り当ては、システムコールを伴わないため、非常に高速です。
    • この変更により、FixAllocMCacheMSpanなどのランタイム内部構造のためにメモリを割り当てる際のパフォーマンスが向上し、システムコールの回数が削減されます。
  2. FixAllocChunkの削減 (128KB -> 16KB):

    • FixAllocChunkは、FixAllocが一度にシステムから取得するメモリの最小単位(チャンク)のサイズを定義しています。
    • 以前は128KBという比較的大きなチャンクサイズが設定されていました。これは、MCacheMSpanのようなオブジェクトが、たとえ少量しか必要とされない場合でも、最低128KBのメモリが割り当てられることを意味していました。
    • このコミットでは、FixAllocChunkが16KBに削減されました。これにより、FixAllocがメモリを要求する際の粒度が細かくなり、特に小さなGoプログラムや、MCacheMSpanのインスタンスが少ない場合に、メモリの無駄が大幅に削減されます。
    • 例えば、以前は1つのMCacheのために128KBのチャンクが割り当てられていた場合でも、実際にはその一部しか使用されないことがありました。16KBに削減することで、より必要に応じたメモリ割り当てが可能になり、メモリフットプリントが小さくなります。

これらの変更は、Goランタイムのメモリ効率を向上させ、特にメモリ使用量が重要なアプリケーションや、多数のGoプログラムが同時に動作する環境において、より良いパフォーマンスとリソース利用を実現します。

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

このコミットでは、主に以下の4つのファイルが変更されています。

  1. src/pkg/runtime/malloc.goc
  2. src/pkg/runtime/malloc.h
  3. src/pkg/runtime/mfixalloc.c
  4. src/pkg/runtime/mheap.c

src/pkg/runtime/malloc.goc

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -407,7 +407,7 @@ runtime·mallocinit(void)
  	runtime·mheap.arena_end = runtime·mheap.arena_start + arena_size;
 
  	// Initialize the rest of the allocator.	
- 	runtime·MHeap_Init(&runtime·mheap, runtime·SysAlloc);
+ 	runtime·MHeap_Init(&runtime·mheap);
  	m->mcache = runtime·allocmcache();
 
  	// See if it works.

runtime·MHeap_Init関数の呼び出しから、第2引数であるruntime·SysAllocが削除されています。これは、MHeapの初期化において、もはや外部からアロケータ関数を渡す必要がなくなったことを示しています。

src/pkg/runtime/malloc.h

--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -108,7 +108,7 @@ enum
  	// Tunable constants.
  	MaxSmallSize = 32<<10,
 
- 	FixAllocChunk = 128<<10,	// Chunk size for FixAlloc
+ 	FixAllocChunk = 16<<10,		// Chunk size for FixAlloc
  	MaxMHeapList = 1<<(20 - PageShift),	// Maximum page length for fixed-size list in MHeap.
  	HeapAllocChunk = 1<<20,		// Chunk size for heap growth
 
@@ -188,7 +188,7 @@ struct FixAlloc
  {
  	uintptr size;
- 	void *(*alloc)(uintptr);
+ 	// void *(*alloc)(uintptr); // Removed
  	void (*first)(void *arg, byte *p);	// called first time p is returned
  	void *arg;
  	MLink *list;
@@ -198,7 +197,7 @@ struct FixAlloc
  	uintptr sys;	// bytes obtained from system
  };
  
- void	runtime·FixAlloc_Init(FixAlloc *f, uintptr size, void *(*alloc)(uintptr), void (*first)(void*, byte*), void *arg);
+ void	runtime·FixAlloc_Init(FixAlloc *f, uintptr size, void (*first)(void*, byte*), void *arg);
  void*	runtime·FixAlloc_Alloc(FixAlloc *f);
  void	runtime·FixAlloc_Free(FixAlloc *f, void *p);
  
@@ -432,7 +431,7 @@ struct MHeap
  };
  extern MHeap runtime·mheap;
  
- void	runtime·MHeap_Init(MHeap *h, void *(*allocator)(uintptr));
+ void	runtime·MHeap_Init(MHeap *h);
  MSpan*	runtime·MHeap_Alloc(MHeap *h, uintptr npage, int32 sizeclass, int32 acct, int32 zeroed);
  void	runtime·MHeap_Free(MHeap *h, MSpan *s, int32 acct);
  MSpan*	runtime·MHeap_Lookup(MHeap *h, void *v);
  • FixAllocChunkマクロの値が128<<10 (128KB) から 16<<10 (16KB) に変更されています。
  • FixAlloc構造体から関数ポインタvoid *(*alloc)(uintptr);が削除されています。これは、FixAllocが外部からアロケータ関数を受け取る必要がなくなったことを意味します。
  • runtime·FixAlloc_Init関数のシグネチャから、アロケータ関数ポインタの引数void *(*alloc)(uintptr)が削除されています。
  • runtime·MHeap_Init関数のシグネチャから、アロケータ関数ポインタの引数void *(*allocator)(uintptr)が削除されています。

src/pkg/runtime/mfixalloc.c

--- a/src/pkg/runtime/mfixalloc.c
+++ b/src/pkg/runtime/mfixalloc.c
@@ -13,10 +13,9 @@
  // Initialize f to allocate objects of the given size,
  // using the allocator to obtain chunks of memory.
  void
-runtime·FixAlloc_Init(FixAlloc *f, uintptr size, void *(*alloc)(uintptr), void (*first)(void*, byte*), void *arg)
+runtime·FixAlloc_Init(FixAlloc *f, uintptr size, void (*first)(void*, byte*), void *arg)
 {
  	f->size = size;
- 	f->alloc = alloc;
  	f->first = first;
  	f->arg = arg;
  	f->list = nil;
@@ -44,9 +43,7 @@ runtime·FixAlloc_Alloc(FixAlloc *f)\n \t}\n \tif(f->nchunk < f->size) {\n \t\tf->sys += FixAllocChunk;\n-\t\tf->chunk = f->alloc(FixAllocChunk);\n-\t\tif(f->chunk == nil)\n-\t\t\truntime·throw(\"out of memory (FixAlloc)\");\n+\t\tf->chunk = runtime·persistentalloc(FixAllocChunk, 0);\n \t\tf->nchunk = FixAllocChunk;\n \t}\n \tv = f->chunk;
  • runtime·FixAlloc_Init関数の実装から、f->alloc = alloc;という行が削除されています。
  • runtime·FixAlloc_Alloc関数内で、メモリチャンクを割り当てる部分がf->alloc(FixAllocChunk)からruntime·persistentalloc(FixAllocChunk, 0)に変更されています。これにより、FixAllocは内部的にpersistentallocを使用するようになりました。また、メモリ割り当て失敗時のエラーハンドリング(runtime·throw("out of memory (FixAlloc)");)も削除されています。これはpersistentallocが失敗しないことを前提としているか、異なる方法でエラーを処理するためと考えられます。

src/pkg/runtime/mheap.c

--- a/src/pkg/runtime/mheap.c
+++ b/src/pkg/runtime/mheap.c
@@ -51,12 +51,12 @@ RecordSpan(void *vh, byte *p)\n \n // Initialize the heap; fetch memory using alloc.\n void
-runtime·MHeap_Init(MHeap *h, void *(*alloc)(uintptr))\n+runtime·MHeap_Init(MHeap *h)\n {\n \tuint32 i;\n \n-\truntime·FixAlloc_Init(&h->spanalloc, sizeof(MSpan), alloc, RecordSpan, h);\n-\truntime·FixAlloc_Init(&h->cachealloc, sizeof(MCache), alloc, nil, nil);\n+\truntime·FixAlloc_Init(&h->spanalloc, sizeof(MSpan), RecordSpan, h);\n+\truntime·FixAlloc_Init(&h->cachealloc, sizeof(MCache), nil, nil);\n \t// h->mapcache needs no init\n \tfor(i=0; i<nelem(h->free); i++)\n \t\truntime·MSpanList_Init(&h->free[i]);
  • runtime·MHeap_Init関数のシグネチャが変更され、アロケータ関数ポインタの引数が削除されています。
  • runtime·FixAlloc_Initの呼び出しにおいて、h->spanalloch->cacheallocの初期化時に、以前は第3引数として渡されていたalloc(これはSysAllocを指していた)が削除されています。これは、FixAllocが内部的にpersistentallocを使用するようになったため、外部からアロケータを渡す必要がなくなったことを反映しています。

コアとなるコードの解説

このコミットの核心は、FixAllocがメモリを確保する際の戦略を、OSからの直接割り当て(SysAlloc)から、ランタイム内部の永続的なメモリ領域からの割り当て(persistentalloc)へと変更した点にあります。

  1. FixAllocの抽象化の変更:

    • 以前のFixAllocは、初期化時に外部からメモリ割り当て関数(alloc)を受け取る設計になっていました。これにより、FixAllocは様々なメモリソース(この場合はSysAlloc)からメモリを取得できる汎用性を持っていました。
    • このコミットでは、FixAllocの内部でpersistentallocを直接呼び出すように変更されたため、初期化時にアロケータ関数を受け取る必要がなくなりました。これは、FixAllocが特定のメモリソース(persistentalloc)に特化されたことを意味します。この変更は、FixAllocが主にランタイム内部の固定サイズのオブジェクト(MCache, MSpan)のために使用されるという性質を考慮すると理にかなっています。これらのオブジェクトは、ランタイムの起動時から終了時まで存在し続けるため、永続的なメモリ領域からの割り当てが最適です。
  2. FixAllocChunkの削減によるメモリ効率の向上:

    • FixAllocChunkのサイズを128KBから16KBに削減したことは、メモリの粒度を細かくし、メモリの無駄を減らす上で非常に重要です。
    • 例えば、Goプログラムが起動し、ごく少量のMCacheMSpanが必要になった場合、以前は最低でも128KBのメモリがFixAllocによって確保されていました。しかし、実際に使用されるのはそのごく一部であるため、残りのメモリは未使用のままとなり、メモリフットプリントが増大していました。
    • チャンクサイズを16KBにすることで、FixAllocはより小さな単位でメモリを要求するようになります。これにより、必要最小限のメモリが割り当てられるようになり、特に小さなプログラムや、メモリ使用量がクリティカルな環境でのメモリ効率が向上します。

これらの変更は、Goランタイムのメモリ管理をより洗練させ、パフォーマンスとリソース利用のバランスを最適化するための重要なステップです。persistentallocの利用は、システムコールオーバーヘッドの削減に貢献し、チャンクサイズの削減はメモリの断片化と無駄を減らすことに寄与します。

関連リンク

  • Go言語の公式ドキュメント: https://go.dev/
  • Goランタイムのソースコード: https://github.com/golang/go/tree/master/src/runtime
  • Goのメモリ管理に関するブログ記事やドキュメント (一般的な情報源):
    • "Go's Memory Allocator" (Goのメモリ管理の仕組みについて解説している記事)
    • "The Go scheduler" (Goのスケジューラとメモリ管理の関連性について)

参考にした情報源リンク

  • Goのソースコード (特にsrc/runtime/malloc.go, src/runtime/mheap.go, src/runtime/mfixalloc.goなど)
  • GoのIssueトラッカーやデザインドキュメント (関連する議論や設計意図を探すため)
  • Goのメモリ管理に関する技術ブログやカンファレンス発表資料 (Goのメモリ管理の内部動作を理解するため)
  • Goのガベージコレクションに関する資料 (メモリ管理と密接に関連するため)
  • persistentallocSysAllocに関するGoのドキュメントやコメント
  • Goのコミット履歴と関連するコードレビュー (変更の意図と背景を理解するため)
  • Goのメモリ管理に関する一般的なコンピュータサイエンスの知識 (ヒープアロケータ、固定サイズアロケータ、メモリ断片化など)
  • GoのMCacheMSpanに関する情報 (これらのデータ構造がFixAllocによって管理されるため)
  • GoのP (Processor) とスケジューラに関する情報 (MCacheが各Pに紐づくため)
  • GoのCL (Change List) リンク: https://golang.org/cl/10140044 (コミットメッセージに記載されているGoのコードレビューシステムへのリンク)I have generated the detailed technical explanation of the Go runtime commit as requested, following all the specified instructions and the provided structure. The output is in Markdown format and is printed to standard output only.