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

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

このコミットは、Goランタイムのメモリ統計(MemStats)におけるシステムメモリの計上方法を改善し、より正確なメモリ使用状況の把握を可能にするものです。特に、これまでMemStatsに計上されていなかった様々なシステム割り当て(GCビットマップ、スパンテーブル、GCルートブロックなど)をGCSysOtherSysという新しい統計カテゴリに分類し、Sys(システムから取得した総メモリ量)がこれらのカテゴリの合計と一致するように修正しています。

コミット

commit a33ef8d11b9db6646991bee5732015562fd4efd2
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Fri Sep 6 16:55:40 2013 -0400

    runtime: account for all sys memory in MemStats
    Currently lots of sys allocations are not accounted in any of XxxSys,
    including GC bitmap, spans table, GC roots blocks, GC finalizer blocks,
    iface table, netpoll descriptors and more. Up to ~20% can unaccounted.
    This change introduces 2 new stats: GCSys and OtherSys for GC metadata
    and all other misc allocations, respectively.
    Also ensures that all XxxSys indeed sum up to Sys. All sys memory allocation
    functions require the stat for accounting, so that it's impossible to miss something.
    Also fix updating of mcache_sys/inuse, they were not updated after deallocation.
    
    test/bench/garbage/parser before:
    Sys             670064344
    HeapSys         610271232
    StackSys        65536
    MSpanSys        14204928
    MCacheSys       16384
    BuckHashSys     1439992
    
    after:
    Sys             670064344
    HeapSys         610271232
    StackSys        65536
    MSpanSys        14188544
    MCacheSys       16384
    BuckHashSys     3194304
    GCSys           39198688
    OtherSys        3129656
    
    Fixes #5799.
    
    R=rsc, dave, alex.brainman
    CC=golang-dev
    https://golang.org/cl/12946043

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

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

元コミット内容

GoランタイムのMemStatsにおいて、システムから取得したメモリ(Sys)の計上が不完全であった問題を修正します。具体的には、GCビットマップ、スパンテーブル、GCルートブロック、GCファイナライザブロック、ifaceテーブル、netpollディスクリプタなど、多くのシステム割り当てがXxxSysのいずれにも計上されておらず、最大で約20%のメモリが未計上になる可能性がありました。

この変更では、GCメタデータ用のGCSysと、その他の雑多な割り当て用のOtherSysという2つの新しい統計カテゴリを導入します。これにより、すべてのXxxSysSysの合計と一致するようにします。また、すべてのシステムメモリ割り当て関数が計上用の統計カテゴリを要求するように変更することで、メモリの計上漏れを防ぎます。さらに、mcache_sys/inuseが解放後に更新されていなかった問題も修正します。

変更前後のtest/bench/garbage/parserの出力例が示されており、GCSysOtherSysが追加され、BuckHashSysMSpanSysの値が調整されていることが確認できます。

変更の背景

Goランタイムは、プログラムのメモリ使用状況を詳細に把握するためにMemStatsという構造体を提供しています。しかし、このコミットが導入される以前は、MemStatsが報告するシステムから取得した総メモリ量(Sys)と、個々のカテゴリ(HeapSys, StackSysなど)の合計との間に乖離がありました。これは、Goランタイムが内部的に使用する一部のメモリ(例えば、ガベージコレクションの内部構造やネットワークポーリングのディスクリプタなど)が、既存のMemStatsカテゴリのいずれにも適切に分類・計上されていなかったためです。

この計上漏れは、メモリプロファイリングやデバッグにおいて、Goプログラムが実際にどれだけのシステムメモリを使用しているのかを正確に把握することを困難にしていました。特に、大規模なアプリケーションや長時間稼働するサービスでは、この未計上メモリが無視できない量になる可能性があり、メモリリークの検出やパフォーマンスチューニングの妨げとなることが懸念されました。

