web-dev-qa-db-ja.com

golangメモリを分析する方法は?

実行時に1.2GBのメモリを使用するgolangプログラムを作成しました。

go tool pprof http://10.10.58.118:8601/debug/pprof/heapを呼び出すと、323.4MBのヒープ使用量のダンプが生成されます。

  • 残りのメモリ使用量はどうですか?
  • Golangランタイムメモリを説明するためのより良いツールはありますか?

gcvisを使用すると、これが得られます。

enter image description here

..およびこのヒープフォームプロファイル:

enter image description here

ここに私のコードがあります: https://github.com/sharewind/Push-server/blob/v3/broker

61
sharewind

ヒーププロファイルは、アクティブメモリ、ランタイムがgoプログラムによって使用されていると見なしているメモリ(つまり、ガベージコレクタによって収集されていないメモリ)を示します。 GCがメモリを収集すると、プロファイルは縮小しますが、システムにメモリは返されません。今後の割り当てでは、システムにさらに要求する前に、以前に収集したオブジェクトのプールからメモリを使用しようとします。

外部から見ると、これはプログラムのメモリ使用量が増加するか、レベルが維持されることを意味します。プログラムの「常駐サイズ」として外部システムが提示するのは、使用中のgo値を保持するか収集した値を保持するかに関係なく、プログラムに割り当てられるRAMのバイト数です。

これらの2つの数値がしばしばまったく異なる理由は、次のとおりです。

  1. メモリを収集するGCは、プログラムの外部ビューには影響しません
  2. メモリの断片化
  3. GCは、使用中のメモリが前のGCの後に使用中のメモリを2倍にしたときにのみ実行されます(デフォルトでは、 http://golang.org/pkg/runtime/#pkg-overview を参照)

Goがメモリをどのように認識するかを正確に分類したい場合は、runtime.ReadMemStats呼び出しを使用できます。 http://golang.org/pkg/runtime/#ReadMemStats

または、ブラウザでhttp://10.10.58.118:8601/debug/pprof/からプロファイリングデータにアクセスできる場合は、Webベースのプロファイリングを使用しているため、ヒープリンクをクリックすると、ランタイムの出力を含むヒーププロファイルのデバッグビューが表示されます。下部の.MemStats構造。

Runtime.MemStatsのドキュメント( http://golang.org/pkg/runtime/#MemStats )にはすべてのフィールドの説明がありますが、この議論で興味深いのは次のとおりです。

  • HeapAlloc:基本的にプロファイラーが提供するもの(アクティブヒープメモリ)
  • Alloc:HeapAllocに似ていますが、すべてgo go管理メモリ
  • Sys:OSから要求されたメモリの総量(アドレススペース)

Goがシステムに要求することと、OSがシステムに与えることは常に同じではないため、SysとOSが報告する内容との間には依然として矛盾があります。また、CGO/syscall(例:malloc/mmap)メモリはgoによって追跡されません。

53
Cookie of Nine

簡単に言うと、@ Cookie of Nineの答えに加えて、--alloc_spaceオプションを試すことができます。

go tool pprofはデフォルトで--inuse_spaceを使用します。メモリ使用量をサンプリングするため、結果は実際のサブセットです。
By --alloc_space pprofは、プログラムの開始以降に割り当てられたすべてのメモリを返します。

25
menghan

私は、Goアプリケーションの常駐メモリの増加について常に混乱していました。最後に、Goエコシステムに存在するプロファイリングツールを学ぶ必要がありました。ランタイムは runtime.Memstats 構造内に多くのメトリックを提供しますが、どれがメモリ増加の原因を見つけるのに役立つかを理解するのが難しい場合があるため、いくつかの追加ツールが必要です。

プロファイリング環境

アプリケーションで https://github.com/tevjef/go-runtime-metrics を使用します。たとえば、これをmainに入れることができます。

import(
    metrics "github.com/tevjef/go-runtime-metrics"
)
func main() {
    //...
    metrics.DefaultConfig.CollectionInterval = time.Second
    if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
        // handle error
    }
}

InfluxDBコンテナー内でGrafanaおよびDockerを実行します。

docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0

GrafanaInfluxDBGrafanaの相互作用を設定します(Grafanaメインページ->左上隅->データソース->新しいデータソースの追加):

enter image description here

ダッシュボードのインポート #3242 from https://grafana.com (Grafanaメインページ->左上隅->ダッシュボード->インポート):

enter image description here

最後に、アプリケーションを起動します。ランタイムメトリックスを、生成されたInfluxdbに送信します。アプリケーションに適切な負荷をかけます(私の場合は非常に小さく、数時間で5 RPS)。

メモリ消費分析

  1. SysRSSの同義語)曲線は、HeapSys曲線に非常に似ています。動的なメモリ割り当てが全体的なメモリ増加の主な要因であることが判明したため、スタック変数によって消費される少量のメモリは一定であるようで、無視できます。
  2. ゴルーチンの一定量は、ゴルーチンのリーク/スタック変数のリークがないことを保証します。
  3. 割り当てられたオブジェクトの合計量は、プロセスの存続期間中は同じままです(変動を考慮しても意味がありません)。
  4. 最も驚くべき事実: HeapIdleSysと同じ速度で成長していますが、 HeapReleased は常にゼロです。明らかに、少なくともこのテストの条件下では、ランタイムはメモリをOSに返しませんat all
HeapIdle minus HeapReleased estimates the amount of memory    
that could be returned to the OS, but is being retained by
the runtime so it can grow the heap without requesting more
memory from the OS.

enter image description hereenter image description here

メモリ消費の問題を調査しようとしている人には、いくつかの些細なエラー(ゴルーチンリークなど)を除外するために、説明されている手順に従うことをお勧めします。

メモリを明示的に解放する

debug.FreeOSMemory()を明示的に呼び出すことでメモリ消費を大幅に削減できるのは興味深いことです。

// in the top-level package
func init() {
   go func() {
       t := time.Tick(time.Second)
       for {
           <-t
           debug.FreeOSMemory()
       }
   }()
}

comparison

実際、このアプローチはデフォルトの状態と比較して約35%のメモリを節約しました。

13
Vitaly Isaev

StackImpact を使用することもできます。これは、通常および異常にトリガーされたメモリ割り当てプロファイルを自動的に記録し、ダッシュボードに報告します。詳細については、このブログ投稿を参照してください Production Goアプリケーションでのメモリリーク検出

enter image description here

免責事項:StackImpactで働いています

5
logix