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

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

このコミットは、Goランタイムにおけるメモリ管理の重要な変更を元に戻すものです。具体的には、以前のコミット(CL 45770044 / d795425bfa18)で導入されたページサイズを8KBに増やす変更を元に戻し、4KBに戻しています。この変更は、DarwinおよびFreeBSD環境で問題を引き起こしたため、元に戻されました。

コミット

commit 8371b0142e45f0753b8058f1667d9fd3e34e431f
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Thu Jan 23 19:56:59 2014 +0400

    undo CL 45770044 / d795425bfa18
    
    Breaks darwin and freebsd.
    
    ««« original CL description
    runtime: increase page size to 8K
    Tcmalloc uses 8K, 32K and 64K pages, and in custom setups 256K pages.
    Only Chromium uses 4K pages today (in "slow but small" configuration).
    The general tendency is to increase page size, because it reduces
    metadata size and DTLB pressure.
    This change reduces GC pause by ~10% and slightly improves other metrics.
    
    json-1
    allocated                 8037492      8038689      +0.01%
    allocs                     105762       105573      -0.18%
    cputime                 158400000    155800000      -1.64%
    gc-pause-one              4412234      4135702      -6.27%
    gc-pause-total            2647340      2398707      -9.39%
    rss                      54923264     54525952      -0.72%
    sys-gc                    3952624      3928048      -0.62%
    sys-heap                 46399488     46006272      -0.85%
    sys-other                 5597504      5290304      -5.49%
    sys-stack                  393216       393216      +0.00%
    sys-total                56342832     55617840      -1.29%
    time                    158478890    156046916      -1.53%
    virtual-mem             256548864    256593920      +0.02%
    
    garbage-1
    allocated                 2991113      2986259      -0.16%
    allocs                      62844        62652      -0.31%
    cputime                  16330000     15860000      -2.88%
    gc-pause-one            789108229    725555211      -8.05%
    gc-pause-total            3945541      3627776      -8.05%
    rss                    1143660544   1132253184      -1.00%
    sys-gc                   65609600     65806208      +0.30%
    sys-heap               1032388608   1035599872      +0.31%
    sys-other                37501632     22777664     -39.26%
    sys-stack                 8650752      8781824      +1.52%
    sys-total              1144150592   1132965568      -0.98%
    time                     16364602     15891994      -2.89%
    virtual-mem            1327296512   1313746944      -1.02%
    
    R=golang-codereviews, dave, khr, rsc, khr
    CC=golang-codereviews
    https://golang.org/cl/45770044
    »»»
    
    R=golang-codereviews
    CC=golang-codereviews
    https://golang.org/cl/56060043

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

https://github.com/golang/go/commit/8371b0142e45f0753b8058f1667d9fd3e34e431f

元コミット内容

このコミットは、以前のコミット CL 45770044 / d795425bfa18 を元に戻すものです。元コミットの目的は、Goランタイムのページサイズを4KBから8KBに増やすことでした。この変更は、Tcmallocが8KB、32KB、64KBのページを使用していることや、メタデータサイズとDTLB(Data Translation Lookaside Buffer)の圧力を軽減するという一般的な傾向に基づいています。

元コミットのベンチマーク結果では、GC(Garbage Collection)の一時停止時間が約10%削減され、その他のメトリクスもわずかに改善されることが示されていました。

json-1 ベンチマークの改善点:

  • gc-pause-one: -6.27%
  • gc-pause-total: -9.39%
  • cputime: -1.64%
  • rss: -0.72%
  • sys-total: -1.29%

garbage-1 ベンチマークの改善点:

  • gc-pause-one: -8.05%
  • gc-pause-total: -8.05%
  • cputime: -2.88%
  • rss: -1.00%
  • sys-total: -0.98%

これらの結果は、ページサイズを大きくすることでメモリ管理の効率が向上し、特にGCのパフォーマンスに良い影響を与えることを示唆していました。

変更の背景

