[インデックス 11270] ファイルの概要
このコミットは、GoランタイムがDarwin(macOS)上でメモリを解放する際の挙動を改善するために、madviseシステムコールとMADV_FREEフラグのサポートを追加するものです。具体的には、Goのガベージコレクタが不要と判断したメモリ領域をOSに通知し、OSがそのメモリを再利用できるようにするメカニズムを導入しています。
コミット
- Author: Dave Cheney dave@cheney.net
- Date: Thu Jan 19 15:51:29 2012 -0500
- Commit Message:
runtime: madvise and SysUnused for Darwin SysUnused is a direct call to madvise MADV_FREE. R=sebastien.paolacci, rsc, minux.ma CC=golang-dev https://golang.org/cl/5531073
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c354f93b93294406f92fecdc9913b7c050472894
元コミット内容
runtime: madvise and SysUnused for Darwin
SysUnused is a direct call to madvise MADV_FREE.
R=sebastien.paolacci, rsc, minux.ma
CC=golang-dev
https://golang.org/cl/5531073
変更の背景
Go言語のランタイムは、ガベージコレクタ(GC)によって自動的にメモリ管理を行います。GCが不要になったメモリ領域を特定した後、そのメモリをOSに返却したり、再利用可能な状態にしたりするメカニズムが必要です。このプロセスは、特にメモリ使用量が変動するアプリケーションにおいて、システムのメモリ効率を向上させる上で重要です。
以前のGoランタイムのDarwin(macOS)実装では、runtime·SysUnused関数(GoランタイムがOSにメモリが未使用であることを通知するために使用する関数)が、実際には何もしないスタブ(USED(v); USED(n);)となっていました。これは、コメントに// TODO(rsc): call madvise MADV_DONTNEEDとあるように、将来的にmadviseシステムコールを呼び出す計画があったことを示唆しています。
このコミットの背景には、GoランタイムがDarwin上でより効率的にメモリをOSに返却し、システム全体のメモリフットプリントを削減したいという意図があります。特に、LinuxのMADV_DONTNEEDに相当する機能がDarwinにはMADV_FREEとして存在するため、これを利用することで、GoのGCが解放したメモリがOSによって適切に扱われるようになります。これにより、Goアプリケーションが使用する物理メモリ量が最適化され、他のプロセスやシステム全体のパフォーマンスに良い影響を与えることが期待されます。
前提知識の解説
1. madviseシステムコール
madviseは、Unix系OS(Linux, macOS, BSDなど)で利用可能なシステムコールの一つで、プロセスが仮想メモリの特定の範囲について、カーネルに「アドバイス」(助言)を与えるために使用されます。このアドバイスは、カーネルがそのメモリ領域をどのように扱うべきかについてのヒントを提供し、メモリ管理の効率を向上させることを目的としています。madviseは、メモリの解放を強制するものではなく、カーネルがそのアドバイスを考慮して最適な判断を下すためのものです。
madviseシステムコールは通常、以下のような形式で呼び出されます。
int madvise(void *addr, size_t length, int advice);
addr: アドバイスを与えるメモリ領域の開始アドレス。length: アドバイスを与えるメモリ領域の長さ(バイト単位)。advice: カーネルへの具体的なアドバイスを示すフラグ。
2. MADV_DONTNEEDとMADV_FREE
adviceフラグにはいくつかの種類がありますが、このコミットで特に重要なのはMADV_DONTNEEDとMADV_FREEです。
-
MADV_DONTNEED(Linux): このフラグは、指定されたメモリ領域の内容がもはや必要ないことをカーネルに伝えます。カーネルは、このメモリ領域に対応する物理ページを解放し、その内容を破棄することができます。ただし、このメモリ領域はプロセスの仮想アドレス空間からは削除されず、後でアクセスがあった場合にはゼロフィルされたページが割り当てられるか、必要に応じてページインされます。これは、メモリを「解放」するが、仮想アドレス空間は保持したい場合に有用です。 -
MADV_FREE(BSD/macOS):MADV_FREEは、BSD系OS(macOSを含む)で提供されるmadviseのフラグです。これはLinuxのMADV_DONTNEEDと似ていますが、微妙な違いがあります。MADV_FREEは、指定されたメモリ領域がもはや必要ないことをカーネルに伝え、カーネルはその物理ページを解放することができます。しかし、MADV_FREEの場合、そのメモリ領域に書き込みが行われるまで、そのページは解放されずに保持される可能性があります。書き込みが行われた時点で、そのページは解放され、新しいゼロフィルされたページが割り当てられます。これは、メモリを解放しつつも、もしすぐに再利用される可能性がある場合に、物理ページの再割り当てのオーバーヘッドを避けるための最適化として機能します。
このコミットのコメント// Linux's MADV_DONTNEED is like BSD's MADV_FREE.は、GoランタイムがLinuxでMADV_DONTNEEDを使用しているのと同様の目的で、DarwinではMADV_FREEを使用することを示しています。
3. Goランタイムのメモリ管理
Goランタイムは、独自のメモリ管理システム(ガベージコレクタとヒープアロケータ)を持っています。Goプログラムがメモリを要求すると、ランタイムはOSから大きなメモリブロック(アリーナ)を確保し、それをより小さなチャンクに分割してアプリケーションに割り当てます。ガベージコレクタが不要なオブジェクトを回収した後、そのメモリはGoランタイムのヒープ内で再利用可能になりますが、すぐにOSに返却されるわけではありません。SysUnusedのような関数は、GoランタイムがOSに対して、特定のメモリ領域がもはや使用されていないことを通知し、OSがその物理メモリを他の用途に再利用できるようにするためのインターフェースを提供します。
技術的詳細
このコミットの技術的詳細を掘り下げると、GoランタイムがDarwin上でどのようにメモリ管理の効率を向上させているかが明らかになります。
-
madviseシステムコールの導入: Goランタイムは、OSの機能を利用するためにシステムコールを直接呼び出す必要があります。このコミットでは、Darwin上でmadviseシステムコールを呼び出すためのアセンブリコードが追加されています。src/pkg/runtime/sys_darwin_386.sおよびsrc/pkg/runtime/sys_darwin_amd64.sに、runtime·madviseというGoランタイム内部関数が追加されました。- このアセンブリコードは、
madviseシステムコールの番号(Darwinでは75)をレジスタに設定し、必要な引数(メモリのアドレス、長さ、アドバイスフラグ)を適切なレジスタに配置した後、INT $0x80(32-bit)またはSYSCALL(64-bit)命令を実行してカーネルにシステムコールを要求します。 - システムコールが成功したかどうかのチェック(
JAE 2(PC)またはJCC 2(PC))と、エラー時のruntime·notok呼び出しも含まれています。
-
MADV_DONTNEEDとMADV_FREE定数の追加:madviseシステムコールで使用するadviceフラグの定数が、GoランタイムのDarwin固有の定義ファイルに追加されました。src/pkg/runtime/defs_darwin.gosrc/pkg/runtime/defs_darwin_386.hsrc/pkg/runtime/defs_darwin_amd64.hこれらのファイルにMADV_DONTNEEDとMADV_FREEの定数値が追加され、Goコードからこれらのフラグをシンボリックに参照できるようになりました。
-
runtime·SysUnusedの実装変更: Goランタイムのruntime·SysUnused関数は、Goのガベージコレクタがメモリ領域が不要になったことをOSに通知するために使用されます。このコミットにより、Darwin版のruntime·SysUnusedが実際に機能するようになりました。src/pkg/runtime/mem_darwin.c内のruntime·SysUnused関数が変更され、以前の何もしないスタブコード(USED(v); USED(n);)が削除されました。- 代わりに、
runtime·madvise(v, n, MADV_FREE);という呼び出しが追加されました。これにより、SysUnusedが呼び出されると、指定されたメモリ領域vからnバイトの範囲について、MADV_FREEフラグを指定してmadviseシステムコールが実行されるようになりました。 - コメント
// Linux's MADV_DONTNEED is like BSD's MADV_FREE.は、この変更の意図、すなわちLinuxでのMADV_DONTNEEDと同様のメモリ解放セマンティクスをDarwinでMADV_FREEを使って実現することを示しています。
この変更により、GoランタイムはDarwin上で、ガベージコレクタが回収したメモリをOSに「ヒント」として通知し、OSがその物理メモリをより効率的に管理できるようになります。これにより、Goアプリケーションのメモリフットプリントが削減され、システム全体のメモリ使用効率が向上する可能性があります。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルと、その変更の概要は以下の通りです。
-
src/pkg/runtime/defs_darwin.go:MADV_DONTNEEDとMADV_FREEの定数が追加されました。これらはC言語の定数をGoの定数として定義し直したものです。const ( // ... MADV_DONTNEED = C.MADV_DONTNEED MADV_FREE = C.MADV_FREE // ... )
-
src/pkg/runtime/defs_darwin_386.h:- 32-bit Darwin(Intel x86)向けのヘッダファイルで、
MADV_DONTNEEDとMADV_FREEの定数値が追加されました。enum { // ... MADV_DONTNEED = 0x4, MADV_FREE = 0x5, // ... };
- 32-bit Darwin(Intel x86)向けのヘッダファイルで、
-
src/pkg/runtime/defs_darwin_amd64.h:- 64-bit Darwin(Intel x86-64)向けのヘッダファイルで、
MADV_DONTNEEDとMADV_FREEの定数値が追加されました。enum { // ... MADV_DONTNEED = 0x4, MADV_FREE = 0x5, // ... };
- 64-bit Darwin(Intel x86-64)向けのヘッダファイルで、
-
src/pkg/runtime/mem_darwin.c:runtime·SysUnused関数の実装が変更されました。以前の何もしないコードが削除され、runtime·madviseを呼び出すようになりました。void runtime·SysUnused(void *v, uintptr n) { // USED(v); // 削除 // USED(n); // 削除 // // TODO(rsc): call madvise MADV_DONTNEED // 削除 // Linux's MADV_DONTNEED is like BSD's MADV_FREE. // 追加 runtime·madvise(v, n, MADV_FREE); // 追加 }
-
src/pkg/runtime/sys_darwin_386.s:- 32-bit Darwin向けのアセンブリファイルで、
runtime·madvise関数の実装が追加されました。これはmadviseシステムコール(syscall番号75)を呼び出すためのものです。TEXT runtime·madvise(SB),7,$0 MOVL $75, AX // syscall number for madvise INT $0x80 JAE 2(PC) CALL runtime·notok(SB) RET
- 32-bit Darwin向けのアセンブリファイルで、
-
src/pkg/runtime/sys_darwin_amd64.s:- 64-bit Darwin向けのアセンブリファイルで、
runtime·madvise関数の実装が追加されました。これもmadviseシステムコール(syscall番号75)を呼び出すためのものです。引数の渡し方が32-bitと異なります(レジスタを使用)。TEXT runtime·madvise(SB), 7, $0 MOVQ 8(SP), DI // arg 1 addr MOVQ 16(SP), SI // arg 2 len MOVL 24(SP), DX // arg 3 advice MOVL $(0x2000000+75), AX // syscall entry madvise (0x2000000 is for 64-bit syscalls on Darwin) SYSCALL JCC 2(PC) CALL runtime·notok(SB) RET
- 64-bit Darwin向けのアセンブリファイルで、
コアとなるコードの解説
このコミットの核心は、GoランタイムがDarwin上でメモリをOSに返却する際の振る舞いを、madviseシステムコールとMADV_FREEフラグを用いて具体的に実装した点にあります。
-
defs_darwin.goおよびdefs_darwin_*.hにおける定数定義: これらのファイルは、GoランタイムがOS固有の定数や構造体を利用するためのインターフェースを提供します。MADV_DONTNEEDとMADV_FREEの追加は、Goコードがこれらのメモリ管理アドバイスフラグをシンボリックに参照できるようにするために不可欠です。これにより、マジックナンバー(直接的な数値)を使うことなく、コードの可読性と保守性が向上します。 -
mem_darwin.cにおけるruntime·SysUnusedの変更:runtime·SysUnusedは、Goのガベージコレクタが、特定のメモリ領域がもはやGoランタイムによって使用されていないことをOSに通知するために呼び出す関数です。この関数が以前は空のスタブであったため、Goランタイムがメモリを解放しても、OSはそのメモリが未使用であることを認識せず、物理メモリが解放されないままでした。 変更後、runtime·SysUnusedはruntime·madvise(v, n, MADV_FREE);を呼び出すようになりました。これは、アドレスvから長さnのメモリ領域について、MADV_FREEというアドバイスをOSに送ることを意味します。MADV_FREEは、Darwinにおいて、そのメモリ領域の物理ページを解放しても良いが、もしすぐに再利用される可能性がある場合は、書き込みが行われるまでページを保持するというセマンティクスを持ちます。これにより、GoランタイムはOSに対してメモリの再利用を促しつつ、過度なページングや再割り当てのオーバーヘッドを避けることができます。コメントにあるように、これはLinuxのMADV_DONTNEEDと同様の目的を果たします。 -
sys_darwin_386.sおよびsys_darwin_amd64.sにおけるruntime·madviseの実装: これらのアセンブリファイルは、Goランタイムが直接OSのシステムコールを呼び出すための低レベルなインターフェースを提供します。- 32-bit (
sys_darwin_386.s):MOVL $75, AXは、システムコール番号75(madvise)をAXレジスタにロードします。INT $0x80は、ソフトウェア割り込みを発生させ、カーネルにシステムコールを実行させます。引数はスタックから読み取られます。 - 64-bit (
sys_darwin_amd64.s):MOVQ 8(SP), DI,MOVQ 16(SP), SI,MOVL 24(SP), DXは、スタックから引数(アドレス、長さ、アドバイス)をそれぞれDI,SI,DXレジスタにロードします。これらはx86-64アーキテクチャにおけるシステムコール規約で、最初の3つの引数がこれらのレジスタに渡されることを意味します。MOVL $(0x2000000+75), AXは、64-bitシステムコール番号をAXレジスタにロードします。Darwinの64-bitシステムコールは、通常のシステムコール番号に0x2000000を加算した値を使用します。SYSCALL命令は、直接カーネルにシステムコールを要求します。 どちらのバージョンも、システムコールが成功したかどうかをチェックし(JAEまたはJCC)、失敗した場合はruntime·notokを呼び出してランタイムエラーを報告します。
- 32-bit (
これらの変更により、GoランタイムはDarwin上で、ガベージコレクタが不要と判断したメモリ領域をOSに効率的に通知し、OSがその物理メモリを再利用できるようにする、より洗練されたメモリ管理戦略を採用できるようになりました。これは、Goアプリケーションのメモリフットプリントを削減し、システム全体のパフォーマンスを向上させる上で重要な改善です。
関連リンク
- Go CL (Change List) 5531073: https://golang.org/cl/5531073
参考にした情報源リンク
madvise(2)man page (Linux): https://man7.org/linux/man-pages/man2/madvise.2.htmlmadvise(2)man page (macOS/FreeBSD): https://www.freebsd.org/cgi/man.cgi?query=madvise&sektion=2- Go runtime source code (relevant files from the commit)
- Understanding Go's Memory Management: https://go.dev/doc/gc-guide (General Go GC information)
- x86-64 calling conventions (for syscalls): https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI
- Darwin System Call Numbers: (Specific link not found easily, but general knowledge of syscalls on Darwin)
- Go Assembly Language: https://go.dev/doc/asm (General Go assembly information)
- Go runtime memory management (general concepts): https://go.dev/src/runtime/README.md (Though this is a general README, it provides context for the runtime)
- Linux
MADV_DONTNEEDvs BSDMADV_FREE: https://stackoverflow.com/questions/10670099/madv-dontneed-vs-madv-free (Stack Overflow discussion on the differences)