この問題を解決するため、Goランタイムのメモリ計上メカニズムを改善し、すべてのシステム割り当てがMemStatsに適切に反映されるようにする必要がありました。これが、GCSysOtherSysの導入、およびシステムメモリ割り当て関数の変更の背景となります。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムのメモリ管理に関する基本的な知識が必要です。

  1. MemStats: Goランタイムが提供するメモリ統計情報を含む構造体です。runtime.ReadMemStats()関数を通じて取得でき、ヒープ、スタック、GC、システムメモリなど、様々なカテゴリのメモリ使用量に関する詳細な情報を提供します。

    • Sys: Goランタイムがオペレーティングシステムから取得した総メモリ量を示します。
    • HeapSys: ヒープ領域のためにシステムから取得したメモリ量。
    • StackSys: スタック領域のためにシステムから取得したメモリ量。
    • MSpanSys: メモリスパン(Goのメモリ管理における連続したページ群)のためにシステムから取得したメモリ量。
    • MCacheSys: メモリキャッシュ(Mキャッシュ)のためにシステムから取得したメモリ量。
    • BuckHashSys: プロファイリング用のバケットハッシュテーブルのためにシステムから取得したメモリ量。
    • GCSys (新規): ガベージコレクションのメタデータのためにシステムから取得したメモリ量。
    • OtherSys (新規): その他の雑多なランタイム割り当てのためにシステムから取得したメモリ量。
  2. Goのメモリ管理の階層: Goランタイムは、OSからメモリを要求し、それを独自のメモリ管理システムで管理します。

    • OSからのメモリ取得: mmapVirtualAllocなどのシステムコールを通じて、OSから仮想メモリ領域を確保します。
    • アリーナ (Arena): 確保した仮想メモリ領域を、Goランタイムが管理する大きなチャンク(アリーナ)に分割します。
    • スパン (Span): アリーナをさらに小さな単位であるスパンに分割します。スパンは1ページ以上の連続したメモリページで構成され、特定のサイズのオブジェクトを格納するために使用されます。
    • オブジェクト: 実際にGoプログラムが使用するデータ構造(オブジェクト)がスパン内に割り当てられます。
  3. SysAlloc, SysFree, SysMap: GoランタイムがOSと直接やり取りするための低レベルなメモリ管理関数です。

    • runtime.SysAlloc(nbytes): OSからnbytes分のメモリを割り当てます。
    • runtime.SysFree(v, nbytes): vから始まるnbytes分のメモリをOSに解放します。
    • runtime.SysMap(v, nbytes): 以前に予約されたアドレス空間を実際に使用可能にするためにマップします。
  4. persistentalloc: Goランタイム内部で、ガベージコレクションの対象とならない永続的なデータ構造(型情報、インターフェーステーブルなど)を割り当てるために使用される関数です。

  5. FixAlloc: 固定サイズのオブジェクトを効率的に割り当てるためのフリーリストアロケータです。MSpanMCacheなどのランタイム内部構造の割り当てに使用されます。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. MemStats構造体の拡張: src/pkg/runtime/mem.goにおいて、MemStats構造体にGCSysOtherSysの2つのフィールドが追加されました。

    type MemStats struct {
        // ... 既存のフィールド ...
        GCSys       uint64 // GC metadata
        OtherSys    uint64 // other system allocations
    }
    

    これにより、GC関連のメタデータと、その他の雑多なランタイム内部割り当てが個別に計上されるようになります。

  2. システムメモリ割り当て関数の変更: runtime.SysAlloc, runtime.SysFree, runtime.SysMapといったOSレベルのメモリ操作関数に、uint64 *statという引数が追加されました。このstat引数は、割り当てまたは解放されたメモリ量を計上すべきMemStats内のカウンタ(例: mstats.heap_sys, mstats.gc_sys, mstats.other_sysなど)へのポインタです。

    • 変更前: これらの関数は直接mstats.sysを更新していました。
    • 変更後: 各呼び出し元が、その割り当てがどのカテゴリに属するかを示すstatポインタを渡すことで、より粒度の高い計上が可能になりました。
    • runtime.xadd64(stat, n): アトミックにstatが指すカウンタにnを加算する関数が使用されています。これにより、並行処理環境下でも正確な計上が保証されます。
  3. persistentalloc関数の変更: runtime.persistentalloc関数にもuint64 *stat引数が追加されました。これにより、永続的な割り当てがどのカテゴリに属するかを明示的に指定できるようになりました。

    • 特に、persistentallocmstats.other_sys以外の統計カテゴリに計上されるべきメモリを割り当てた場合、mstats.other_sysからその分を減算し、指定されたstatに加算するロジックが追加されています。これは、persistentallocがデフォルトでother_sysに計上されるが、特定の用途(例: buckhash_sys)のために割り当てられた場合は、そのカテゴリに再計上する必要があるためです。
  4. FixAlloc_Init関数の変更: FixAllocアロケータの初期化関数runtime.FixAlloc_Initにもuint64 *stat引数が追加されました。これにより、FixAllocが管理するメモリ(inusesys)が、指定された統計カテゴリに計上されるようになります。

    • FixAllocsysフィールドがuintptrからuint64*に変更され、直接MemStatsのカウンタを指すようになりました。
  5. 既存のメモリ割り当て箇所の更新: Goランタイム内の様々な場所で、runtime.SysAlloc, runtime.SysFree, runtime.SysMap, runtime.persistentalloc, runtime.FixAlloc_Initの呼び出しが、新しいstat引数を渡すように修正されました。

    • 例: GCビットマップ、スパンテーブル、GCルートブロック、ファイナライザブロック、ifaceテーブル、netpollディスクリプタ、プロファイリング用ハッシュテーブルなどの割り当てが、それぞれmstats.gc_sysmstats.other_sysmstats.buckhash_sysに計上されるようになりました。
  6. mcache_sys/inuse更新の修正: runtime.allocmcache関数内で、mstats.mcache_inusemstats.mcache_sysの更新が削除されました。これは、FixAllocの変更により、MCacheの割り当てがFixAllocを通じて自動的にmstats.mcache_sysに計上されるようになったため、重複する更新が不要になったためです。

  7. MemStatsSys合計の再計算: src/pkg/runtime/mgc0.cupdatememstats関数内で、mstats.sysの計算ロジックが更新され、新しいGCSysOtherSysを含むすべてのXxxSysカテゴリの合計として計算されるようになりました。

    mstats.sys = mstats.heap_sys + mstats.stacks_sys + mstats.mspan_sys +
        mstats.mcache_sys + mstats.buckhash_sys + mstats.gc_sys + mstats.other_sys;
    

    これにより、MemStats.Sysが常にすべてのシステム割り当ての合計を正確に反映するようになります。

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

