[インデックス 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ページサイズに戻しています。
主な変更点は以下の通りです。
- ページサイズの定義の変更:
src/pkg/runtime/malloc.h
内のPageShift
定数が13
(8KB) から12
(4KB) に戻されました。これにより、PageSize
は1<<13
から1<<12
になります。
- サイズクラスの数の変更:
src/pkg/runtime/malloc.h
内のNumSizeClasses
定数が67
から61
に戻されました。これは、メモリ割り当て器が管理するオブジェクトサイズクラスの数を元に戻すことを意味します。
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
を介した複雑な処理が不要になりました。
- メモリブロックサイズの調整:
src/pkg/runtime/mgc0.c
およびsrc/pkg/runtime/netpoll.goc
では、WorkbufSize
やPollBlockSize
といった定数が削除され、代わりにPageSize
を基準としたメモリ割り当てが行われるようになりました。例えば、Workbuf
のサイズは2 * PageSize
に、FinBlock
やPollDesc
の割り当ては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
PageShift
とPageSize
の変更:PageShift = 13
はPageSize = 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
を基準とした動的な計算に置き換えられました。
- これらの定数は、以前のコミットで8KBページサイズに合わせて定義されていましたが、このコミットで削除され、代わりに
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の内部処理、および統計情報の取得方法が適切に調整されたことを示しています。
関連リンク
- 元のコミット (CL 45770044): https://golang.org/cl/45770044 (このコミットによって元に戻された変更)
- このコミット (CL 56060043): https://golang.org/cl/56060043
参考にした情報源リンク
- Go言語の公式ドキュメント (メモリ管理、GCに関する情報): https://golang.org/doc/
- Tcmallocのドキュメント (メモリ割り当ての概念): https://github.com/gperftools/gperftools/wiki/tcmalloc
- オペレーティングシステムのメモリ管理に関する一般的な情報 (ページング、TLBなど)
- Goのソースコード (runtimeパッケージ): https://github.com/golang/go/tree/master/src/runtime