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

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

このコミットは、Go言語のプロファイリングツールであるruntime/pprofおよびmisc/pprofにおけるヒーププロファイルの正確性を向上させるための変更です。具体的には、使用済みバイトがゼロのバケット(割り当てられたが現在使用されていないメモリ)をヒーププロファイルに含めるように修正し、デバッグモードでいくつかの不足していたMemStatsフィールドを追加することで、総割り当てのプロファイルがより正確になるように改善されています。

コミット

commit c4a814f2da2982a03f930b43797767f35721b5c1
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Thu May 31 07:57:49 2012 +0200

    runtime/pprof, misc/pprof: correct profile of total allocations.
    
    The previous heap profile format did not include buckets with
    zero used bytes. Also add several missing MemStats fields in
    debug mode.
    
    R=golang-dev, rsc
    CC=golang-dev, remy
    https://golang.org/cl/6249068

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

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

元コミット内容

このコミットは、Goのプロファイリングツールであるruntime/pprofmisc/pprofのヒーププロファイル機能に関する修正です。主な変更点は以下の通りです。

  1. 使用済みバイトがゼロのバケットのプロファイルへの追加: 以前のヒーププロファイル形式では、割り当てられたものの、現在は使用されていない(使用済みバイトがゼロの)メモリバケットが含まれていませんでした。このコミットにより、これらのバケットもプロファイルに含まれるようになり、総割り当てのより正確なビューが提供されます。
  2. 不足していたMemStatsフィールドの追加: デバッグモードにおいて、いくつかの重要なMemStatsフィールドがヒーププロファイル出力から欠落していました。このコミットは、これらのフィールド(Frees, HeapReleased, HeapObjectsなど)を追加し、より詳細なメモリ統計情報を提供します。

変更の背景

この変更の背景には、Goプログラムのメモリ使用状況をより正確に理解したいというニーズがありました。

  • 不正確なヒーププロファイル: 以前のヒーププロファイルは、割り当てられたが既に解放されたメモリ(使用済みバイトがゼロのバケット)を考慮していませんでした。これにより、プロファイルが示す「総割り当て」が実際のアプリケーションのメモリ割り当てパターンを完全に反映していない可能性がありました。例えば、一時的に大量のメモリを割り当ててすぐに解放するようなパターンがある場合、その割り当ての痕跡がプロファイルから見えにくくなっていました。この修正は、メモリのライフサイクル全体を把握するために、これらの「使用済みゼロ」の割り当てサイトもプロファイルに含めることを目的としています。
  • 不足するメモリ統計情報: MemStatsはGoランタイムのメモリ統計情報を提供する重要な構造体です。デバッグモードでのヒーププロファイル出力において、Frees(解放されたオブジェクトの総数)、HeapReleased(OSに解放されたヒープメモリの量)、HeapObjects(割り当てられたヒープオブジェクトの総数)といった重要なフィールドが欠落していました。これらの情報は、メモリリークの特定、ガベージコレクションの動作分析、および全体的なメモリ使用量の最適化において不可欠です。これらのフィールドを追加することで、開発者はより包括的なメモリ使用状況の洞察を得られるようになります。

これらの改善は、Goアプリケーションのパフォーマンスチューニングとメモリ最適化をより効果的に行うための基盤を強化するものです。

前提知識の解説

