[インデックス 18996] ファイルの概要
このコミットは、Goランタイムのデバッグ機能である GODEBUG=allocfreetrace=1
と GODEBUG=gcdead=1
の挙動を調整し、改善することを目的としています。特に allocfreetrace
については、ヒーププロファイリングとの結合を解消し、より正確で有用なスタックトレース情報を提供するように変更されました。また、gcdead
については、デッドポインタの検出精度を向上させています。
コミット
commit 1ec4d5e9e775b2adcf7dd2e464a10854bad09803
Author: Russ Cox <rsc@golang.org>
Date: Tue Apr 1 13:30:10 2014 -0400
runtime: adjust GODEBUG=allocfreetrace=1 and GODEBUG=gcdead=1
GODEBUG=allocfreetrace=1:
The allocfreetrace=1 mode prints a stack trace for each block
allocated and freed, and also a stack trace for each garbage collection.
It was implemented by reusing the heap profiling support: if allocfreetrace=1
then the heap profile was effectively running at 1 sample per 1 byte allocated
(always sample). The stack being shown at allocation was the stack gathered
for profiling, meaning it was derived only from the program counters and
did not include information about function arguments or frame pointers.
The stack being shown at free was the allocation stack, not the free stack.
If you are generating this log, you can find the allocation stack yourself, but
it can be useful to see exactly the sequence that led to freeing the block:
was it the garbage collector or an explicit free? Now that the garbage collector
runs on an m0 stack, the stack trace for the garbage collector was never interesting.
Fix all these problems:
1. Decouple allocfreetrace=1 from heap profiling.
2. Print the standard goroutine stack traces instead of a custom format.
3. Print the stack trace at time of allocation for an allocation,
and print the stack trace at time of free (not the allocation trace again)
for a free.
4. Print all goroutine stacks at garbage collection. Having all the stacks
means that you can see the exact point at which each goroutine was
preempted, which is often useful for identifying liveness-related errors.
GODEBUG=gcdead=1:
This mode overwrites dead pointers with a poison value.
Detect the poison value as an invalid pointer during collection,
the same way that small integers are invalid pointers.
LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/81670043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1ec4d5e9e775b2adcf7dd2e464a10854bad09803
元コミット内容
runtime: adjust GODEBUG=allocfreetrace=1 and GODEBUG=gcdead=1
GODEBUG=allocfreetrace=1:
The allocfreetrace=1 mode prints a stack trace for each block
allocated and freed, and also a stack trace for each garbage collection.
It was implemented by reusing the heap profiling support: if allocfreetrace=1
then the heap profile was effectively running at 1 sample per 1 byte allocated
(always sample). The stack being shown at allocation was the stack gathered
for profiling, meaning it was derived only from the program counters and
did not include information about function arguments or frame pointers.
The stack being shown at free was the allocation stack, not the free stack.
If you are generating this log, you can find the allocation stack yourself, but
it can be useful to see exactly the sequence that led to freeing the block:
was it the garbage collector or an explicit free? Now that the garbage collector
runs on an m0 stack, the stack trace for the garbage collector was never interesting.
Fix all these problems:
1. Decouple allocfreetrace=1 from heap profiling.
2. Print the standard goroutine stack traces instead of a custom format.
3. Print the stack trace at time of allocation for an allocation,
and print the stack trace at time of free (not the allocation trace again)
for a free.
4. Print all goroutine stacks at garbage collection. Having all the stacks
means that you can see the exact point at which each goroutine was
preempted, which is often useful for identifying liveness-related errors.
GODEBUG=gcdead=1:
This mode overwrites dead pointers with a poison value.
Detect the poison value as an invalid pointer during collection,
the same way that small integers are invalid pointers.
LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/81670043
変更の背景
このコミットが行われた背景には、Goランタイムのデバッグ機能、特にメモリ割り当てと解放の追跡 (allocfreetrace
) およびガベージコレクション (GC) のデバッグ (gcdead
) における既存の課題がありました。
-
allocfreetrace=1
の問題点:- ヒーププロファイリングとの結合: 以前の
allocfreetrace=1
は、ヒーププロファイリングのメカニズムを再利用していました。これにより、allocfreetrace=1
を有効にすると、実質的にヒーププロファイリングが「常にサンプリング」される状態になり、オーバーヘッドが大きくなる可能性がありました。 - スタックトレースの品質: 割り当て時に表示されるスタックトレースは、プロファイリングのために収集されたものであり、プログラムカウンタのみから導出されていました。そのため、関数引数やフレームポインタに関する情報が含まれておらず、デバッグの際に十分な情報を提供できませんでした。
- 解放時のスタックトレースの不正確さ: メモリ解放時に表示されるスタックトレースは、解放時のものではなく、割り当て時のスタックトレースが再利用されていました。これにより、実際にメモリが解放された経緯(GCによるものか、明示的な解放かなど)を追跡することが困難でした。
- GC時のスタックトレースの有用性の欠如: GCが
m0
スタックで実行されるようになったため、GC時のスタックトレースはほとんど有用な情報を提供していませんでした。GCがどのゴルーチンをプリエンプトしたかなど、並行処理におけるライブネス関連のエラーを特定する上で重要な情報が不足していました。
- ヒーププロファイリングとの結合: 以前の
-
gcdead=1
の改善:gcdead=1
はデッドポインタを特定の「ポイズン値」で上書きする機能ですが、このポイズン値がGC中に無効なポインタとして正しく検出されるように、検出ロジックの改善が必要でした。
これらの問題に対処し、Goランタイムのデバッグ機能をより強力で使いやすいものにすることが、このコミットの主要な動機となりました。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムおよびデバッグに関する概念を理解しておく必要があります。
GODEBUG
環境変数: Goプログラムの実行時にランタイムの挙動を制御するための環境変数です。様々なデバッグオプションやパフォーマンスチューニングオプションを提供します。例えば、GODEBUG=gctrace=1
はGCのトレース情報を出力します。allocfreetrace
:GODEBUG
のオプションの一つで、メモリの割り当て (allocation) と解放 (free) のイベントをトレースし、関連するスタックトレースを出力する機能です。メモリリークの特定や、メモリ使用パターンの分析に役立ちます。gcdead
:GODEBUG
のオプションの一つで、ガベージコレクションによって「デッド」と判断されたポインタ(参照されなくなったメモリ領域へのポインタ)を特定の「ポイズン値」で上書きする機能です。これにより、デッドポインタが誤って使用された場合に、その不正なアクセスを早期に検出することができます。- ヒーププロファイリング: Goの
pprof
ツールなどで提供される機能で、プログラムがヒープメモリをどのように使用しているかを分析するためのものです。メモリ割り当ての頻度、サイズ、場所などを統計的に収集し、メモリリークや過剰なメモリ使用を特定するのに役立ちます。 - スタックトレース: プログラムの実行中に、ある時点での関数呼び出しの連鎖(コールスタック)を示す情報です。デバッグ時に、エラーが発生した場所や、特定の関数がどのように呼び出されたかを特定するために不可欠です。
- ゴルーチン (Goroutine): Goの軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数千から数百万のゴルーチンを同時に実行できます。
m0
スタック: Goランタイム内部で使用される特別なスタックの一つです。ガベージコレクタなどのランタイムの重要な処理は、ユーザーゴルーチンのスタックとは異なるm0
スタックで実行されることがあります。- ポイズン値 (Poison Value): デバッグ目的で、無効なデータや解放されたメモリ領域に書き込まれる特定のパターン値です。これにより、その領域が誤ってアクセスされた場合に、その値が異常であることを検出できます。このコミットでは
0x6969696969696969LL
という値がPoisonPtr
として定義されています。
技術的詳細
このコミットは、前述の問題点を解決するために、以下の技術的な変更を導入しています。
-
allocfreetrace=1
とヒーププロファイリングの分離:- 以前は
allocfreetrace=1
が有効な場合、ヒーププロファイリングが常にサンプリングされるように動作していました。このコミットでは、allocfreetrace
のロジックをヒーププロファイリング (MProf_Malloc
,MProf_Free
) から分離し、runtime·tracealloc
、runtime·tracefree
、runtime·tracegc
という新しい関数を導入しました。これにより、allocfreetrace
は独立して動作し、ヒーププロファイリングのオーバーヘッドなしにトレース情報を出力できるようになりました。 runtime/malloc.goc
のruntime·mallocgc
関数内で、runtime·debug.allocfreetrace
が真の場合にruntime·tracealloc
を直接呼び出すように変更されています。また、profilealloc
の呼び出しパスからtyp
引数が削除され、MProf_Malloc
も同様に変更されています。
- 以前は
-
標準ゴルーチンスタックトレースの利用:
- 以前の
allocfreetrace
はカスタムフォーマットでスタックトレースを出力していましたが、このコミットではruntime·traceback
関数を利用して、標準のゴルーチンスタックトレースを出力するように変更されました。これにより、より詳細で理解しやすいスタック情報(関数引数やフレームポインタを含む可能性)が提供されます。 runtime/mprof.goc
からprintstackframes
関数が削除され、代わりにruntime·tracealloc
、runtime·tracefree
、runtime·tracegc
内でruntime·traceback
やruntime·tracebackothers
が使用されています。
- 以前の
-
割り当て時と解放時の正確なスタックトレース:
- 割り当て時には
runtime·tracealloc
が呼び出され、その時点でのスタックトレースが出力されます。 - 解放時には
runtime·tracefree
が呼び出され、その時点でのスタックトレースが出力されます。これにより、メモリがGCによって解放されたのか、あるいは明示的なfree
呼び出しによって解放されたのかを正確に区別できるようになりました。 runtime/malloc.goc
のruntime·free
関数にruntime·tracefree
の呼び出しが追加され、runtime/mgc0.c
のruntime·MSpan_Sweep
関数内でもruntime·tracefree
が呼び出されるようになりました。
- 割り当て時には
-
GC時の全ゴルーチンスタックトレースの出力:
- GCが実行される際に、
runtime·tracegc
が呼び出され、その時点で実行中の全てのゴルーチンのスタックトレースが出力されるようになりました。これは、特に並行処理において、GCがどのゴルーチンをどの時点でプリエンプトしたかを把握する上で非常に有用です。ライブネス関連のバグ(例えば、あるゴルーチンがGCによって一時停止され、その間に別のゴルーチンが予期せぬ状態になった場合など)の特定に役立ちます。 runtime/mgc0.c
のruntime·gc
関数内でruntime·tracegc
が呼び出されるようになりました。また、m->traceback = 2
の設定により、トレースバックの深度が調整されています。
- GCが実行される際に、
-
gcdead=1
のポイズン値検出の改善:gcdead=1
が有効な場合、デッドポインタをPoisonPtr
(値0x6969696969696969LL
) で上書きします。このコミットでは、GC中のポインタスキャンにおいて、このPoisonPtr
が無効なポインタとして正しく検出されるようにロジックが追加されました。これは、小さな整数値が無効なポインタとして扱われるのと同様のメカニズムです。runtime/malloc.h
でPoisonPtr
が定義され、runtime/mgc0.c
のscanbitvector
関数内で、ポインタがPoisonPtr
と等しい場合に無効なポインタとして扱う条件が追加されています。
これらの変更により、Goランタイムのデバッグ機能は大幅に強化され、開発者がメモリ関連の問題や並行処理のバグをより効率的に診断できるようになりました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
src/pkg/runtime/malloc.goc
:profilealloc
関数のシグネチャからtyp
引数が削除されました。runtime·mallocgc
内でruntime·debug.allocfreetrace
が有効な場合にprofilealloc
の代わりにruntime·tracealloc
を呼び出すように変更されました。runtime·free
関数にruntime·tracefree
の呼び出しが追加されました。
src/pkg/runtime/malloc.h
:runtime·tracealloc
,runtime·tracefree
,runtime·tracegc
のプロトタイプ宣言が追加されました。runtime·MProf_Malloc
とruntime·MProf_Free
のシグネチャが変更され、typ
やvoid *p
引数が削除されました。runtime·MProf_TraceGC
の宣言が削除されました。PoisonPtr
マクロが定義されました。
src/pkg/runtime/mgc0.c
:scanbitvector
関数内で、runtime·debug.gcdead
が有効な場合にPoisonPtr
を使用してデッドポインタを上書きするように変更されました。また、ポインタがPoisonPtr
と等しい場合を無効なポインタとして検出するロジックが追加されました。runtime·MSpan_Sweep
関数内で、runtime·debug.allocfreetrace
が有効な場合にruntime·tracefree
を呼び出すように変更されました。runtime·gchelper
およびruntime·gc
関数内でm->traceback = 2
の設定とruntime·tracegc
の呼び出しが追加されました。
src/pkg/runtime/mheap.c
:runtime·MProf_Free
の呼び出し箇所で引数が変更されました。
src/pkg/runtime/mprof.goc
:typeinfoname
、printstackframes
、runtime·MProf_TraceGC
関数が削除されました。runtime·MProf_Malloc
およびruntime·MProf_Free
からallocfreetrace
関連のロジック(runtime·printf
やprintstackframes
の呼び出し)が削除され、シグネチャも変更されました。runtime·tracealloc
、runtime·tracefree
、runtime·tracegc
の新しい実装が追加されました。これらの関数は、tracelock
を使用して排他制御を行い、runtime·printf
やruntime·traceback
を利用して詳細なトレース情報を出力します。
src/pkg/runtime/stack.c
:adjustpointers
関数内で、ポインタがPoisonPtr
と等しい場合を無効なポインタとして検出する条件が追加されました。
コアとなるコードの解説
このコミットの核心は、allocfreetrace
の機能をヒーププロファイリングから完全に分離し、より詳細で正確なスタックトレース情報を提供する新しいトレース関数群を導入した点にあります。
-
runtime·tracealloc(void *p, uintptr size, uintptr typ)
:- メモリが割り当てられた直後に呼び出されます。
- 割り当てられたメモリのアドレス
p
、サイズsize
、型情報typ
を引数に取ります。 tracelock
を取得して排他制御を行い、複数のゴルーチンからの同時呼び出しによる出力の混在を防ぎます。m->traceback = 2
を設定することで、スタックトレースの深度を調整し、より詳細な情報を取得できるようにします。runtime·printf
を使用して、割り当てられたオブジェクトのアドレス、サイズ、型情報を出力します。runtime·goroutineheader(g)
とruntime·traceback(...)
を呼び出すことで、現在のゴルーチンのヘッダ情報と、割り当てが行われた時点の正確なスタックトレースを出力します。これにより、どのコードパスがメモリ割り当てを引き起こしたかを明確に把握できます。
-
runtime·tracefree(void *p, uintptr size)
:- メモリが解放された直後に呼び出されます。これは、明示的な
free
呼び出しによるものか、GCによるものかに関わらず呼び出されます。 - 解放されたメモリのアドレス
p
とサイズsize
を引数に取ります。 tracelock
を取得し、m->traceback = 2
を設定します。runtime·printf
で解放されたオブジェクトのアドレスとサイズを出力します。runtime·goroutineheader(g)
とruntime·traceback(...)
を呼び出すことで、解放が行われた時点の正確なスタックトレースを出力します。これにより、メモリがどのように、そしてどのコードパスによって解放されたかを追跡できます。
- メモリが解放された直後に呼び出されます。これは、明示的な
-
runtime·tracegc(void)
:- ガベージコレクションが開始される際に呼び出されます。
tracelock
を取得し、m->traceback = 2
を設定します。runtime·printf("tracegc()\\n")
を出力し、GCの開始を示します。runtime·tracebackothers(g)
を呼び出す点が重要です。これは、GCを実行しているゴルーチン(通常はg0
スタック)以外の、全てのユーザーゴルーチンのスタックトレースを出力します。これにより、GCが実行された瞬間に各ゴルーチンがどのような状態にあったか、どこでプリエンプトされたかといった情報が得られ、並行処理におけるライブネス問題のデバッグに非常に役立ちます。- GCトレースの終了を示す
end tracegc
と改行を出力します。
-
PoisonPtr
の導入と検出ロジック:#define PoisonPtr ((uintptr)0x6969696969696969LL)
により、デッドポインタを上書きするための特定の64ビット値が定義されました。src/pkg/runtime/mgc0.c
のscanbitvector
関数では、GC中にポインタをスキャンする際、precise
モードでポインタがPageSize
未満であるか、またはPoisonPtr
と等しい場合に、それを無効なポインタとして検出するロジックが追加されました。これにより、gcdead=1
が有効な場合に、デッドポインタが誤って参照された際に、ランタイムがその不正を検出しやすくなります。
これらの変更は、Goランタイムのデバッグ能力を向上させ、開発者がメモリ管理や並行処理に関連する複雑な問題をより深く理解し、解決するための強力なツールを提供します。
関連リンク
- Go言語公式ドキュメント: https://go.dev/
- GoのGODEBUG環境変数に関する情報: https://pkg.go.dev/runtime (runtimeパッケージのドキュメント内にGODEBUGに関する記述があります)
- Goのプロファイリングツール (pprof): https://go.dev/blog/pprof
参考にした情報源リンク
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHQ8lNXqYoett0JC12kB406auZNMuVlnbvB8GftzJYZJGVwN5QKi2pIK7BLNSsgmqRfMkmtSptNAFdlrSzL8p8ZLso1Qy3HDFXbmpQNIW6XX-GY2ZXi1Sn3LR8p7NDdmFUvw7JV_-KYNgJGYfWuxH7t
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHkW0xBTCwiGJpz6AiDWyYVq1n0qnNCJab1L61ygDuYNOmkNj4jkZG367IKX3n1Ob2kEA9JFg74F0v16wT3h7TmOFngfs0RNOLUJwh88bZIPWb2BidudEgD1K0My-Pqg2l3uN8=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHf_KkiVXwOe7LeWuvp6n4mMVfNrmsVjhRWLKOkxdieIMsp2YUvF_zBwqgP7Yr5fQ1wtrc3bFMqdZq6n4-Gqh49ocEwed-MlDdZPwrO4KKOsr3udb0UrPhqM7Rn7DOI_5NKXAYD7LpIcwqgAcT6ImWO_UNKE_KhMTPI12H1Mwq-nA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFP-dt8E2CJ5VPk_gN3kzhWdFMM5knbHYOFVhDM7yBheF49wkVgvjcaWv5vndTPU0J_7IxK-JngkplpVrpq-0co4moUR1GYYLdtmIIgIL6kMj48NSz79w-H4m7hh
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHA8vHQcSXqmaHurS0cMUR9PDhzu8i6OSPgaHt85ayCIoQieqBm4pJwI4kg2YG1ZWEcaFjUHq_mu_sn9maORsD1fl7DDFUmW47W2OVB_hHKSgkgHhQ=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEJrc2syf7lAAitx84534yp3IrMH_tIKI13D2M0BFSvrswtMqCNR5YvrgjT-GZT3Dq7_kGEv_ZAGcgf5vgLh9wYqFbKCWKPO7Ei-OVyYC-UjlsbrGAnv8EL3VuPp2O6Ex26m6pAzz-kHSAptJuTLEMR6jCgycjHtD3lkcDIdEO2YsAJrJKe63PyEvG8O1RAWXfU2HQ-W184ZNpWW09O2piHcJlbDDyZ69dvUGFU-Py2dQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEXHH21EZqGHrfTNuoeRkdrIXyUEgyGeUVy29Cggk9HCXQ6apDy1ZSQfUa2N8owfaUcN-8OnGwi5XkFYM-UDqgzdugwcHbKqmKP2TZC8_ejAYfO0VqJpChMHvdZdZqkjKu-no7D-B1sAw==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG9lF1yuN2E0KsiSKpscKIhqjX28emWb74lNOlwEirIHqTNl5ZBuQuXF_zCLSO0M40Jr7m7y8feWvnvm95BQt0G-ZidXwLBFvwxsVhfoVWPUdIx4bqYayupWrHIDQNZVJKw0PjxrnfVmBTuQCN1O1WPJk6t5ddNGeCklHTK7comOwjRz55biNaLqhyGAYSpveucULp8FBdK8HsGwh0jzoqQWbRoBT2xR0q3kjxOdmLiD7uAsaA91AV-SA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHgqjkxjEBnqpomIIao-mREmYrdXW4MLUCdIekbSfvok5emfSPFqEv2NIJpTg2bD5Hi1B3MAgV3ABwPUdS23Z4SqbhZKtPd9FtXyQ73G-YXAX5USXzDsC8sGKohPR6Rn72gcOwjmAZWcg4cVawbN-IHS2ipQazs2UBaWfDltVDkoFXzgc36ZCEsjlw7YLwM