[インデックス 13328] ファイルの概要
このコミットは、Goランタイムのメモリ管理部分、特にアロケータの統計情報(malloc stats
)に関する改善を目的としています。主な変更点は、統計情報を保持するデータ型をint64
からuintptr
に変更し、32ビットシステムでの統計情報のオーバーフローを防ぐためのメカニズムを導入したことです。これにより、特定のベンチマークにおいて顕著なパフォーマンス向上が見られます。
コミット
commit 0b09425b5cfda5bd535ec226b9368e396d6d07a7
Author: Dave Cheney <dave@cheney.net>
Date: Fri Jun 8 17:35:14 2012 -0400
runtime: use uintptr where possible in malloc stats
linux/arm OMAP4 pandaboard
benchmark old ns/op new ns/op delta
BenchmarkBinaryTree17 68723297000 37026214000 -46.12%
BenchmarkFannkuch11 34962402000 35958435000 +2.85%
BenchmarkGobDecode 137298600 124182150 -9.55%
BenchmarkGobEncode 60717160 60006700 -1.17%
BenchmarkGzip 5647156000 5550873000 -1.70%
BenchmarkGunzip 1196350000 1198670000 +0.19%
BenchmarkJSONEncode 863012800 782898000 -9.28%
BenchmarkJSONDecode 3312989000 2781800000 -16.03%
BenchmarkMandelbrot200 45727540 45703120 -0.05%
BenchmarkParse 74781800 59990840 -19.78%
BenchmarkRevcomp 140043650 139462300 -0.42%
BenchmarkTemplate 6467682000 5832153000 -9.83%\n
benchmark old MB/s new MB/s speedup
BenchmarkGobDecode 5.59 6.18 1.11x
BenchmarkGobEncode 12.64 12.79 1.01x
BenchmarkGzip 3.44 3.50 1.02x
BenchmarkGunzip 16.22 16.19 1.00x
BenchmarkJSONEncode 2.25 2.48 1.10x
BenchmarkJSONDecode 0.59 0.70 1.19x
BenchmarkParse 0.77 0.97 1.26x
BenchmarkRevcomp 18.15 18.23 1.00x
BenchmarkTemplate 0.30 0.33 1.10x
darwin/386 core duo
benchmark old ns/op new ns/op delta
BenchmarkBinaryTree17 10591616577 9678245733 -8.62%\n BenchmarkFannkuch11 10758473315 10749303846 -0.09%\n BenchmarkGobDecode 34379785 34121250 -0.75%\n BenchmarkGobEncode 23523721 23475750 -0.20%\n BenchmarkGzip 2486191492 2446539568 -1.59%\n BenchmarkGunzip 444179328 444250293 +0.02%\n BenchmarkJSONEncode 221138507 219757826 -0.62%\n BenchmarkJSONDecode 1056034428 1048975133 -0.67%\n BenchmarkMandelbrot200 19862516 19868346 +0.03%\n BenchmarkRevcomp 3742610872 3724821662 -0.48%\n BenchmarkTemplate 960283112 944791517 -1.61%\n
benchmark old MB/s new MB/s speedup
BenchmarkGobDecode 22.33 22.49 1.01x
BenchmarkGobEncode 32.63 32.69 1.00x
BenchmarkGzip 7.80 7.93 1.02x
BenchmarkGunzip 43.69 43.68 1.00x
BenchmarkJSONEncode 8.77 8.83 1.01x
BenchmarkJSONDecode 1.84 1.85 1.01x
BenchmarkRevcomp 67.91 68.24 1.00x
BenchmarkTemplate 2.02 2.05 1.01x
R=rsc, 0xe2.0x9a.0x9b, mirtchovski
CC=golang-dev, minux.ma
https://golang.org/cl/6297047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0b09425b5cfda5bd535ec226b9368e396d6d07a7
元コミット内容
このコミットの元々の内容は、Goランタイムのメモリ割り当て統計(malloc stats
)において、可能な限りuintptr
型を使用するように変更することです。これは、特に32ビットアーキテクチャにおいて、統計カウンタのオーバーフローを防ぎ、パフォーマンスを向上させることを目的としています。
コミットメッセージには、linux/arm OMAP4 pandaboard
とdarwin/386 core duo
という異なるアーキテクチャでのベンチマーク結果が示されており、特にBenchmarkBinaryTree17
、BenchmarkGobDecode
、BenchmarkJSONEncode
、BenchmarkJSONDecode
、BenchmarkParse
、BenchmarkTemplate
などで大幅な性能改善が見られます。
変更の背景
Goランタイムのメモリ管理システムは、内部的に様々な統計情報を収集しています。これには、割り当てられたバイト数、オブジェクト数、ルックアップ回数などが含まれます。これらの統計情報は、ガベージコレクション(GC)の動作やメモリ使用状況の分析に利用されます。
しかし、これらの統計カウンタがint64
型で定義されている場合、特に32ビットシステムでは、カウンタの値が非常に大きくなった際に、その値の処理や比較において非効率が生じる可能性がありました。また、理論的にはint64
は64ビットなのでオーバーフローの心配は少ないですが、特定の内部処理や、32ビット環境でのint64
の扱い方によっては、意図しない挙動や性能劣化を引き起こす可能性も考えられます。
このコミットの背景には、32ビットアーキテクチャ(例: ARM、x86 32-bit)でのメモリ統計情報の効率的な管理と、それに伴うパフォーマンスの最適化という課題がありました。特に、uintptr
型はポインタのサイズに合わせた符号なし整数型であり、32ビットシステムでは32ビット、64ビットシステムでは64ビットのサイズを持ちます。これにより、システムのアドレス空間のサイズに合わせた最適なデータ型で統計情報を扱うことが可能になります。
前提知識の解説
Goランタイムとメモリ管理
Go言語は独自のランタイムを持っており、その中でメモリ管理(アロケーションとガベージコレクション)が行われます。Goのメモリ管理は、ヒープ領域を管理し、オブジェクトの割り当てと解放を効率的に行うことを目的としています。
- ヒープ (Heap): プログラムが動的にメモリを割り当てる領域です。Goのオブジェクトは通常ヒープに割り当てられます。
- ガベージコレクション (GC): 不要になったメモリを自動的に解放する仕組みです。GoのGCは並行マーク&スイープ方式を採用しており、プログラムの実行と並行して動作します。
mheap
: Goランタイムのグローバルなヒープ構造体で、システム全体のメモリ管理を司ります。mcache
: 各P(プロセッサ、Goスケジューラが管理する論理CPU)に紐付けられたローカルなキャッシュです。小さなオブジェクトの割り当てを高速化するために使用されます。mcache
は、グローバルなmheap
へのロック競合を減らす役割も果たします。MSpan
: ヒープメモリの連続したページ群を表す構造体です。Goのメモリ管理はページ単位で行われます。
uintptr
型
Go言語におけるuintptr
は、ポインタを保持するのに十分な大きさの符号なし整数型です。そのサイズは、実行されているシステムのアーキテクチャに依存します。
- 32ビットシステム:
uintptr
は32ビット(4バイト)です。 - 64ビットシステム:
uintptr
は64ビット(8バイト)です。
uintptr
は、ポインタ演算を行う際や、C言語との相互運用でポインタを整数として扱う必要がある場合によく使用されます。このコミットでは、メモリ統計情報がポインタサイズと密接に関連しているため、uintptr
が選択されました。
統計情報のオーバーフロー
コンピュータの数値型には表現できる値の範囲に限りがあります。例えば、32ビット符号なし整数は0から約42億までの値を表現できます。もしカウンタがこの上限を超えると、値がゼロに戻ったり(ラップアラウンド)、予期しない負の値になったりする「オーバーフロー」が発生します。
このコミットでは、特に32ビットシステムにおいて、メモリ割り当てやルックアップの回数が非常に多くなった場合に、統計カウンタがオーバーフローする可能性を考慮しています。int64
は64ビットなので、通常はオーバーフローの心配は少ないですが、32ビットシステムでのint64
の処理コストや、特定のコンテキストでの型変換の際に問題が生じる可能性がありました。
技術的詳細
このコミットの技術的な核心は、Goランタイムのメモリ管理における統計情報のデータ型をint64
からuintptr
に変更し、さらに32ビットシステムでのオーバーフロー対策を講じた点にあります。
-
データ型の変更 (
src/pkg/runtime/malloc.h
):MCache
構造体内の複数の統計カウンタ(size
,local_cachealloc
,local_objects
,local_alloc
,local_total_alloc
,local_nmalloc
,local_nfree
,local_nlookup
)および、local_by_size
内のnmalloc
,nfree
がint64
からuintptr
に変更されました。 これにより、これらのカウンタはシステムのポインタサイズに合わせた最適なビット幅を持つようになります。32ビットシステムでは32ビット、64ビットシステムでは64ビットとして扱われます。これは、カウンタの読み書きの効率化に寄与し、特に32ビットシステムでのパフォーマンス向上に繋がったと考えられます。 -
32ビットシステムでのオーバーフロー対策 (
src/pkg/runtime/malloc.goc
):runtime·mallocgc
関数とruntime·mlookup
関数に、以下の条件分岐が追加されました。if (sizeof(void*) == 4 && c->local_total_alloc >= (1<<30)) { // purge cache stats to prevent overflow runtime·lock(&runtime·mheap); runtime·purgecachedstats(m); runtime·unlock(&runtime·mheap); }
sizeof(void*) == 4
: これは、現在のシステムが32ビットアーキテクチャであるかどうかをチェックする条件です。void*
のサイズが4バイト(32ビット)であれば、32ビットシステムと判断されます。c->local_total_alloc >= (1<<30)
(またはm->mcache->local_nlookup >= (1<<30)
): これは、該当する統計カウンタが約1ギガバイト(または10億回)に達したかどうかをチェックする条件です。1<<30
は2の30乗、つまり1,073,741,824です。runtime·purgecachedstats(m)
: 上記の条件が満たされた場合、この関数が呼び出されます。この関数は、現在のM(マシン、OSスレッド)に関連付けられたmcache
の統計情報をクリア(またはリセット)する役割を担います。これにより、32ビットシステムでuintptr
型のカウンタがオーバーフローする前に、その値をリセットし、正確な統計情報を維持します。runtime·lock(&runtime·mheap)
/runtime·unlock(&runtime·mheap)
:purgecachedstats
の呼び出しは、グローバルなヒープロック(runtime·mheap
)によって保護されています。これは、統計情報のクリア処理中に他のスレッドがヒープにアクセスしてデータ競合が発生するのを防ぐためです。
この変更により、32ビットシステムではuintptr
が32ビット幅となるため、カウンタが約2GB(符号なしの場合)に近づく前に、明示的に統計情報をクリアすることで、オーバーフローを回避しつつ、カウンタの精度を保つことができます。64ビットシステムではuintptr
が64ビット幅であるため、このオーバーフロー対策の条件は満たされず、purgecachedstats
は呼び出されません。
ベンチマーク結果が示すように、この変更は特にlinux/arm
のような32ビットアーキテクチャで顕著なパフォーマンス向上をもたらしています。これは、データ型の変更によるメモリアクセスの効率化や、カウンタのオーバーフロー対策が適切に機能した結果と考えられます。
コアとなるコードの変更箇所
src/pkg/runtime/malloc.goc
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -72,6 +72,14 @@ runtime·mallocgc(uintptr size, uint32 flag, int32 dogc, int32 zeroed)
// setup for mark sweep
runtime·markspan(v, 0, 0, true);
}
+\
+\tif (sizeof(void*) == 4 && c->local_total_alloc >= (1<<30)) {
+\t\t// purge cache stats to prevent overflow
+\t\truntime·lock(&runtime·mheap);\
+\t\truntime·purgecachedstats(m);\
+\t\truntime·unlock(&runtime·mheap);\
+\t}\
+\
if(!(flag & FlagNoGC))
runtime·markallocated(v, size, (flag&FlagNoPointers) != 0);
@@ -170,6 +178,13 @@ runtime·mlookup(void *v, byte **base, uintptr *size, MSpan **sp)
MSpan *s;
m->mcache->local_nlookup++;
+\tif (sizeof(void*) == 4 && m->mcache->local_nlookup >= (1<<30)) {
+\t\t// purge cache stats to prevent overflow
+\t\truntime·lock(&runtime·mheap);\
+\t\truntime·purgecachedstats(m);\
+\t\truntime·unlock(&runtime·mheap);\
+\t}\
+\
s = runtime·MHeap_LookupMaybe(&runtime·mheap, v);
if(sp)
*sp = s;
src/pkg/runtime/malloc.h
--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -273,19 +273,19 @@ struct MCacheList
struct MCache
{
MCacheList list[NumSizeClasses];
-\tuint64 size;\
-\tint64 local_cachealloc;\t// bytes allocated (or freed) from cache since last lock of heap
-\tint64 local_objects;\t// objects allocated (or freed) from cache since last lock of heap
-\tint64 local_alloc;\t// bytes allocated (or freed) since last lock of heap
-\tint64 local_total_alloc;\t// bytes allocated (even if freed) since last lock of heap
-\tint64 local_nmalloc;\t// number of mallocs since last lock of heap
-\tint64 local_nfree;\t// number of frees since last lock of heap
-\tint64 local_nlookup;\t// number of pointer lookups since last lock of heap
+\tuintptr size;\
+\tuintptr local_cachealloc;\t// bytes allocated (or freed) from cache since last lock of heap
+\tuintptr local_objects;\t// objects allocated (or freed) from cache since last lock of heap
+\tuintptr local_alloc;\t// bytes allocated (or freed) from cache since last lock of heap
+\tuintptr local_total_alloc;\t// bytes allocated (even if freed) since last lock of heap
+\tuintptr local_nmalloc;\t// number of mallocs since last lock of heap
+\tuintptr local_nfree;\t// number of frees since last lock of heap
+\tuintptr local_nlookup;\t// number of pointer lookups since last lock of heap
int32 next_sample;\t// trigger heap sample after allocating this many bytes
// Statistics about allocation size classes since last lock of heap
struct {
-\t\tint64 nmalloc;\
-\t\tint64 nfree;\
+\t\tuintptr nmalloc;\
+\t\tuintptr nfree;\
} local_by_size[NumSizeClasses];
};
コアとなるコードの解説
src/pkg/runtime/malloc.h
の変更
このファイルでは、Goランタイムのメモリキャッシュ(MCache
)構造体内の統計情報フィールドの型が変更されています。
uint64
からuintptr
への変更:size
: キャッシュのサイズ。local_cachealloc
: キャッシュから割り当て(または解放)されたバイト数。local_objects
: キャッシュから割り当て(または解放)されたオブジェクト数。local_alloc
: ヒープロック以降に割り当て(または解放)されたバイト数。local_total_alloc
: ヒープロック以降に割り当てられた(解放済みであっても)バイト数。local_nmalloc
: ヒープロック以降のmalloc呼び出し回数。local_nfree
: ヒープロック以降のfree呼び出し回数。local_nlookup
: ヒープロック以降のポインタルックアップ回数。local_by_size
内のnmalloc
とnfree
: 特定のサイズクラスにおけるmalloc/free回数。
これらの変更により、統計カウンタのサイズがシステムのポインタサイズに最適化され、特に32ビットシステムでのメモリ効率とアクセス速度が向上します。
src/pkg/runtime/malloc.goc
の変更
このファイルでは、メモリ割り当てとルックアップの主要な関数に、32ビットシステムでの統計情報オーバーフロー対策が追加されています。
-
runtime·mallocgc
関数内: この関数は、ガベージコレクションを考慮したメモリ割り当ての主要なエントリポイントです。割り当て処理の後に、以下のチェックが追加されました。if (sizeof(void*) == 4 && c->local_total_alloc >= (1<<30)) { runtime·lock(&runtime·mheap); runtime·purgecachedstats(m); runtime·unlock(&runtime·mheap); }
c->local_total_alloc
は、現在のP(プロセッサ)のローカルキャッシュで割り当てられた総バイト数を追跡します。32ビットシステムでこの値が約1GBに達した場合、runtime·purgecachedstats
が呼び出され、統計情報がクリアされます。 -
runtime·mlookup
関数内: この関数は、指定されたアドレスがどのメモリ領域(MSpan
)に属するかをルックアップする際に使用されます。ルックアップ処理の後に、以下のチェックが追加されました。if (sizeof(void*) == 4 && m->mcache->local_nlookup >= (1<<30)) { runtime·lock(&runtime·mheap); runtime·purgecachedstats(m); runtime·unlock(&runtime·mheap); }
m->mcache->local_nlookup
は、現在のM(OSスレッド)のローカルキャッシュで実行されたポインタルックアップの回数を追跡します。32ビットシステムでこの値が約10億回に達した場合、同様にruntime·purgecachedstats
が呼び出され、統計情報がクリアされます。
これらの追加されたロジックは、32ビットシステムでのuintptr
型のカウンタがオーバーフローする前に、定期的に統計情報をリセットすることで、カウンタの正確性を維持し、潜在的な問題を防ぐためのものです。runtime·lock
とruntime·unlock
は、runtime·mheap
というグローバルなヒープロックを使い、統計情報のクリア処理がアトミックに行われることを保証します。
関連リンク
- Go言語のメモリ管理に関する公式ドキュメントやブログ記事
- Goのガベージコレクションに関する詳細な解説
uintptr
型のGo言語仕様
参考にした情報源リンク
- Goのソースコード (GitHub)
- Goのコードレビューシステム (Gerrit) - コミットメッセージに記載されている
https://golang.org/cl/6297047
は、このGerritの変更リストへのリンクです。 - Dave Cheneyのブログ - コミット作者のブログで、Goに関する技術的な洞察が共有されている場合があります。
- Goのベンチマーク結果 - Goの公式ベンチマークサイトで、様々なベンチマークの履歴データを確認できます。
- Goのランタイムに関するドキュメント (もしあれば)
- Goのメモリモデル