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

[インデックス 15958] ファイルの概要

このコミットは、Goランタイムにおけるメモリ管理の挙動を一時的に変更するものです。具体的には、ヒープ割り当て時にruntime.memlimitのチェックを無視するように修正しています。これは、Go 1.1のリリースに向けて発生した特定のメモリ割り当ての問題(Go issue 5049)に対応するための暫定的な措置です。

コミット

commit 515353a290be7718783ce31987b74e8e515e6c2d
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Mar 26 14:01:12 2013 -0700

    pkg/runtime: ignore runtime.memlimit when allocating heap
    
    For Go 1.1, stop checking the rlimit, because it broke now
    that mheap is allocated using SysAlloc.  See issue 5049.
    
    R=r
    CC=golang-dev
    https://golang.org/cl/7741050

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/515353a290be7718783ce31987b74e8e515e6c2d

元コミット内容

pkg/runtime: ignore runtime.memlimit when allocating heap

For Go 1.1, stop checking the rlimit, because it broke now
that mheap is allocated using SysAlloc.  See issue 5049.

R=r
CC=golang-dev
https://golang.org/cl/7741050

変更の背景

このコミットは、Go 1.1のリリースを控えた時期に発生した、Goランタイムのメモリ管理に関する重要な問題(Go issue 5049)に対処するために導入されました。

Goランタイムは、プログラムが利用できるメモリ量を管理するために、オペレーティングシステム(OS)のrlimit(リソース制限)設定を考慮していました。特に、runtime.memlimit()関数は、このrlimitからメモリ制限を取得し、Goのヒープ割り当ての最大サイズを決定するために使用されていました。

しかし、Go 1.1の変更により、Goのメモリヒープ(mheap)の割り当て方法が変更され、SysAllocというシステムコールを通じてメモリが確保されるようになりました。この新しいSysAllocによるmheapの割り当てが、既存のruntime.memlimitのチェックと競合し、問題を引き起こすことが判明しました。具体的には、mheapが仮想メモリを大量に(例えば256MB)予約しようとすると、厳格なulimit設定下でGoプログラムがクラッシュする可能性がありました。

この問題は、Go 1.1の安定性とリリースを阻害する可能性があったため、一時的な回避策として、ヒープ割り当て時にruntime.memlimitのチェックを無効にすることが決定されました。コミットメッセージにある「TODO(rsc): Fix after 1.1.」というコメントは、この変更が暫定的なものであり、Go 1.1リリース後に適切な解決策が検討されるべきであることを示しています。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムとOSのメモリ管理に関する概念を理解しておく必要があります。

  • rlimit (Resource Limit): Unix系OSにおけるリソース制限のメカニズムです。プロセスが利用できるCPU時間、ファイルサイズ、メモリ量などのシステムリソースに上限を設定するために使用されます。ulimitコマンドを通じてユーザーが設定することも可能です。メモリ関連では、RLIMIT_AS(アドレス空間の最大サイズ)やRLIMIT_DATA(データセグメントの最大サイズ)などが関連します。Goランタイムは、これらのrlimit設定を読み取り、自身のメモリ管理に反映させようとします。

  • mheap (Memory Heap): Goランタイムの主要なメモリ管理コンポーネントの一つです。Goプログラムが動的に確保するメモリ(例えば、makenewで作成されるオブジェクト)は、このmheapから割り当てられます。mheapは、メモリをページ単位で管理し、OSから仮想メモリを要求し、それをGoのヒープとして利用可能な状態に保ちます。

  • SysAlloc: GoランタイムがOSから直接メモリを要求するために使用する低レベルの関数です。これは通常、mmapVirtualAllocといったOS固有のシステムコールをラップしており、仮想メモリ空間を確保する役割を担います。Go 1.1でmheapSysAllocを使ってメモリを割り当てるようになったことが、今回の問題の直接的な原因となりました。

  • runtime.memlimit(): Goランタイム内部の関数で、OSのrlimit設定に基づいて、Goプログラムが利用できるメモリの最大量を計算しようとします。この関数が返す値は、Goのヒープサイズの上限として利用されることを意図していました。

技術的詳細

Go 1.1より前のバージョンでは、Goランタイムはruntime.memlimit()を呼び出してOSのrlimit設定を考慮し、ヒープの最大サイズを決定していました。この制限は、プログラムがOSによって強制されるメモリ制限を超過しないようにするための安全策として機能していました。

