[インデックス 17801] ファイルの概要
このコミットは、以前の変更 (CL 14231047 / 2f4c2dde2756) を元に戻すものです。元に戻された変更は、g0
スタック上でもプロファイルを収集しようとするものでしたが、プロファイル収集を著しく、かつ予測不能な形で遅くするという問題があったため、このコミットで元に戻されました。
コミット
commit 9aee98def8e6c6ce6af36ffab1348c1f9356e316
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Tue Oct 15 14:37:43 2013 -0400
undo CL 14231047 / 2f4c2dde2756
undone because the change slows down profile collection
significantly and unpredictable at times (see comments
at https://golang.org/cl/14231047 for details)
««« original CL description
runtime: collect profiles even while on g0 stack
Fixes #6417
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/14231047
»»»
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/14535046
---
src/pkg/runtime/os_windows.c | 2 +-\
1 file changed, 1 insertion(+), 1 deletion(-)\
diff --git a/src/pkg/runtime/os_windows.c b/src/pkg/runtime/os_windows.c
index 44c9b342de..c3e296aa67 100644
--- a/src/pkg/runtime/os_windows.c
+++ b/src/pkg/runtime/os_windows.c
@@ -402,7 +402,7 @@ profilem(M *mp)
tls = runtime·tls0;
gp = *(G**)tls;
- if(gp != nil) {
+ if(gp != nil && gp != mp->g0 && gp->status != Gsyscall) {
// align Context to 16 bytes
r = (Context*)((uintptr)(&rbuf[15]) & ~15);
r->ContextFlags = CONTEXT_CONTROL;
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9aee98def8e6c6ce6af36ffab1348c1f9356e316
元コミット内容
このコミットによって元に戻された元の変更 (CL 14231047) は、Go ランタイムが g0
スタック上にある間でもプロファイルを収集するようにするものでした。これは、Go の issue #6417 を修正することを目的としていました。
変更の背景
元の変更 (CL 14231047) は、g0
スタック上でのプロファイル収集を可能にすることで、特定のシナリオでのプロファイル情報の欠落を解消しようとしました。しかし、この変更はプロファイル収集のパフォーマンスに深刻な悪影響を及ぼしました。具体的には、プロファイル収集が著しく遅くなり、その遅延が予測不能な形で発生するという問題が報告されました。このパフォーマンスの低下は許容できないと判断されたため、元の変更を元に戻すことが決定されました。
前提知識の解説
このコミットを理解するためには、Go ランタイムのいくつかの基本的な概念を理解しておく必要があります。
- Goroutine (G): Go ランタイムによって管理される軽量なスレッドのようなものです。Go プログラムの並行処理の単位となります。
- Machine (M): オペレーティングシステム (OS) のスレッドを表します。Go ランタイムは、M を使用して G を実行します。
- Scheduler: G と M の間のマッピングを管理し、G を M 上で実行するようにスケジュールします。
- g0 stack: 各 M には、特別な Goroutine である
g0
が関連付けられています。g0
は、スケジューラやガベージコレクタなどのランタイム内部のコードを実行するためのスタック (g0 stack
) を持っています。通常のユーザー Goroutine はg0
スタックでは実行されません。システムコールやコンテキストスイッチなど、ランタイムの低レベルな処理が行われる際にg0
スタックが使用されます。 - Profile Collection: Go ランタイムは、CPU プロファイル、メモリプロファイル、ブロックプロファイルなど、様々な種類のプロファイル情報を収集する機能を提供します。これらのプロファイルは、プログラムのパフォーマンスボトルネックを特定するのに役立ちます。プロファイル収集は、定期的に実行中の Goroutine のスタックトレースをサンプリングすることで行われます。
- TLS (Thread Local Storage): スレッドごとに独立したデータを保存するためのメカニズムです。Windows の
os_windows.c
のコンテキストでは、Go ランタイムが現在の Goroutine (G) へのポインタをスレッドローカルストレージに保存していることを示唆しています。 - Context (Windows API): Windows API における
CONTEXT
構造体は、スレッドのレジスタの状態(CPU レジスタの値)を保持するために使用されます。プロファイル収集では、実行中のスレッドのコンテキスト情報を取得してスタックトレースを生成することがあります。CONTEXT_CONTROL
フラグは、制御レジスタ(命令ポインタ、スタックポインタなど)の情報のみを要求することを示します。 - Gsyscall: Goroutine のステータスの一つで、Goroutine がシステムコールを実行中であることを示します。システムコール中は、Goroutine はカーネルモードで実行されており、ユーザーコードの実行は一時停止しています。
技術的詳細
元の変更は、profilem
関数内でプロファイル収集の対象となる Goroutine を選択するロジックを変更しました。profilem
関数は、Go ランタイムがプロファイル情報を収集する際に呼び出される関数の一つで、特定の M (OS スレッド) 上で実行されている Goroutine のプロファイル情報を取得しようとします。
元のコードでは、gp != nil
という条件のみで、現在の Goroutine gp
が存在すればプロファイル収集を試みていました。しかし、g0
スタック上で実行されている場合、gp
は g0
を指すことがあります。g0
はランタイム内部の処理を行うための特別な Goroutine であり、通常のユーザー Goroutine とは異なる性質を持っています。
元の変更 (CL 14231047) は、この g0
スタック上でのプロファイル収集を可能にすることで、特定のランタイム内部処理中に発生するプロファイル情報の欠落を補完しようとしました。しかし、g0
スタック上でのプロファイル収集は、ランタイムの低レベルな処理に干渉し、プロファイル収集のオーバーヘッドを著しく増加させました。特に、g0
は頻繁にコンテキストスイッチやシステムコールを行うため、そのスタックをサンプリングすることは、予測不能なパフォーマンスの低下を引き起こす可能性がありました。
このコミットは、このパフォーマンス問題を解決するために、元の変更を元に戻しました。具体的には、プロファイル収集の対象から g0
およびシステムコール中の Goroutine を除外する条件を追加しました。これにより、プロファイル収集はユーザー Goroutine のみに限定され、ランタイムの安定性とパフォーマンスが維持されます。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/os_windows.c
+++ b/src/pkg/runtime/os_windows.c
@@ -402,7 +402,7 @@ profilem(M *mp)
tls = runtime·tls0;
gp = *(G**)tls;
- if(gp != nil) {
+ if(gp != nil && gp != mp->g0 && gp->status != Gsyscall) {
// align Context to 16 bytes
r = (Context*)((uintptr)(&rbuf[15]) & ~15);
r->ContextFlags = CONTEXT_CONTROL;
コアとなるコードの解説
変更は src/pkg/runtime/os_windows.c
ファイルの profilem
関数内で行われています。
元のコード:
if(gp != nil) {
この条件は、現在のスレッドローカルストレージから取得した Goroutine ポインタ gp
が nil
でない場合に、プロファイル収集の処理に進むことを意味していました。これにより、g0
スタック上で実行されている場合でも gp
が g0
を指すため、プロファイル収集が試みられていました。
変更後のコード:
if(gp != nil && gp != mp->g0 && gp->status != Gsyscall) {
この変更により、プロファイル収集の条件がより厳しくなりました。
gp != nil
: これは以前と同じで、有効な Goroutine ポインタが存在することを確認します。gp != mp->g0
: この新しい条件は、現在の Goroutinegp
が、現在の M (OS スレッド) に関連付けられたg0
Goroutine でないことを確認します。これにより、g0
スタック上でのプロファイル収集が明示的に除外されます。gp->status != Gsyscall
: この条件は、現在の Goroutinegp
がシステムコールを実行中 (Gsyscall
ステータス) でないことを確認します。システムコール中は、Goroutine はカーネルモードで実行されており、そのスタックをサンプリングすることは複雑であり、パフォーマンスに悪影響を与える可能性があります。
これらの追加条件により、プロファイル収集は、通常のユーザー Goroutine が実行中で、かつシステムコール中でない場合にのみ行われるようになり、g0
スタック上でのプロファイル収集によるパフォーマンス低下が回避されます。
関連リンク
- 元の変更 (CL 14231047) のリンクがコミットメッセージに記載されていますが、Web検索では詳細な情報を見つけることができませんでした。
- 関連する Go issue #6417 についても、公開されている情報を見つけることができませんでした。
参考にした情報源リンク
- この解説の主要な情報源は、提供されたコミットメッセージ自体です。
- Go ランタイムの
G
,M
,g0
スタック、プロファイル収集、TLS、Windows API のCONTEXT
構造体に関する一般的な知識。