元コミット(ページサイズを8KBに増やす変更)は、Goランタイムのパフォーマンス、特にガベージコレクションの一時停止時間を改善することを目的としていました。しかし、この変更がDarwin(macOS)およびFreeBSD環境で動作を破壊するという予期せぬ副作用を引き起こしました。

安定性と互換性を最優先するため、パフォーマンス上の利点があったにもかかわらず、この破壊的な変更を元に戻すことが決定されました。これは、特定のプラットフォームでの動作保証が、一般的なパフォーマンス改善よりも重要であるという判断を示しています。

前提知識の解説

ページサイズ (Page Size)

オペレーティングシステム(OS)は、メモリを「ページ」と呼ばれる固定サイズのブロックに分割して管理します。CPUは、仮想アドレスを物理アドレスに変換する際に、このページ単位でメモリにアクセスします。ページサイズが大きいほど、ページテーブルのエントリ数が減り、TLB(Translation Lookaside Buffer)ミスが減少する可能性があります。TLBは、仮想アドレスから物理アドレスへの変換を高速化するためのキャッシュです。TLBミスが減ると、メモリアクセスのパフォーマンスが向上します。

Tcmalloc

Tcmalloc (Thread-Caching Malloc) は、Googleが開発した高速なメモリ割り当てライブラリです。Goランタイムのメモリ管理は、Tcmallocの設計思想に影響を受けています。Tcmallocは、スレッドローカルなキャッシュと中央のヒープを組み合わせて、メモリ割り当ての競合を減らし、パフォーマンスを向上させます。Tcmallocは、一般的に8KB、32KB、64KBといった比較的大きなページサイズを使用します。

ガベージコレクション (GC)

Go言語は、自動メモリ管理(ガベージコレクション)を採用しています。GCは、プログラムが不要になったメモリを自動的に解放するプロセスです。GCの実行中には、プログラムの実行が一時的に停止する「GC一時停止(GC pause)」が発生することがあります。この一時停止時間は、アプリケーションの応答性やレイテンシに直接影響するため、短縮することが重要です。

NumSizeClasses

Goランタイムのメモリ割り当て器は、様々なサイズのオブジェクトを効率的に管理するために、事前に定義された「サイズクラス(size classes)」を使用します。NumSizeClassesは、これらのサイズクラスの総数を定義する定数です。オブジェクトのサイズに応じて、適切なサイズクラスのメモリブロックが割り当てられます。この値の変更は、メモリ割り当て器の内部構造に影響を与えます。

Darwin / FreeBSD

DarwinはmacOSの基盤となるオープンソースのUNIX系オペレーティングシステムです。FreeBSDもまた、UNIX系のオープンソースOSです。Goランタイムは、これらのOSを含む複数のプラットフォームをサポートしており、特定のプラットフォームで問題が発生した場合、その互換性を維持するために変更を元に戻すことがあります。

技術的詳細

このコミットは、以前のページサイズ8KBへの変更を完全に元に戻すことで、Goランタイムのメモリ管理を元の4KBページサイズに戻しています。

主な変更点は以下の通りです。

  1. ページサイズの定義の変更:
    • src/pkg/runtime/malloc.h 内の PageShift 定数が 13 (8KB) から 12 (4KB) に戻されました。これにより、PageSize1<<13 から 1<<12 になります。
  2. サイズクラスの数の変更:
    • src/pkg/runtime/malloc.h 内の NumSizeClasses 定数が 67 から 61 に戻されました。これは、メモリ割り当て器が管理するオブジェクトサイズクラスの数を元に戻すことを意味します。
  3. MStats 構造体の扱い:
    • src/pkg/runtime/malloc.goc では、runtime·sizeof_C_MStats の計算が簡素化され、NumSizeClasses の変更に伴う複雑な計算が不要になりました。
    • src/pkg/runtime/mem.go では、memStats 変数の宣言が init() 関数内からパッケージレベルに移動され、グローバル変数として扱われるようになりました。
    • src/pkg/runtime/mgc0.c では、runtime·ReadMemStats 関数内で mstats のコピー方法が簡素化され、runtime·memcopy を介した複雑な処理が不要になりました。
  4. メモリブロックサイズの調整:
    • src/pkg/runtime/mgc0.c および src/pkg/runtime/netpoll.goc では、WorkbufSizePollBlockSize といった定数が削除され、代わりに PageSize を基準としたメモリ割り当てが行われるようになりました。例えば、Workbuf のサイズは 2 * PageSize に、FinBlockPollDesc の割り当ては PageSize を基準に行われるようになりました。これは、以前のコミットで8KBページサイズに合わせて調整されたメモリブロックのサイズ定義を、4KBページサイズに戻すための変更です。

