[インデックス 16518] ファイルの概要
このコミットは、Goランタイムにおけるネットワークポーラー(netpoll
)のメモリ割り当てメカニズムの変更に関するものです。具体的には、SysAlloc
によるシステムコールベースのメモリ割り当てから、Goランタイム内部のpersistentalloc
による永続的なメモリ割り当てへと切り替えることで、特にWindows環境での仮想メモリ予約の効率を改善することを目的としています。
コミット
commit 5290e551641842151e3c86d65f42a97af2d95f9d
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Sun Jun 9 21:39:47 2013 +0400
runtime: use persistentalloc instead of SysAlloc in netpoll
Especially important for Windows because it reserves VM
only in multiple of 64k.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/10138043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5290e551641842151e3c86d65f42a97af2d95f9d
元コミット内容
runtime: use persistentalloc instead of SysAlloc in netpoll
Especially important for Windows because it reserves VM
only in multiple of 64k.
変更の背景
この変更の背景には、GoランタイムがネットワークI/Oを効率的に処理するために使用するnetpoll
メカニズムにおけるメモリ割り当ての最適化があります。特に、Windowsオペレーティングシステムにおける仮想メモリ(VM)の予約方法が主な動機となっています。
Windowsでは、仮想メモリの予約が64KBの倍数で行われるという特性があります。これは、たとえ小さなメモリ領域が必要な場合でも、システムは最小単位として64KBを予約してしまうことを意味します。netpoll
のような頻繁に小さなデータ構造を割り当てる必要があるコンポーネントにおいて、SysAlloc
(オペレーティングシステムから直接メモリを要求する関数)を使用すると、この64KBの粒度によってメモリの断片化や無駄な予約が頻繁に発生する可能性がありました。
Goランタイムは、ガベージコレクション(GC)の対象とならない、特定の内部データ構造のために永続的なメモリ領域を必要とします。netpoll
が使用するPollDesc
構造体もその一つであり、これらはOSのI/Oメカニズム(例: epoll, kqueue, IOCPなど)の内部から参照されるため、GCの対象外である必要があります。
SysAlloc
を使い続けると、WindowsのVM予約の特性により、PollDesc
のような小さな構造体を多数割り当てる際に、必要以上に多くの仮想メモリが予約され、システムリソースの無駄遣いやパフォーマンスの低下を招く恐れがありました。この問題を解決し、メモリ使用効率を向上させるために、Goランタイムが管理するより粒度の細かい永続メモリプールであるpersistentalloc
を使用するよう変更されました。
前提知識の解説
Goランタイム
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクタ、スケジューラ(ゴルーチンの管理)、メモリ割り当て、ネットワークI/O、システムコールインターフェースなどが含まれます。Goプログラムは、OS上で直接実行されるのではなく、このランタイム上で動作します。
ネットワークポーラー(netpoll)
netpoll
は、Goランタイムが非同期ネットワークI/Oを効率的に処理するためのメカニズムです。Goのコルーチンであるゴルーチンは、ネットワーク操作(ソケットの読み書きなど)を行う際にブロックされることなく、他のゴルーチンが実行できるように設計されています。これを実現するために、netpoll
はOSが提供するI/O多重化メカニズム(Linuxのepoll、macOS/FreeBSDのkqueue、WindowsのIOCPなど)を利用します。
netpoll
は、ネットワークイベント(データが利用可能になった、書き込みが可能になったなど)を監視し、イベントが発生した際に、そのイベントを待機していたゴルーチンをスケジューラに実行可能として通知します。これにより、多数の同時接続を効率的に処理できる「C10K問題」のような課題に対応しています。
PollDesc
構造体
PollDesc
は、netpoll
が個々のネットワークファイルディスクリプタ(ソケットなど)の状態を管理するために使用する内部データ構造です。これには、イベントの登録、待機中のゴルーチンへの通知、タイマー管理など、ポーリング操作に必要な情報が含まれています。この構造体は、OSのI/O多重化メカニズムの内部から参照される可能性があるため、Goのガベージコレクタによって移動されたり解放されたりしてはならない「非GCメモリ」に配置される必要があります。
SysAlloc
とpersistentalloc
SysAlloc
: Goランタイムがオペレーティングシステムから直接メモリを要求するために使用する低レベルの関数です。これは通常、OSのシステムコール(例: WindowsのVirtualAlloc
、Unix系のmmap
)をラップしており、OSのメモリ割り当て粒度(ページサイズなど)に直接影響されます。persistentalloc
: Goランタイムが内部で管理する永続的なメモリプールからメモリを割り当てるための関数です。このメモリはガベージコレクションの対象外であり、ランタイムの起動から終了まで存在し続けるデータ構造(例: スケジューラのデータ、netpoll
のPollDesc
など)のために使用されます。persistentalloc
は、SysAlloc
によってOSから取得した大きなメモリブロックを、より小さな単位で効率的に管理・割り当てることで、OSの割り当て粒度による無駄を削減します。
Windowsの仮想メモリ予約の粒度
Windowsオペレーティングシステムでは、VirtualAlloc
などのAPIを使用して仮想メモリを予約する際、その粒度が比較的大きい(通常64KB)という特徴があります。これは、アプリケーションがたとえ数バイトのメモリを要求したとしても、OSは物理メモリを割り当てる前に、まず仮想アドレス空間上で64KBのブロックを予約するという意味です。この予約は、実際に物理メモリがコミットされる(使用される)前に行われます。小さなオブジェクトを頻繁に割り当てる場合、この大きな予約粒度がメモリの無駄につながることがあります。
技術的詳細
このコミットは、src/pkg/runtime/netpoll.goc
ファイル内のallocPollDesc
関数におけるメモリ割り当て方法を変更しています。
allocPollDesc
関数は、netpoll
が使用するPollDesc
構造体を割り当てる役割を担っています。このPollDesc
構造体は、前述の通り、OSのI/O多重化メカニズムの内部から参照される可能性があるため、Goのガベージコレクタの管理外のメモリに配置される必要があります。
変更前は、runtime·SysAlloc(n*sizeof(*pd));
を使用して、直接OSからメモリを割り当てていました。ここでn*sizeof(*pd)
は、必要なPollDesc
構造体の合計サイズを示します。しかし、Windowsの仮想メモリ予約の粒度が64KBであるため、PollDesc
のような比較的小さな構造体を個別にSysAlloc
で割り当てるたびに、たとえ数KBしか必要なくても64KBの仮想メモリが予約されてしまい、効率が悪かったのです。
変更後は、runtime·persistentalloc(n*sizeof(*pd), 0);
を使用するようになりました。persistentalloc
は、Goランタイムが内部で管理する永続的なメモリプールからメモリを割り当てます。このプールは、Goランタイムが起動時にSysAlloc
を使ってOSから大きなメモリブロックをまとめて取得し、それをより小さな単位で効率的に管理・割り当てることで、OSの割り当て粒度による無駄を回避します。
この切り替えにより、PollDesc
構造体の割り当てが、Windowsの64KB予約粒度の影響を受けにくくなり、より効率的なメモリ利用が可能になります。特に、多数のネットワーク接続を扱うサーバーアプリケーションなどでは、PollDesc
構造体の割り当てが頻繁に行われるため、この最適化は全体的なメモリフットプリントとパフォーマンスに寄与します。
persistentalloc
の第二引数0
は、割り当てられたメモリがアラインメントを必要としないことを示しています。PollDesc
構造体は通常、特定の厳密なアラインメント要件を持たないため、これは適切な設定です。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/netpoll.goc
ファイル内のallocPollDesc
関数にあります。
--- a/src/pkg/runtime/netpoll.goc
+++ b/src/pkg/runtime/netpoll.goc
@@ -338,7 +338,7 @@ allocPollDesc(void)
tn = 1;
// Must be in non-GC memory because can be referenced
// only from epoll/kqueue internals.
- pd = runtime·SysAlloc(n*sizeof(*pd));
+ pd = runtime·persistentalloc(n*sizeof(*pd), 0);
for(i = 0; i < n; i++) {
pd[i].link = pollcache.first;
pollcache.first = &pd[i];
コアとなるコードの解説
上記の差分は、allocPollDesc
関数内でPollDesc
構造体の配列を割り当てる行を変更しています。
-
変更前:
pd = runtime·SysAlloc(n*sizeof(*pd));
この行では、
runtime·SysAlloc
関数を呼び出して、n
個のPollDesc
構造体を格納するのに十分なサイズのメモリをオペレーティングシステムから直接要求していました。SysAlloc
はOSのメモリ割り当てAPI(WindowsではVirtualAlloc
など)を直接呼び出すため、OSのメモリ予約粒度(Windowsでは64KB)の影響を直接受けます。 -
変更後:
pd = runtime·persistentalloc(n*sizeof(*pd), 0);
この行では、
runtime·persistentalloc
関数を呼び出して、Goランタイムが管理する永続的なメモリプールからメモリを割り当てています。persistentalloc
は、Goランタイムが事前にOSから大きなブロックで取得したメモリを、より細かい粒度で効率的に割り当てるメカニズムを提供します。これにより、特にWindowsの64KB仮想メモリ予約の制約を回避し、メモリの無駄を削減します。第二引数の0
は、割り当てられたメモリのアラインメント要件がないことを示します。
この変更により、netpoll
がPollDesc
構造体を割り当てる際のメモリ効率が向上し、特にWindows環境でのリソース利用が最適化されます。
関連リンク
- Goのネットワークポーラーに関する議論(一般的な情報源):
- Goのメモリ管理に関する一般的な情報源:
- Go Memory Management (公式ドキュメント)
参考にした情報源リンク
- コミットメッセージと差分: https://github.com/golang/go/commit/5290e551641842151e3c86d65f42a97af2d95f9d
- Goの
persistentalloc
に関する一般的な情報(Goソースコード内のコメントや関連する設計ドキュメントなど) - Windowsの
VirtualAlloc
および仮想メモリ管理に関するMicrosoftのドキュメント - Goランタイムの
netpoll
実装に関する一般的な知識 - Goのガベージコレクションとメモリ割り当てに関する一般的な知識