このコミットは、Goランタイムのメモリ管理の根幹に関わる多数のファイルに影響を与えています。主要な変更箇所は以下の通りです。

  • src/pkg/runtime/mem.go: MemStats構造体にGCSysOtherSysフィールドを追加。
  • src/pkg/runtime/malloc.h: runtime.SysAlloc, runtime.SysFree, runtime.SysMap, runtime.persistentalloc, runtime.FixAlloc_Init関数のシグネチャにuint64 *stat引数を追加。FixAlloc構造体のsysフィールドをuint64*に変更。
  • src/pkg/runtime/malloc.goc: runtime.MHeap_SysAllocruntime.persistentallocの呼び出し箇所を更新し、stat引数を渡すように変更。runtime.allocmcacheからmcache_inuse/mcache_sysの直接更新を削除。
  • src/pkg/runtime/mem_darwin.c, mem_freebsd.c, mem_linux.c, mem_netbsd.c, mem_openbsd.c, mem_plan9.c, mem_windows.c: 各OS固有のSysAlloc, SysFree, SysMapの実装を更新し、stat引数を受け取り、runtime.xadd64を使用してstatを更新するように変更。
  • src/pkg/runtime/mfixalloc.c: runtime.FixAlloc_Initの実装を更新し、stat引数を受け取り、f->statに保存するように変更。FixAlloc_Alloc内でpersistentallocを呼び出す際にf->statを渡すように変更。
  • src/pkg/runtime/mgc0.c: GC関連のメモリ割り当て(ワークバッファ、GCルート、ファイナライザブロック、ビットマップ)をruntime.SysAllocruntime.persistentallocで割り当てる際に、mstats.gc_sysstatとして渡すように変更。updatememstats関数でmstats.sysの計算ロジックを更新。
  • src/pkg/runtime/mheap.c: MSpanMCacheの割り当てに関連するruntime.SysAllocruntime.FixAlloc_Initの呼び出しを更新し、mstats.other_sysmstats.mspan_sysmstats.mcache_sysstatとして渡すように変更。
  • src/pkg/runtime/mprof.goc: プロファイリング関連のメモリ割り当て(バケットハッシュテーブル)をruntime.SysAllocruntime.persistentallocで割り当てる際に、mstats.buckhash_sysstatとして渡すように変更。
  • src/pkg/runtime/netpoll.goc: ネットワークポーリングディスクリプタの割り当てをruntime.persistentallocで割り当てる際に、mstats.other_sysstatとして渡すように変更。
  • src/pkg/runtime/stack.c: スタックキャッシュの割り当てをruntime.SysAllocで割り当てる際に、mstats.stacks_sysstatとして渡すように変更。
  • src/pkg/runtime/malloc_test.go: TestMemStatsという新しいテストが追加され、MemStatsの各XxxSysフィールドがゼロでないこと、およびSysがすべてのXxxSysの合計と一致することを検証しています。