これらの変更は、Goランタイムが特定のOS(DarwinとFreeBSD)で正しく動作するために不可欠でした。パフォーマンスの最適化よりも、基本的な機能の安定性が優先された結果です。

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

src/pkg/runtime/malloc.h

--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -90,7 +90,7 @@ typedef struct GCStats	GCStats;
 
 enum
 {
-	PageShift	= 13,
+	PageShift	= 12,
 	PageSize	= 1<<PageShift,
 	PageMask	= PageSize - 1,
 };
@@ -103,7 +103,7 @@ enum
 	// size classes.  NumSizeClasses is that number.  It's needed here
 	// because there are static arrays of this length; when msize runs its
 	// size choosing algorithm it double-checks that NumSizeClasses agrees.
-	NumSizeClasses = 67,
+	NumSizeClasses = 61,
 
 	// Tunable constants.
 	MaxSmallSize = 32<<10,
@@ -255,7 +255,7 @@ struct MStats
 	} by_size[NumSizeClasses];
 };
 
-#define mstats runtime·memStats
+#define mstats runtime·memStats	/* name shared with Go */
 extern MStats mstats;
 
 // Size classes.  Computed and initialized by InitSizes.

src/pkg/runtime/mgc0.c

--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -25,10 +25,6 @@ enum {
 	wordsPerBitmapWord = sizeof(void*)*8/4,
 	bitShift = sizeof(void*)*8/4,
 
-\tWorkbufSize	= 16*1024,
-\tRootBlockSize	= 4*1024,
-\tFinBlockSize	= 4*1024,
-\
 \thandoffThreshold = 4,
 	IntermediateBufferCapacity = 64,
 
@@ -147,10 +143,11 @@ struct Obj
 	uintptr		ti;	// type info
 };
 