このコミットを理解するためには、以下のGo言語のプロファイリングとメモリ管理に関する基本的な知識が必要です。

  • Goのプロファイリング (pprof):

    • pprofはGo言語に組み込まれているプロファイリングツール群の総称です。CPU使用率、メモリ割り当て(ヒープ)、ゴルーチン、ブロッキング操作など、様々な側面からプログラムのパフォーマンスを分析できます。
    • runtime/pprofパッケージは、Goプログラム内でプロファイルデータを収集するためのAPIを提供します。
    • go tool pprofコマンドは、収集されたプロファイルデータを解析し、グラフやテキスト形式で可視化するためのツールです。
    • misc/pprofは、かつてGoプロジェクトに含まれていたPerlスクリプトベースのpprofツールを指します。これはGoogleで開発されたオリジナルのpprofツールであり、Goのプロファイリングにも使用されていましたが、現在はGoネイティブのgo tool pprofに置き換えられています。このコミットが作成された2012年時点では、まだPerlスクリプトが使用されていました。
  • ヒーププロファイリング:

    • ヒーププロファイリングは、プログラムがヒープメモリをどのように割り当て、使用しているかを分析するプロセスです。これにより、メモリリーク、過剰なメモリ割り当て、または非効率なメモリ使用パターンを特定できます。
    • Goのヒーププロファイルは、メモリが割り当てられたコールスタック(割り当てサイト)ごとに、割り当てられたバイト数と使用中のバイト数を記録します。
  • runtime.MemProfile関数:

    • runtime.MemProfileは、Goランタイムのメモリプロファイルを取得するための関数です。
    • func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool)というシグネチャを持ちます。
    • pはプロファイルレコードを格納するためのスライスです。
    • inuseZeroパラメータは非常に重要です。
      • inuseZerofalseの場合(以前のデフォルト動作)、現在使用中のメモリがある割り当てサイトのみがプロファイルに含まれます。つまり、割り当てられたが既に完全に解放されたメモリの記録は含まれません。
      • inuseZerotrueの場合、割り当てられたが現在使用中のバイトがゼロである(つまり、割り当てられたメモリがすべて解放された)割り当てサイトもプロファイルに含まれます。これにより、メモリの割り当てと解放の完全なライフサイクルを追跡できるようになります。
  • runtime.MemStats構造体:

    • runtime.MemStatsは、Goランタイムの現在のメモリ統計情報を含む構造体です。runtime.ReadMemStats関数を呼び出すことで取得できます。
    • この構造体には、ヒープの割り当て、システムメモリの使用量、ガベージコレクションの統計など、様々なメモリ関連のメトリクスが含まれています。
    • このコミットで追加された、または言及されている主要なフィールドは以下の通りです。
      • Frees: ガベージコレクタによって解放されたヒープオブジェクトの累積数。
      • HeapAlloc: ヒープに割り当てられたオブジェクトによって現在使用されているバイト数。
      • HeapSys: ヒープのためにOSから取得されたバイト数。
      • HeapIdle: ヒープ内でアイドル状態(使用されていない)のメモリのバイト数。
      • HeapInuse: ヒープ内で現在使用中のメモリのバイト数。
      • HeapReleased: OSに解放された物理メモリのバイト数。これは、アイドル状態のヒープスパンがOSに返却された量を示します。
      • HeapObjects: 割り当てられたヒープオブジェクトの総数。

技術的詳細

このコミットの技術的な変更は、主にGoランタイムのプロファイリングメカニズムと、それを解析するツール(Perlスクリプト)の両方に及びます。

  1. runtime.MemProfileinuseZeroパラメータの変更:

    • src/pkg/runtime/pprof/pprof.go内のcountHeap関数とwriteHeap関数において、runtime.MemProfileの呼び出しがinuseZero=falseからinuseZero=trueに変更されました。
    • この変更により、ヒーププロファイルには、割り当てられたが現在使用中のバイトがゼロである(つまり、割り当てられたメモリがすべて解放された)メモリバケットも含まれるようになります。これは、一時的なメモリ割り当てのパターンを正確に把握するために重要です。例えば、関数内で大きなバッファを割り当ててすぐに解放するような場合、以前はプロファイルに現れませんでしたが、この変更によりその割り当てサイトが記録されるようになります。これにより、メモリ使用量のピークや、ガベージコレクションの負荷に寄与する可能性のある割り当てパターンを特定しやすくなります。
  2. misc/pprof (Perlスクリプト) の修正:

    • misc/pprofスクリプトは、Goランタイムから出力されたプロファイルデータを解析し、可視化するためのツールです。
    • このスクリプトのReadHeapProfileサブルーチンにおいて、$n1$n2(それぞれ割り当てられたバイト数と使用中のバイト数に関連する変数)がゼロの場合に、$ratioの計算とそれに基づく$n1, $s1, $n2, $s2の調整を行わないように条件が追加されました。
    • 具体的には、if ($n1 > 0)if ($n2 > 0)という条件が追加されています。これは、ゼロ除算を防ぐとともに、使用済みバイトがゼロのバケットがプロファイルに含まれるようになった新しいデータ形式に対応するための変更です。これにより、プロファイルデータの解析がより堅牢になります。
  3. MemStatsフィールドの追加:

    • src/pkg/runtime/pprof/pprof.gowriteHeap関数において、デバッグモード(debug引数が設定されている場合)で出力されるMemStats情報に、以下のフィールドが追加されました。
      • Frees: 解放されたオブジェクトの総数。
      • HeapReleased: OSに解放されたヒープメモリの量。
      • HeapObjects: 割り当てられたヒープオブジェクトの総数。
    • これらのフィールドは、Goランタイムのメモリ管理の内部動作をより深く理解するために不可欠です。例えば、FreesはGCの効率を、HeapReleasedはOSへのメモリ返却の状況を、HeapObjectsはオブジェクトの総数を把握するのに役立ちます。これにより、開発者はメモリ使用量の傾向をより詳細に分析し、最適化の機会を見つけることができます。