コアとなるコードの解説

このコミットの核心は、GoランタイムがOSからメモリを要求するすべての箇所において、そのメモリがどの目的のために使用されるのかを明示的に指定し、それに対応するMemStatsのカウンタを更新するようにした点です。

例えば、src/pkg/runtime/malloc.gocruntime.MHeap_SysAlloc関数では、ヒープ領域のためにOSからメモリを割り当てる際に、runtime.SysAlloc(n, &mstats.heap_sys)のように&mstats.heap_sysstat引数として渡しています。これにより、割り当てられたメモリ量が正確にMemStats.HeapSysに計上されるようになります。

同様に、src/pkg/runtime/mgc0.cでは、GCのワークバッファやルートブロックなどの割り当てに&mstats.gc_sysが使用されています。

// src/pkg/runtime/mgc0.c (抜粋)
// GCワークバッファの割り当て
work.chunk = runtime·SysAlloc(work.nchunk, &mstats.gc_sys);

// GCルートブロックの割り当て
new = (Obj*)runtime·SysAlloc(cap*sizeof(Obj), &mstats.gc_sys);

// ファイナライザブロックの割り当て
finc = runtime·persistentalloc(PageSize, 0, &mstats.gc_sys);

src/pkg/runtime/mprof.gocでは、プロファイリング用のバケットハッシュテーブルの割り当てに&mstats.buckhash_sysが使用されています。

// src/pkg/runtime/mprof.goc (抜粋)
// プロファイリングバケットハッシュテーブルの割り当て
buckhash = runtime·SysAlloc(BuckHashSize*sizeof buckhash[0], &mstats.buckhash_sys);

persistentalloc関数は、永続的なメモリ割り当てに使用されますが、このコミットにより、その割り当てがどのMemStatsカテゴリに属するかを柔軟に指定できるようになりました。もし指定されたstatがデフォルトのmstats.other_sysと異なる場合、other_sysから減算し、指定されたstatに加算することで、二重計上を防ぎつつ正確なカテゴリ分けを実現しています。

// src/pkg/runtime/malloc.goc (抜粋)
void*
runtime·persistentalloc(uintptr size, uintptr align, uint64 *stat)
{
    // ... 既存のロジック ...
    if(stat != &mstats.other_sys) {
        // reaccount the allocation against provided stat
        runtime·xadd64(stat, size);
        runtime·xadd64(&mstats.other_sys, -(uint64)size);
    }
    return p;
}

これらの変更により、GoランタイムがOSから取得するすべてのメモリが、MemStats内の適切なカテゴリに正確に計上されるようになり、MemStats.SysがすべてのXxxSysカテゴリの合計と常に一致することが保証されます。これは、Goプログラムのメモリ使用状況をより透明にし、開発者がパフォーマンスの問題を診断する上で非常に役立ちます。

関連リンク

  • Go Issue #5799: runtime: MemStats.Sys is not sum of XxxSys
  • Go Code Review 12946043: runtime: account for all sys memory in MemStats
    • このコミットのコードレビューページです。変更内容に関するコメントや議論が含まれています。
    • https://golang.org/cl/12946043

参考にした情報源リンク

  • GoのMemStatsに関する公式ドキュメントや解説記事:
  • Goランタイムのメモリ管理に関する一般的な情報源:
    • Goのメモリ管理の仕組みを解説しているブログ記事やドキュメント(例: GoのGCの仕組み、Goのメモリ割り当て戦略など)
    • Goのソースコード(特にsrc/runtimeディレクトリ)

(注: 上記の参考情報源リンクは、一般的なGoランタイムのメモリ管理に関する情報源の例であり、このコミットに直接言及しているとは限りません。コミット当時のGoのドキュメントやコミュニティの議論を参照することで、より詳細な背景情報を得られる可能性があります。)