[インデックス 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.htmlVirtualAllocfunction (Microsoft Learn): https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocVirtualFreefunction (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 (これも一般的な情報源であり、特定のコミットに直接関連するものではありませんが、背景知識として有用です。)