+// The size of Workbuf is N*PageSize.
 typedef struct Workbuf Workbuf;
 struct Workbuf
 {
-#define SIZE (WorkbufSize-sizeof(LFNode)-sizeof(uintptr))\n+#define SIZE (2*PageSize-sizeof(LFNode)-sizeof(uintptr))\n 	LFNode  node; // must be first
 	uintptr nobj;
 	Obj     obj[SIZE/sizeof(Obj) - 1];
 
@@ -729,7 +726,7 @@ scanblock(Workbuf *wbuf, bool keepworking)
 	ChanType *chantype;
 	Obj *wp;
 
-\tif(sizeof(Workbuf) % WorkbufSize != 0)\n+\tif(sizeof(Workbuf) % PageSize != 0)\n 	\truntime·throw("scanblock: size of Workbuf is suboptimal");
 
 	// Memory arena parameters.
@@ -1590,8 +1587,8 @@ runtime·queuefinalizer(byte *p, FuncVal *fn, uintptr nret, Type *fint, PtrType
 	runtime·lock(&finlock);
 	if(finq == nil || finq->cnt == finq->cap) {
 		if(finc == nil) {
-\t\t\tfinc = runtime·persistentalloc(FinBlockSize, 0, &mstats.gc_sys);
-\t\t\tfinc->cap = (FinBlockSize - sizeof(FinBlock)) / sizeof(Finalizer) + 1;
+\t\t\tfinc = runtime·persistentalloc(PageSize, 0, &mstats.gc_sys);
+\t\t\tfinc->cap = (PageSize - sizeof(FinBlock)) / sizeof(Finalizer) + 1;
 			finc->alllink = allfin;
 			allfin = finc;
 		}
@@ -2218,8 +2215,6 @@ gc(struct gc_args *args)
 	runtime·MProf_GC();
 }
 
-extern uintptr runtime·sizeof_C_MStats;
-
 void
 runtime·ReadMemStats(MStats *stats)
 {
@@ -2231,9 +2226,7 @@ runtime·ReadMemStats(MStats *stats)
 	m->gcing = 1;
 	runtime·stoptheworld();
 	updatememstats(nil);
-\t// Size of the trailing by_size array differs between Go and C,
-\t// NumSizeClasses was changed, but we can not change Go struct because of backward compatibility.
-\truntime·memcopy(runtime·sizeof_C_MStats, stats, &mstats);
+\t*stats = mstats;
 	m->gcing = 0;
 	m->locks++;
 	runtime·semrelease(&runtime·worldsema);

コアとなるコードの解説

src/pkg/runtime/malloc.h

  • PageShiftPageSize の変更:
    • PageShift = 13PageSize = 8192 (8KB) を意味します。これが PageShift = 12 に戻され、PageSize = 4096 (4KB) になります。これは、このコミットの最も根本的な変更であり、メモリ管理の基本単位を元に戻すものです。
  • NumSizeClasses の変更:
    • NumSizeClasses = 67 は、メモリ割り当て器が管理するサイズクラスの数が67であったことを示します。これが 61 に戻されます。この変更は、ページサイズと密接に関連しており、メモリ割り当て器の内部構造に影響を与えます。
  • mstats の定義:
    • #define mstats runtime·memStats は、mstats というマクロが runtime·memStats を指すように定義されています。これは、GoとCのコード間でメモリ統計情報にアクセスするためのエイリアスです。

src/pkg/runtime/mgc0.c

  • WorkbufSize, RootBlockSize, FinBlockSize の削除:
    • これらの定数は、以前のコミットで8KBページサイズに合わせて定義されていましたが、このコミットで削除され、代わりに PageSize を基準とした動的な計算に置き換えられました。
  • Workbuf 構造体の SIZE マクロの変更:
    • #define SIZE (WorkbufSize-sizeof(LFNode)-sizeof(uintptr)) から #define SIZE (2*PageSize-sizeof(LFNode)-sizeof(uintptr)) に変更されました。これにより、Workbuf のサイズが 2 * PageSize (8KB) になります。これは、以前の WorkbufSize (16KB) よりも小さくなりますが、4KBページサイズに戻ったことに合わせて調整されています。
  • scanblock 関数内のチェックの変更:
    • if(sizeof(Workbuf) % WorkbufSize != 0) から if(sizeof(Workbuf) % PageSize != 0) に変更されました。これは、Workbuf のサイズが PageSize の倍数であることを確認するためのチェックであり、ページサイズの変更に合わせて更新されています。
  • runtime·queuefinalizer 関数内のメモリ割り当ての変更:
    • runtime·persistentalloc(FinBlockSize, ...) から runtime·persistentalloc(PageSize, ...) に変更されました。これにより、ファイナライザブロックの割り当てが PageSize (4KB) 単位で行われるようになります。
  • runtime·ReadMemStats 関数の簡素化:
    • runtime·memcopy(runtime·sizeof_C_MStats, stats, &mstats); から *stats = mstats; に変更されました。これは、NumSizeClasses が元の値に戻ったため、MStats 構造体のサイズに関する特別な考慮が不要になり、直接代入で済むようになったことを示しています。

これらの変更は、ページサイズが4KBに戻されたことに伴い、メモリ割り当て、GCの内部処理、および統計情報の取得方法が適切に調整されたことを示しています。

関連リンク

参考にした情報源リンク