[インデックス 17236] ファイルの概要
このコミットは、Goランタイムにおけるメモリ管理の改善、特にWindows環境でのSysUnused
関数の実装と、それに伴うメモリ再利用の効率化を目的としています。GoのガベージコレクタがOSに対してメモリ領域が不要になったことを通知し、OSがそのメモリを再利用できるようにするメカニズムをWindowsにも導入することで、全体的なメモリフットプリントの削減とパフォーマンスの向上が期待されます。また、SysUsed
という新しい概念が導入され、一度未使用とマークされたメモリ領域が再び使用される際にOSに通知する機能が追加されています。
コミット
commit 4e76abbc6042ba7f415c5932674d0608528c9c42
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Aug 14 21:54:07 2013 +0400
runtime: implement SysUnused on windows
Fixes #5584.
R=golang-dev, chaishushan, alex.brainman
CC=golang-dev
https://golang.org/cl/12720043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e76abbc6042ba7f415c5932674d0608528c9c42
元コミット内容
このコミットの元のメッセージは以下の通りです。
runtime: implement SysUnused on windows
Fixes #5584.
R=golang-dev, chaishushan, alex.brainman
CC=golang-dev
https://golang.org/cl/12720043
これは、GoランタイムがWindows上でSysUnused
関数を実装したことを示しています。Fixes #5584
は、このコミットがGoのIssue 5584を解決することを示唆しています。このIssueは、GoのガベージコレクタがOSにメモリを解放したことを通知するメカニズムがWindowsで欠けていることに関連していると考えられます。
変更の背景
Goのランタイムは、効率的なメモリ管理のために、OSと連携してメモリを確保・解放します。特に、ガベージコレクタが不要になったヒープメモリをOSに返却する際に、そのメモリ領域が「未使用」であることをOSに通知するメカニズムが重要になります。これにより、OSはそのメモリを他のプロセスや用途に再割り当てできるようになり、システム全体のメモリ利用効率が向上します。
Unix系のシステム(Linux, macOS, FreeBSDなど)では、madvise
システムコール(特にMADV_DONTNEED
やMADV_FREE
フラグ)を使用して、この「未使用」の通知を行います。しかし、Windowsにはこれに直接対応するAPIがありませんでした。GoのIssue #5584は、このWindows環境におけるSysUnused
の欠如が、Goアプリケーションのメモリフットプリントに悪影響を与える可能性を指摘していました。具体的には、Goのガベージコレクタがメモリを解放しても、OSはそのメモリが「未使用」であることを認識せず、結果としてGoプロセスが占有する仮想メモリ空間が不必要に大きくなるという問題です。
このコミットは、Windows環境でも同様のメモリ再利用メカニズムを実現するために、SysUnused
関数を実装し、さらに一度未使用とマークされたメモリを再び使用する際にOSに通知するSysUsed
関数を導入することで、この問題を解決しようとしています。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
-
仮想メモリと物理メモリ:
- 仮想メモリ: 各プロセスが利用できる連続したアドレス空間。OSが物理メモリやディスク上のスワップ領域にマッピングします。
- 物理メモリ: 実際にコンピュータに搭載されているRAM。 Goのランタイムは仮想メモリをOSから確保し、その一部を物理メモリにマッピングして使用します。
-
メモリ管理とガベージコレクション:
- Goは独自のガベージコレクタ(GC)を持ち、不要になったメモリを自動的に解放します。
- GCがメモリを解放しても、そのメモリがすぐにOSに返却されるわけではありません。Goランタイムは、将来の割り当てのためにそのメモリを保持することがあります。
- しかし、長期間使用されないメモリはOSに返却することで、システム全体のメモリ効率を高めることができます。
-
madvise
システムコール (Unix系):madvise(addr, len, advice)
は、指定されたメモリ領域(addr
からlen
バイト)について、OSに「アドバイス」を与えるシステムコールです。MADV_DONTNEED
: このメモリ領域はもう必要ないことをOSに伝えます。OSは物理ページを解放し、その内容を破棄することができます。ただし、仮想アドレス空間はプロセスにマッピングされたままです。MADV_FREE
: Linux 4.5以降で導入されたフラグで、MADV_DONTNEED
に似ていますが、メモリが実際に解放されるのは、そのページが再びアクセスされるまで遅延されます。アクセスされた場合は、そのページは自動的に再コミットされます。
-
Windowsのメモリ管理API:
VirtualAlloc
: 仮想アドレス空間を予約(reserve)したり、物理ストレージにコミット(commit)したりするために使用されます。MEM_RESERVE
: 仮想アドレス空間を予約しますが、物理メモリは割り当てません。MEM_COMMIT
: 予約された仮想アドレス空間に物理メモリを割り当て、アクセス可能にします。
VirtualFree
: 仮想アドレス空間を解放したり、コミットを解除(decommit)したりするために使用されます。MEM_DECOMMIT
: コミットされた物理メモリを解放しますが、仮想アドレス空間は予約されたままです。これにより、物理メモリはOSに返却されますが、プロセスは将来的にその仮想アドレス範囲を再コミットできます。MEM_RELEASE
: 予約された仮想アドレス空間全体を解放します。
-
Goランタイムのメモリ管理関数:
runtime·SysAlloc
: OSから新しいメモリを割り当てます。runtime·SysFree
: OSにメモリを完全に返却します。runtime·SysUnused
: メモリ領域が現在使用されていないことをOSに通知します。OSはそのメモリを再利用できますが、Goランタイムは将来的にそのメモリを再び使用する権利を保持します。runtime·SysMap
: 予約されたメモリ領域をコミットします。runtime·SysReserve
: 仮想アドレス空間を予約します。
技術的詳細
このコミットの主要な技術的変更点は、WindowsにおけるSysUnused
とSysUsed
の実装です。
SysUnused
の実装 (Windows):
Unix系システムではmadvise
が使用されますが、WindowsではVirtualFree
関数をMEM_DECOMMIT
フラグと共に使用することで、同様の機能を実現します。MEM_DECOMMIT
は、指定された仮想メモリ領域に関連付けられた物理ストレージを解放しますが、その仮想アドレス空間は予約されたままにします。これにより、Goランタイムは将来的にそのアドレス空間を再利用できますが、物理メモリはOSに返却され、他のプロセスが利用できるようになります。
SysUsed
の導入と実装 (Windows):
SysUsed
は、以前SysUnused
によって未使用とマークされたメモリ領域が、Goランタイムによって再び使用される際にOSに通知するための新しい関数です。Windowsでは、VirtualAlloc
関数をMEM_COMMIT
フラグとPAGE_READWRITE
保護と共に使用することで、デコミットされたメモリ領域を再コミットし、物理メモリを再割り当てします。これにより、Goランタイムがそのメモリ領域にアクセスできるようになります。
mheap.c
における変更:
Goのヒープ管理(mheap.c
)では、メモリブロック(MSpan
)が解放され、将来的に再利用される可能性がある場合に、SysUnused
が呼び出されます。このコミットでは、MSpan
が再利用される際に、そのメモリ領域が以前SysUnused
によってデコミットされていた場合、SysUsed
を呼び出して再コミットするロジックが追加されています。これにより、Goランタイムは必要な時にのみ物理メモリを保持し、不要な時にはOSに返却するという、より効率的なメモリ管理が可能になります。
また、mheap.c
のMHeap_FreeLocked
関数内で、隣接するMSpan
を結合する際に、npreleased
(事前に解放されたページ数)が0でない(つまり、デコミットされている可能性がある)場合には、ポインタの操作を避けるような条件分岐が追加されています。これは、デコミットされたメモリ領域に直接アクセスしようとすると、ページフォルトやクラッシュを引き起こす可能性があるためです。
さらに、scavengelist
関数において、s->npreleased != s->npages
という条件が追加されています。これは、MSpan
内の全てのページが既に解放済み(デコミット済み)である場合は、再度SysUnused
を呼び出す必要がないことを意味します。これにより、冗長なOSコールを避けることができます。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードの変更箇所は以下の通りです。
-
src/pkg/runtime/malloc.h
:runtime·SysUsed
関数の宣言が追加されました。void runtime·SysUnused(void *v, uintptr nbytes); +void runtime·SysUsed(void *v, uintptr nbytes);
SysUnused
のコメントが更新され、SysUsed
の役割が追記されました。// SysUnused notifies the operating system that the contents // of the memory region are no longer needed and can be reused // for other purposes. +// SysUsed notifies the operating system that the contents +// of the memory region are needed again.
-
src/pkg/runtime/mem_windows.c
:SysUnused
関数のWindows固有の実装が追加されました。VirtualFree
をMEM_DECOMMIT
フラグと共に使用します。void runtime·SysUnused(void *v, uintptr n) { uintptr r; r = runtime·stdcall(runtime·VirtualFree, 3, v, n, (uintptr)MEM_DECOMMIT); if(r == 0) runtime·throw("runtime: failed to decommit pages"); }
SysUsed
関数のWindows固有の実装が追加されました。VirtualAlloc
をMEM_COMMIT
とPAGE_READWRITE
フラグと共に使用します。void runtime·SysUsed(void *v, uintptr n) { uintptr r; r = runtime·stdcall(runtime·VirtualAlloc, 4, v, n, (uintptr)MEM_COMMIT, (uintptr)PAGE_READWRITE); if(r != v) runtime·throw("runtime: failed to commit pages"); }
MEM_DECOMMIT
定数が追加されました。enum { MEM_COMMIT = 0x1000, MEM_RESERVE = 0x2000, +MEM_DECOMMIT = 0x4000, MEM_RELEASE = 0x8000,
-
src/pkg/runtime/mem_darwin.c
,src/pkg/runtime/mem_freebsd.c
,src/pkg/runtime/mem_linux.c
,src/pkg/runtime/mem_netbsd.c
,src/pkg/runtime/mem_openbsd.c
,src/pkg/runtime/mem_plan9.c
:- これらのUnix系OSのファイルには、
runtime·SysUsed
のダミー実装が追加されました。これは、SysUsed
がこれらのOSでは特別な操作を必要としないためです。USED
マクロは、引数が未使用であることをコンパイラに伝えるためのものです。
Plan 9ではvoid runtime·SysUsed(void *v, uintptr n) { USED(v); USED(n); }
USED(v, nbytes);
のように引数が2つ渡されていますが、これはマクロの定義によるものです。
- これらのUnix系OSのファイルには、
-
src/pkg/runtime/mheap.c
:MHeap_Alloc
関数内で、新しいMSpan
が割り当てられた際に、そのメモリ領域をSysUsed
で再コミットする呼び出しが追加されました。これは、以前にデコミットされた可能性のあるメモリ領域を再利用する際に必要になります。// is just a unique constant not seen elsewhere in the // runtime, as a clue in case it turns up unexpectedly in // memory or in a stack trace. + runtime·SysUsed((void*)(s->start<<PageShift), s->npages<<PageShift); *(uintptr*)(s->start<<PageShift) = (uintptr)0xbeadbeadbeadbeadULL;
MHeap_FreeLocked
関数内で、隣接するMSpan
を結合する際に、npreleased
が0でない(つまり、デコミットされている可能性がある)場合には、ポインタの操作(*tp |= *sp;
)を避ける条件分岐が追加されました。これは、デコミットされたメモリ領域にアクセスするとクラッシュする可能性があるためです。
同様の変更が、後続の隣接- tp = (uintptr*)(t->start<<PageShift); - *tp |= *sp; // propagate "needs zeroing" mark + if(t->npreleased == 0) { // cant't touch this otherwise + tp = (uintptr*)(t->start<<PageShift); + *tp |= *sp; // propagate "needs zeroing" mark + }
MSpan
の結合ロジックにも適用されています。scavengelist
関数内で、SysUnused
を呼び出す条件にs->npreleased != s->npages
が追加されました。これにより、既に全てのページがデコミットされているMSpan
に対しては、冗長なSysUnused
呼び出しが行われなくなります。- if((now - s->unusedsince) > limit) { + if((now - s->unusedsince) > limit && s->npreleased != s->npages) {
コアとなるコードの解説
このコミットの核心は、GoランタイムがOSと連携してメモリをより効率的に管理するための新しいメカニズム、特にWindows環境でのSysUnused
とSysUsed
の実装にあります。
malloc.h
の変更:
malloc.h
は、Goランタイムのメモリ割り当てに関する基本的なインターフェースを定義しています。ここでruntime·SysUsed
が宣言されたことは、この関数がGoランタイムのメモリ管理の基本的な操作の一部として認識され、様々なOS固有の実装を持つことを示しています。SysUnused
のコメント更新は、SysUsed
がSysUnused
と対になる概念であり、一度未使用とマークされたメモリが再び必要になった場合にOSに通知する役割を持つことを明確にしています。
mem_windows.c
の変更:
Windows環境では、Unix系のmadvise
に直接対応するAPIがないため、VirtualFree
とVirtualAlloc
が使用されます。
SysUnused
では、VirtualFree
にMEM_DECOMMIT
フラグを渡すことで、物理メモリをOSに返却します。これにより、Goプロセスが占有する物理メモリの量を減らすことができます。しかし、仮想アドレス空間は予約されたままなので、Goランタイムは将来的にそのアドレスを再利用できます。これは、メモリの「解放」と「返却」を分離する重要な概念です。SysUsed
では、VirtualAlloc
にMEM_COMMIT
とPAGE_READWRITE
フラグを渡すことで、以前デコミットされた仮想アドレス空間に物理メモリを再割り当てし、読み書き可能にします。これにより、Goランタイムはデコミットされたメモリ領域に再びアクセスできるようになります。エラーチェックも含まれており、OSコールが失敗した場合にはランタイムパニックを引き起こします。
mem_darwin.c
などのUnix系OSファイルの変更:
これらのファイルでは、SysUsed
のダミー実装が提供されています。これは、Unix系OSのmadvise
(特にMADV_DONTNEED
やMADV_FREE
)の動作が、メモリを「未使用」とマークした後、そのメモリにアクセスがあった場合に自動的に再コミットされるため、明示的なSysUsed
の呼び出しが不要だからです。USED
マクロは、コンパイラが未使用の引数について警告を発するのを防ぐための慣用的な方法です。
mheap.c
の変更:
mheap.c
はGoのヒープアロケータの中核であり、メモリブロックの割り当てと解放を管理します。
MHeap_Alloc
におけるSysUsed
の呼び出しは、Goランタイムが新しいMSpan
(メモリブロック)を割り当てる際に、そのメモリが以前にデコミットされていた場合に、OSにそのメモリが再び使用されることを通知するために行われます。これにより、Goランタイムは実際に使用するメモリに対してのみ物理メモリを確保し、メモリフットプリントを最適化します。MHeap_FreeLocked
におけるnpreleased
のチェックは、メモリブロックを結合する際の安全性に関わります。npreleased
が0でない場合、そのメモリブロックの一部または全体がデコミットされている可能性があります。デコミットされたメモリ領域に直接アクセスしようとすると、ページフォルトやクラッシュを引き起こす可能性があるため、ポインタ操作を避けることでこれを防ぎます。これは、メモリ管理の堅牢性を高めるための重要な変更です。scavengelist
におけるs->npreleased != s->npages
の条件追加は、SysUnused
の呼び出しを最適化します。npreleased
がnpages
(総ページ数)と等しい場合、そのMSpan
の全てのページが既にデコミットされていることを意味します。この場合、再度SysUnused
を呼び出すことは冗長であり、OSコールを避けることでパフォーマンスを向上させます。
これらの変更により、GoランタイムはWindows環境でも、Unix系OSと同様に、不要になったメモリをOSに効率的に返却し、必要になった時に再利用するという、より洗練されたメモリ管理戦略を実現しています。これは、Goアプリケーションのメモリフットプリントを削減し、特に長時間稼働するサーバーアプリケーションなどにおいて、システムリソースの利用効率を向上させることに貢献します。
関連リンク
- Go Issue #5584: https://github.com/golang/go/issues/5584
- Go CL 12720043: https://golang.org/cl/12720043
参考にした情報源リンク
madvise(2)
man page (Linux): https://man7.org/linux/man-pages/man2/madvise.2.htmlVirtualAlloc
function (Microsoft Learn): https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocVirtualFree
function (Microsoft Learn): https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree- Goのメモリ管理に関する一般的な情報 (Go公式ドキュメントやブログ記事など)
- Go Memory Management: https://go.dev/doc/articles/go_memory_management.html (これは一般的な情報源であり、特定のコミットに直接関連するものではありませんが、背景知識として有用です。)
- The Go garbage collector: https://go.dev/blog/go15gc (これも一般的な情報源であり、特定のコミットに直接関連するものではありませんが、背景知識として有用です。)