これらの変更は、Goのプロファイリングツールが提供するメモリ使用状況の可視性を大幅に向上させ、開発者がより効果的にメモリ関連のパフォーマンス問題を診断・解決できるようにすることを目的としています。

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

diff --git a/misc/pprof b/misc/pprof
index 2fe56503c9..92009a1ce8 100755
--- a/misc/pprof
+++ b/misc/pprof
@@ -3753,15 +3753,19 @@ sub ReadHeapProfile {
         } else {
           # Remote-heap version 1
           my $ratio;
-          $ratio = (($s1*1.0)/$n1)/($sample_adjustment);\
-          if ($ratio < 1) {\
-            $n1 /= $ratio;\
-            $s1 /= $ratio;\
+          if ($n1 > 0) {\
+            $ratio = (($s1*1.0)/$n1)/($sample_adjustment);\
+            if ($ratio < 1) {\
+                $n1 /= $ratio;\
+                $s1 /= $ratio;\
+            }\
           }\
-          $ratio = (($s2*1.0)/$n2)/($sample_adjustment);\
-          if ($ratio < 1) {\
-            $n2 /= $ratio;\
-            $s2 /= $ratio;\
+          if ($n2 > 0) {\
+            $ratio = (($s2*1.0)/$n2)/($sample_adjustment);\
+            if ($ratio < 1) {\
+                $n2 /= $ratio;\
+                $s2 /= $ratio;\
+            }\
           }\
         }\
       }\
diff --git a/src/pkg/runtime/pprof/pprof.go b/src/pkg/runtime/pprof/pprof.go
index f67e8a8f9a..a0a5b7c0cc 100644
--- a/src/pkg/runtime/pprof/pprof.go
+++ b/src/pkg/runtime/pprof/pprof.go
@@ -352,26 +352,26 @@ func WriteHeapProfile(w io.Writer) error {\
 
 // countHeap returns the number of records in the heap profile.
 func countHeap() int {\
-\tn, _ := runtime.MemProfile(nil, false)\
+\tn, _ := runtime.MemProfile(nil, true)\
 \treturn n\
 }\
 
 // writeHeapProfile writes the current runtime heap profile to w.
 func writeHeap(w io.Writer, debug int) error {\
-\t// Find out how many records there are (MemProfile(nil, false)),\
+\t// Find out how many records there are (MemProfile(nil, true)),\
 \t// allocate that many records, and get the data.\
 \t// There\'s a race—more records might be added between\
 \t// the two calls—so allocate a few extra records for safety\
 \t// and also try again if we\'re very unlucky.\
 \t// The loop should only execute one iteration in the common case.\
 \tvar p []runtime.MemProfileRecord\
-\tn, ok := runtime.MemProfile(nil, false)\
+\tn, ok := runtime.MemProfile(nil, true)\
 \tfor {\
 \t\t// Allocate room for a slightly bigger profile,\
 \t\t// in case a few more entries have been added\
 \t\t// since the call to MemProfile.\
 \t\tp = make([]runtime.MemProfileRecord, n+50)\
-\t\tn, ok = runtime.MemProfile(p, false)\
+\t\tn, ok = runtime.MemProfile(p, true)\
 \t\tif ok {\
 \t\t\tp = p[0:n]\
 \t\t\tbreak\
@@ -431,11 +431,14 @@ func writeHeap(w io.Writer, debug int) error {\
 \t\tfmt.Fprintf(w, \"# Sys = %d\\n\", s.Sys)\
 \t\tfmt.Fprintf(w, \"# Lookups = %d\\n\", s.Lookups)\
 \t\tfmt.Fprintf(w, \"# Mallocs = %d\\n\", s.Mallocs)\
+\t\tfmt.Fprintf(w, \"# Frees = %d\\n\", s.Frees)\
 \n \t\tfmt.Fprintf(w, \"# HeapAlloc = %d\\n\", s.HeapAlloc)\
 \t\tfmt.Fprintf(w, \"# HeapSys = %d\\n\", s.HeapSys)\
 \t\tfmt.Fprintf(w, \"# HeapIdle = %d\\n\", s.HeapIdle)\
 \t\tfmt.Fprintf(w, \"# HeapInuse = %d\\n\", s.HeapInuse)\
+\t\tfmt.Fprintf(w, \"# HeapReleased = %d\\n\", s.HeapReleased)\
+\t\tfmt.Fprintf(w, \"# HeapObjects = %d\\n\", s.HeapObjects)\
 \n \t\tfmt.Fprintf(w, \"# Stack = %d / %d\\n\", s.StackInuse, s.StackSys)\
 \t\tfmt.Fprintf(w, \"# MSpan = %d / %d\\n\", s.MSpanInuse, s.MSpanSys)\

コアとなるコードの解説

このコミットは、Goのヒーププロファイリングの精度と詳細度を向上させるために、Goランタイムのプロファイリングコードと、それを解析するPerlスクリプトの両方に変更を加えています。

  1. src/pkg/runtime/pprof/pprof.go の変更点:

    • runtime.MemProfileinuseZeroパラメータの変更:
      • countHeap()関数とwriteHeap()関数内のruntime.MemProfileの呼び出しにおいて、第2引数(inuseZero)がfalseからtrueに変更されました。
      • 変更前: n, _ := runtime.MemProfile(nil, false)
      • 変更後: n, _ := runtime.MemProfile(nil, true)
      • この変更により、ヒーププロファイルには、割り当てられたが現在使用中のバイトがゼロである(つまり、割り当てられたメモリがすべて解放された)メモリバケットも含まれるようになります。これにより、メモリの割り当てと解放の完全なライフサイクルを追跡できるようになり、一時的なメモリ割り当てパターンもプロファイルに反映されるようになります。
    • MemStatsフィールドの追加:
      • writeHeap()関数内で、デバッグモード(debug引数が設定されている場合)で出力されるMemStats情報に、以下の3つのフィールドが追加されました。
        • # Frees = %d\n, s.Frees
        • # HeapReleased = %d\n, s.HeapReleased
        • # HeapObjects = %d\n, s.HeapObjects
      • これらのフィールドは、それぞれ解放されたオブジェクトの総数、OSに解放されたヒープメモリの量、割り当てられたヒープオブジェクトの総数を示します。これにより、メモリ使用状況に関するより詳細な統計情報がプロファイル出力に含まれるようになり、メモリリークの診断やパフォーマンスチューニングに役立ちます。
  2. misc/pprof (Perlスクリプト) の変更点:

    • sub ReadHeapProfileサブルーチン内で、$n1$n2(それぞれ割り当てられたバイト数と使用中のバイト数に関連する変数)がゼロの場合に、$ratioの計算とそれに基づく$n1, $s1, $n2, $s2の調整を行わないように条件が追加されました。
    • 変更前は、$n1$n2がゼロの場合にゼロ除算が発生する可能性がありましたが、if ($n1 > 0)if ($n2 > 0)という条件が追加されたことで、この問題が解決されました。
    • この修正は、runtime.MemProfileinuseZero=trueで呼び出されるようになった結果、プロファイルデータにn1n2がゼロとなるエントリが含まれるようになったことへの対応です。これにより、Perlスクリプトが新しい形式のプロファイルデータを正しく解析できるようになります。

これらの変更は相互に関連しており、Goのヒーププロファイリング機能の全体的な正確性と有用性を向上させることを目的としています。

関連リンク

参考にした情報源リンク