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

[インデックス 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メモリ」に配置される必要があります。

SysAllocpersistentalloc

  • SysAlloc: Goランタイムがオペレーティングシステムから直接メモリを要求するために使用する低レベルの関数です。これは通常、OSのシステムコール(例: WindowsのVirtualAlloc、Unix系のmmap)をラップしており、OSのメモリ割り当て粒度(ページサイズなど)に直接影響されます。
  • persistentalloc: Goランタイムが内部で管理する永続的なメモリプールからメモリを割り当てるための関数です。このメモリはガベージコレクションの対象外であり、ランタイムの起動から終了まで存在し続けるデータ構造(例: スケジューラのデータ、netpollPollDescなど)のために使用されます。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は、割り当てられたメモリのアラインメント要件がないことを示します。

この変更により、netpollPollDesc構造体を割り当てる際のメモリ効率が向上し、特にWindows環境でのリソース利用が最適化されます。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分: https://github.com/golang/go/commit/5290e551641842151e3c86d65f42a97af2d95f9d
  • Goのpersistentallocに関する一般的な情報(Goソースコード内のコメントや関連する設計ドキュメントなど)
  • WindowsのVirtualAllocおよび仮想メモリ管理に関するMicrosoftのドキュメント
  • Goランタイムのnetpoll実装に関する一般的な知識
  • Goのガベージコレクションとメモリ割り当てに関する一般的な知識