しかし、Go 1.1でmheapのメモリ割り当てロジックが変更され、SysAllocを介して直接OSから仮想メモリを要求するようになりました。この変更により、mheapは起動時に比較的大きな仮想メモリ領域(例えば256MB)を予約するようになりました。

問題は、このmheapによる仮想メモリの予約が、runtime.memlimit()が返すrlimitに基づく制限と衝突した点にあります。特に、rlimitが厳しく設定されている環境(例えば、ulimit -vで仮想メモリが制限されている場合)では、mheapが予約しようとするメモリ量がrlimitを超過し、プログラムの起動に失敗したり、予期せぬクラッシュを引き起こしたりするようになりました。

このコミットでは、この問題を回避するために、src/pkg/runtime/malloc.goc内のruntime·mallocinit関数において、runtime·memlimit()の呼び出し結果を無視し、代わりにlimit変数を0に設定しています。

// limit = runtime·memlimit();
// See https://code.google.com/p/go/issues/detail?id=5049
// TODO(rsc): Fix after 1.1.
limit = 0;

limit = 0と設定することで、Goランタイムはヒープの最大サイズをrlimitに基づいて制限するのをやめ、実質的にOSが許す限りメモリを割り当てられるようになります。これは、rlimitによる制限がGoのメモリ管理と正しく連携しないという当時の状況に対する、実用的な回避策でした。

ただし、この変更は「TODO(rsc): Fix after 1.1.」と明記されている通り、暫定的なものです。これは、rlimitを完全に無視することが常に望ましいわけではなく、将来的にrlimitを適切に尊重しつつ、mheapSysAllocによる割り当てと競合しないような、より洗練された解決策が必要であるという認識があったことを示しています。Go 1.1リリース後のバージョンでは、この問題に対するより恒久的な修正が導入されることになります。

コアとなるコードの変更箇所

変更はsrc/pkg/runtime/malloc.gocファイルにあります。

diff --git a/src/pkg/runtime/malloc.goc b/src/pkg/runtime/malloc.goc
index fa28e2b738..a30129ffc1 100644
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -318,7 +318,10 @@ runtime·mallocinit(void)
 
  	runtime·InitSizes();
 
-	limit = runtime·memlimit();
+	// limit = runtime·memlimit();
+	// See https://code.google.com/p/go/issues/detail?id=5049
+	// TODO(rsc): Fix after 1.1.
+	limit = 0;
 
  	// Set up the allocation arena, a contiguous area of memory where
  	// allocated data will be found.  The arena begins with a bitmap large

コアとなるコードの解説

変更はruntime·mallocinit関数内で行われています。この関数は、Goランタイムのメモリ割り当てシステムが初期化される際に呼び出されます。

元のコードでは、以下の行でlimit変数がruntime·memlimit()の戻り値で初期化されていました。

limit = runtime·memlimit();

このlimit変数は、Goのヒープが利用できる最大メモリ量を示すために使用されていました。

今回のコミットでは、この行がコメントアウトされ、代わりに以下の3行が追加されています。

// limit = runtime·memlimit();
// See https://code.google.com/p/go/issues/detail?id=5049
// TODO(rsc): Fix after 1.1.
limit = 0;
  • // limit = runtime·memlimit();: 元の行をコメントアウトすることで、runtime·memlimit()の呼び出し結果がlimit変数に代入されなくなります。
  • // See https://code.google.com/p/go/issues/detail?id=5049: この変更がGo issue 5049に関連していることを示すコメントです。
  • // TODO(rsc): Fix after 1.1.: この変更がGo 1.1リリース後の将来の修正を必要とする暫定的なものであることを示すコメントです。rscはおそらくGoの主要開発者であるRuss Cox氏を指します。
  • limit = 0;: limit変数を明示的に0に設定します。これにより、ヒープの最大サイズに関するrlimitに基づく制限が実質的に無効化され、GoランタイムはOSが許す限りメモリを割り当てられるようになります。

この変更により、Go 1.1におけるmheapSysAllocによるメモリ予約とrlimitの間の競合が回避され、プログラムがクラッシュすることなく起動できるようになりました。

関連リンク

参考にした情報源リンク