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

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

このコミットは、Goランタイムにおけるメモリ管理の最適化、特にWindows/amd64環境での最大アリーナサイズ(ヒープ領域)の削減に関するものです。これにより、gofmtなどのGoプログラムがWindows上で消費するコミット済みメモリが大幅に削減されます。

コミット

commit b3b1efd88291c63b9717db190ded45df2efc243
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue May 7 06:53:02 2013 +0800

    runtime: reduce max arena size on windows/amd64 to 32 GiB
    Update #5236
    Update #5402
    This CL reduces gofmt's committed memory from 545864 KiB to 139568 KiB.
    Note: Go 1.0.3 uses about 70MiB.
    
    R=golang-dev, r, iant, nightlyone
    CC=golang-dev
    https://golang.org/cl/9245043

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

https://github.com/golang/go/commit/b3b1efd88291c63b9717db190ded45df2efc243

元コミット内容

runtime: reduce max arena size on windows/amd64 to 32 GiB
Update #5236
Update #5402
This CL reduces gofmt's committed memory from 545864 KiB to 139568 KiB.
Note: Go 1.0.3 uses about 70MiB.

変更の背景

この変更の背景には、Windows/amd64環境におけるGoプログラムのメモリ使用量の問題がありました。特に、gofmtのようなツールが起動時に大量のメモリをコミットしてしまうという報告が上がっていました(Issue #5236, #5402)。

Windowsのメモリ管理の特性として、ページテーブルがプロセスにコミットされたメモリとしてカウントされるという点が挙げられます。Goランタイムは、将来のメモリ割り当てのために広大な仮想アドレス空間を予約(reserve)しますが、Windowsではこの予約された領域のページテーブルもコミット済みメモリとして計上されるため、実際に使用されていないにも関わらず、OSからは大量のメモリを消費しているように見えてしまう問題がありました。

これにより、ユーザーはGoプログラムが不必要に多くのメモリを消費していると誤解し、パフォーマンスやリソース利用の観点から懸念を抱く可能性がありました。このコミットは、このWindows特有のメモリ計上方法に対応し、実際のメモリ使用量に影響を与えることなく、見かけ上のコミット済みメモリを削減することを目的としています。

前提知識の解説

  • Goランタイムのメモリ管理: Goは独自のランタイムとガベージコレクタ(GC)を持っており、メモリの割り当てと解放を管理しています。Goのヒープは「アリーナ」と呼ばれる大きな仮想アドレス空間のチャンクに分割され、そこからオブジェクトが割り当てられます。
  • 仮想メモリと物理メモリ:
    • 仮想メモリ: プロセスが利用できるメモリ空間の抽象化です。各プロセスは独自の仮想アドレス空間を持ち、物理メモリに直接アクセスする代わりに仮想アドレスを使用します。
    • 物理メモリ: 実際にコンピュータに搭載されているRAMのことです。
  • メモリの予約 (Reserve) とコミット (Commit):
    • 予約 (Reserve): 仮想アドレス空間の一部を、将来使用するために確保することです。この時点では物理メモリは割り当てられません。
    • コミット (Commit): 予約された仮想アドレス空間に物理メモリを割り当てることです。これにより、そのメモリ領域が実際に使用可能になります。
  • ページテーブル: 仮想アドレスを物理アドレスに変換するためのOSのデータ構造です。仮想メモリシステムでは、メモリは固定サイズの「ページ」に分割され、ページテーブルは各仮想ページが対応する物理ページフレームのどこにあるかを記録します。
  • Windowsのメモリ管理の特性: Windowsでは、仮想アドレス空間を予約する際に、その予約された領域に対応するページテーブルのエントリが作成され、そのページテーブル自体がプロセスのコミット済みメモリとして計上されることがあります。これは、他のOS(例: Linux)とは異なる挙動であり、Goランタイムが広大な仮想アドレス空間を予約する設計と相まって、見かけ上のメモリ使用量が増大する原因となっていました。
  • gofmt: Go言語のソースコードを標準的なスタイルにフォーマットするためのツールです。Goの配布物に含まれており、開発者が一貫したコードスタイルを維持するのに役立ちます。

技術的詳細

このコミットは、Goランタイムのメモリ管理におけるMHeapMap_Bitsという定数の値を変更することで、Windows/amd64環境での最大アリーナサイズを削減しています。

  • MHeapMap_Bits: この定数は、Goランタイムが管理するヒープアリーナの仮想アドレス空間のサイズを決定するために使用されます。具体的には、MHeapMap_Bits + PageShiftがアリーナのビット数を表し、1ULL<<(MHeapMap_Bits+PageShift)で最大アリーナサイズが計算されます。PageShiftはページのサイズ(通常4KB)のビットシフト量です。
  • 変更点:
    • 従来の64-bitシステムでは、MHeapMap_Bits37 - PageShiftに設定されており、これにより最大アリーナサイズは128 GiBでした。
    • このコミットでは、GOOS_windows(Windows OS)かつ_64BIT(64-bitアーキテクチャ)の場合に限り、MHeapMap_Bits35 - PageShiftに設定するように変更されました。
    • これにより、Windows/amd64環境での最大アリーナサイズは1ULL<<(35)、つまり32 GiBに削減されます。
  • 理由: コミットメッセージに明記されている通り、Windowsではページテーブルがコミット済みメモリとしてカウントされるため、あまりにも多くのメモリを予約すると、見かけ上のコミット済みメモリが不必要に増大します。最大アリーナサイズを32 GiBに制限することで、このWindows特有の挙動によるメモリ使用量の見かけ上の増大を抑制し、gofmtのようなツールのメモリフットプリントを大幅に削減することに成功しています。
  • MaxMemのコメント変更: MaxMemマクロのコメントも、この変更を反映して「128 GB or 32 GB」と更新されています。

この変更は、GoランタイムがWindows上でより効率的に、かつOSのメモリ管理特性に配慮して動作するようにするための重要な改善です。

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

src/pkg/runtime/malloc.h ファイルの以下の部分が変更されました。

--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -115,10 +115,18 @@ enum
 	HeapAllocChunk = 1<<20,		// Chunk size for heap growth
 
 	// Number of bits in page to span calculations (4k pages).
-	// On 64-bit, we limit the arena to 128GB, or 37 bits.
+	// On Windows 64-bit we limit the arena to 32GB or 35 bits (see below for reason).
+	// On other 64-bit platforms, we limit the arena to 128GB, or 37 bits.
 	// On 32-bit, we don't bother limiting anything, so we use the full 32-bit address.
 #ifdef _64BIT
+#ifdef GOOS_windows
+	// Windows counts memory used by page table into committed memory
+	// of the process, so we can't reserve too much memory.
+	// See http://golang.org/issue/5402 and http://golang.org/issue/5236.
+	MHeapMap_Bits = 35 - PageShift,
+#else
 	MHeapMap_Bits = 37 - PageShift,
+#endif
 #else
 	MHeapMap_Bits = 32 - PageShift,
 #endif
@@ -134,7 +142,7 @@ enum
 // This must be a #define instead of an enum because it
 // is so large.
 #ifdef _64BIT
-#define	MaxMem	(1ULL<<(MHeapMap_Bits+PageShift))	/* 128 GB */
+#define	MaxMem	(1ULL<<(MHeapMap_Bits+PageShift))	/* 128 GB or 32 GB */
 #else
 #define	MaxMem	((uintptr)-1)
 #endif

コアとなるコードの解説

変更の中心は、MHeapMap_Bitsの定義に条件付きコンパイルディレクティブ(#ifdef)が追加されたことです。

  1. #ifdef _64BIT: 64-bitシステムの場合の処理ブロックです。
  2. #ifdef GOOS_windows: この内部で、さらにオペレーティングシステムがWindowsであるかどうかがチェックされます。
    • MHeapMap_Bits = 35 - PageShift,: Windows/amd64の場合、MHeapMap_Bits35 - PageShiftに設定されます。これにより、最大アリーナサイズが32 GiBに制限されます。コメントで、Windowsがページテーブルをコミット済みメモリとしてカウントする特性と、関連するIssue番号(#5402, #5236)が説明されています。
    • #else: Windows以外の64-bitシステムの場合(例: Linux/amd64)。
    • MHeapMap_Bits = 37 - PageShift,: こちらでは、従来通り37 - PageShiftが設定され、最大アリーナサイズは128 GiBのままです。
  3. #else (32-bit): 32-bitシステムの場合の処理ブロックです。
    • MHeapMap_Bits = 32 - PageShift,: 32-bitシステムでは、アリーナサイズに特に制限を設けていないため、32-bitアドレス空間全体を使用します。
  4. MaxMemマクロのコメント更新: MaxMemマクロのコメントが、この変更を反映して「128 GB or 32 GB」と更新されています。これは、MaxMemの値がプラットフォームによって128GBまたは32GBになることを示しています。

このコード変更により、GoランタイムはWindows/amd64環境において、仮想アドレス空間の予約をより控えめに行うようになり、結果としてOSが報告するコミット済みメモリの量を削減します。これは、実際のメモリ使用量には影響を与えず、見かけ上のメモリフットプリントを改善するものです。

関連リンク

参考にした情報源リンク

  • GoのIssueトラッカー (GitHub): https://github.com/golang/go/issues
  • Goのコードレビューシステム (Gerrit): https://golang.org/cl/
  • Windowsのメモリ管理に関するドキュメント (Microsoft Learnなど)
  • Goのメモリ管理に関する公式ドキュメントやブログ記事 (Go公式ブログなど)
  • Goのソースコード (特にsrc/runtimeディレクトリ)