[インデックス 17044] ファイルの概要
このコミットは、Goランタイムのプロファイリング機能、特にCPUプロファイリングに関する重要な変更を導入しています。主な目的は、OS XにおけるCPUプロファイリングの信頼性の低い挙動に対処するため、既存の回避策を削除することです。これにより、OS X上でのCPUプロファイルレポートは一時的に停止されますが、これはプロファイリングの正確性を確保し、スケジューラの複雑性を軽減するための措置です。また、マルチスレッド環境でのプロファイリングテストが追加されています。
コミット
commit d3066e47b13f3a46ae76a0612abbe25d4d80ddbf
Author: Russ Cox <rsc@golang.org>
Date: Mon Aug 5 19:49:02 2013 -0400
runtime/pprof: test multithreaded profile, remove OS X workarounds
This means that pprof will no longer report profiles on OS X.
That's unfortunate, but the profiles were often wrong and, worse,
it was difficult to tell whether the profile was wrong or not.
The workarounds were making the scheduler more complex,
possibly caused a deadlock (see issue 5519), and did not actually
deliver reliable results.
It may be possible for adventurous users to apply a patch to
their kernels to get working results, or perhaps having no results
will encourage someone to do the work of creating a profiling
thread like on Windows. Issue 6047 has details.
Fixes #5519.
Fixes #6047.
R=golang-dev, bradfitz, r
CC=golang-dev
https://golang.org/cl/12429045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d3066e47b13f3a46ae76a0612abbe25d4d80ddbf
元コミット内容
このコミットの元の内容は、Goランタイムのプロファイリング機能、特にOS XにおけるCPUプロファイリングの挙動に関するものです。OS Xでは、CPUプロファイリングに使用されるSIGPROF
シグナルが、プロファイリング対象のスレッドではなく、スリープ中のスレッドに誤って配送されるという問題がありました。これを回避するために、Goランタイムはスレッドがブロッキングシステムコールに入る際にSIGPROF
の受信を一時的に無効にし、システムコールから戻る際に再度有効にするというワークアラウンドを導入していました。しかし、このワークアラウンドは複雑であり、デッドロックの原因となる可能性があり(Issue 5519)、また信頼性の高いプロファイル結果を提供できていませんでした。
このコミットでは、これらのOS X固有のワークアラウンドを削除し、OS X上でのCPUプロファイリングレポートを一時的に停止することを決定しました。これは、誤ったプロファイル結果を提供し続けるよりも、一時的に機能を提供しない方が良いという判断に基づいています。
変更の背景
この変更の背景には、GoランタイムのCPUプロファイリングにおけるOS X固有の課題がありました。
- OS Xにおける
SIGPROF
の挙動の不正確さ: OS Xでは、CPUプロファイリングに利用されるSIGPROF
シグナルが、期待通りにCPUを消費しているスレッドに配送されず、スリープ中のスレッドに配送されることが頻繁にありました。これはAppleのOSのバグ(Apple Bug Report #9177434)として報告されており、Goランタイムが正確なCPUプロファイルを取得する上で大きな障害となっていました。 - 既存のワークアラウンドの限界と問題: この問題を回避するため、Goランタイムはスレッドがブロッキング状態に入る際に
SIGPROF
の受信をブロックし、ブロッキング状態から抜ける際にアンブロックするというワークアラウンドを導入していました。しかし、この方法は以下の問題を引き起こしていました。- スケジューラの複雑化: シグナルマスクの変更は、Goランタイムのスケジューラに不必要な複雑性をもたらしていました。
- デッドロックの可能性: Issue 5519で報告されたように、このワークアラウンドがデッドロックの原因となる可能性がありました。
- 信頼性の欠如: ワークアラウンドを適用しても、OS X上でのCPUプロファイルは依然として不正確であり、信頼できる結果を提供できていませんでした。特に、64-bit Snow Leopardカーネルでは、このワークアラウンドが機能しないことが確認されていました。
- Issue 6047: このIssueでは、OS Xでのプロファイリングの不正確さについて詳細が議論されており、Windowsのようにプロファイリング専用のスレッドを作成するアプローチが提案されていました。
- 品質と信頼性の重視: Go開発チームは、不正確な情報を提供するよりも、一時的に機能を提供しないことを選択しました。これにより、ユーザーが誤ったプロファイル結果に基づいて最適化を行うリスクを排除し、将来的に信頼性の高いプロファイリング機能を提供するための基盤を整えることを目指しました。
これらの背景から、OS X固有のワークアラウンドを削除し、より堅牢なプロファイリングメカニズムの再構築を目指すという判断が下されました。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
- CPUプロファイリング:
- プログラムがCPU時間をどこで消費しているかを特定するための手法です。
- 一般的には、一定の間隔(例: 100Hz)で実行中のスレッドのスタックトレースをサンプリングし、どの関数がCPUを多く使用しているかを統計的に推測します。
- Go言語では、
runtime/pprof
パッケージがこの機能を提供します。
SIGPROF
シグナル:- Unix系OSにおけるシグナルの一種で、プロファイリングタイマーが期限切れになったときにプロセスに送信されます。
- 通常、このシグナルはCPUを消費しているスレッドに配送されることが期待されます。
ITIMER_PROF
:- プロファイリングタイマーの一種で、プロセスが消費したCPU時間に基づいてシグナルを生成します。
setitimer
システムコールで設定されます。
- シグナルマスク:
- 各スレッドが受信をブロックするシグナルのセットです。
sigprocmask
システムコールを使用して変更できます。シグナルマスクに設定されたシグナルは、そのスレッドには配送されず、保留されるか、他のスレッドに配送されます。
- Goランタイムのスケジューラ:
- Go言語のプログラムは、Goランタイムのスケジューラによって管理されるゴルーチン(goroutine)上で実行されます。
- スケジューラは、OSのスレッド(M: Machine)とゴルーチン(G: Goroutine)を多対多でマッピングし、効率的な並行実行を実現します。
- ブロッキングシステムコール(例: ファイルI/O、ネットワークI/O、ロックの待機)は、OSスレッドをブロックする可能性があります。Goランタイムは、このような場合にOSスレッドを解放し、他のゴルーチンを実行できるようにします。
runtime·setprof
関数:- このコミットで削除される主要な関数の一つです。
- OS Xにおいて、プロファイリングシグナル(
SIGPROF
)の受信をスレッドごとに有効/無効にするためのワークアラウンドとして使用されていました。 - スレッドがブロッキングシステムコールに入る前に
runtime·setprof(false)
を呼び出してSIGPROF
をブロックし、システムコールから戻った後にruntime·setprof(true)
を呼び出してSIGPROF
をアンブロックしていました。
futex
(Fast Userspace muTex):- Linuxカーネルが提供する同期プリミティブで、ユーザー空間でのロックやセマフォの実装に利用されます。
- ユーザー空間で競合がない場合はカーネルへのシステムコールなしで高速に動作し、競合が発生した場合のみカーネルに処理を委ねます。
sema
(Semaphore):- 並行プログラミングにおける同期プリミティブの一つで、リソースへのアクセスを制御するために使用されます。
- Goランタイムでは、内部的な同期メカニズムとしてセマフォが使用されることがあります。
m->profilehz
:- Goランタイムの
m
(Machine、OSスレッドを表す構造体)に存在するフィールドで、プロファイリングのサンプリングレート(Hz)を保持します。これが0より大きい場合、プロファイリングが有効であることを示します。
- Goランタイムの
entersyscall
/exitsyscall
:- Goランタイムの内部関数で、ゴルーチンがシステムコールに入る前と出た後に呼び出されます。
- これらの関数は、スケジューラがゴルーチンの状態を適切に管理し、GC(ガベージコレクション)やトレースバックが正しく機能するようにするために重要です。
これらの概念を理解することで、コミットがなぜ行われたのか、そしてそれがGoランタイムの内部動作にどのような影響を与えるのかを深く把握できます。
技術的詳細
このコミットの技術的詳細は、主にOS XにおけるCPUプロファイリングの不正確さに対処するための、Goランタイム内部の変更に集約されます。
runtime·setprof
関数の削除と関連コードのクリーンアップ:- 最も顕著な変更は、
runtime·setprof
関数の削除です。この関数は、OS XにおいてSIGPROF
シグナルの受信をスレッドごとにブロック/アンブロックするために使用されていました。 src/pkg/runtime/os_darwin.c
からruntime·setprof
の実装が完全に削除されました。これに伴い、sigset_prof
(SIGPROF
シグナルを表すシグナルセット)の定義も削除されています。- 他のOS固有のファイル(
os_freebsd.c
,os_linux.c
,os_netbsd.c
,os_openbsd.c
,os_plan9.c
,os_windows.c
)では、元々runtime·setprof
が空の関数(USED(on);
のみ)として定義されていましたが、これらも削除されました。これにより、クロスプラットフォームでのruntime·setprof
の概念自体がGoランランタイムから取り除かれました。 src/pkg/runtime/runtime.h
からruntime·setprof
のプロトタイプ宣言と、その機能に関するコメント(OS Xのバグとワークアラウンドに関する説明)が削除されました。
- 最も顕著な変更は、
- ロックプリミティブからのプロファイリング制御の削除:
src/pkg/runtime/lock_futex.c
とsrc/pkg/runtime/lock_sema.c
において、runtime·lock
、runtime·notesleep
、runtime·notetsleep
などのロックおよび同期プリミティブの関数内から、m->profilehz > 0
のチェックとそれに続くruntime·setprof(false)
およびruntime·setprof(true)
の呼び出しが削除されました。- これは、スレッドがロックを待機したり、システムコールでスリープしたりする際に、プロファイリングシグナルを一時的に無効にするというOS X固有のワークアラウンドが不要になったためです。この変更により、これらの同期プリミティブのコードパスが簡素化され、デッドロックのリスクが軽減されます。
- システムコール出入り口からのプロファイリング制御の削除:
src/pkg/runtime/proc.c
内のentersyscall
およびexitsyscall
関数から、m->profilehz > 0
のチェックとruntime·setprof
の呼び出しが削除されました。entersyscall
はゴルーチンがシステムコールに入る直前に呼び出され、exitsyscall
はシステムコールから戻った直後に呼び出されます。これらの場所でプロファイリングを制御していたのは、OS XのSIGPROF
配送の不正確さに対処するためでした。この削除により、システムコールとプロファイリングの間の結合が解消されました。
- CPUプロファイラのリセットロジックの簡素化:
src/pkg/runtime/signal_unix.c
内のruntime·resetcpuprofiler
関数から、runtime·setprof(false)
およびruntime·setprof(true)
の呼び出しが削除されました。- この関数はCPUプロファイリングのレートを設定する際に呼び出されますが、もはや
runtime·setprof
によるシグナルマスクの操作は不要となりました。
- OS XにおけるCPUプロファイリングの無効化とテストの調整:
src/pkg/runtime/pprof/pprof.go
のコメントが更新され、OS Xでのプロファイリングが不完全で不正確である旨が明記されました(Issue 6047への参照)。src/pkg/runtime/pprof/pprof_test.go
では、OS Xにおける特定のカーネルバージョン(64-bit Leopard / Snow Leopard)でのテストスキップロジックが削除されました。代わりに、OS Xでのプロファイリング失敗をログに記録し、テストを続行する(ただし、失敗を無視する)ロジックが追加されました。これは、OS Xでのプロファイリングが意図的に無効化されたため、テストが失敗しても問題ないという方針転換を示しています。
- マルチスレッドプロファイリングテストの追加:
src/pkg/runtime/pprof/pprof_test.go
にTestCPUProfileMultithreaded
という新しいテストケースが追加されました。- このテストは、
GOMAXPROCS
を2に設定し、複数のゴルーチンが並行してCPUを消費するシナリオでCPUプロファイリングが正しく機能するかを検証します。これは、OS Xのワークアラウンドを削除したことで、他のプラットフォームでのマルチスレッドプロファイリングの正確性をより確実に保証するためのものです。
これらの変更は、OS Xにおける特定のOSレベルの挙動に依存していた複雑なワークアラウンドを排除し、Goランタイムのプロファイリングコードベースを簡素化することを目的としています。これにより、OS Xでのプロファイリングは一時的に利用できなくなりますが、他のプラットフォームでのプロファイリングの信頼性が向上し、将来的にOS Xでのより堅牢なプロファイリングソリューション(例えば、プロファイリング専用スレッドの導入)を開発するための道が開かれました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主にruntime·setprof
関数の呼び出しの削除と、OS X固有のプロファイリング関連コードの削除です。
-
src/pkg/runtime/lock_futex.c
およびsrc/pkg/runtime/lock_sema.c
:runtime·lock
,runtime·notesleep
,runtime·notetsleep
関数内で、m->profilehz > 0
の条件分岐とそれに続くruntime·setprof(false)
およびruntime·setprof(true)
の呼び出しが削除されました。- 例 (
src/pkg/runtime/lock_futex.c
のruntime·lock
関数から抜粋):--- a/src/pkg/runtime/lock_futex.c +++ b/src/pkg/runtime/lock_futex.c @@ -83,11 +83,7 @@ runtime·lock(Lock *l) if(v == MUTEX_UNLOCKED) return; wait = MUTEX_SLEEPING; - if(m->profilehz > 0) - runtime·setprof(false); runtime·futexsleep((uint32*)&l->key, MUTEX_SLEEPING, -1); - if(m->profilehz > 0) - runtime·setprof(true); } }
-
src/pkg/runtime/os_darwin.c
:runtime·setprof
関数の実装が完全に削除されました。sigset_prof
の定義も削除されました。runtime·minit
関数内のruntime·setprof(m->profilehz > 0);
の呼び出しも削除されました。- 例 (
src/pkg/runtime/os_darwin.c
から抜粋):--- a/src/pkg/runtime/os_darwin.c +++ b/src/pkg/runtime/os_darwin.c @@ -12,7 +12,6 @@ extern SigTab runtime·sigtab[]; static Sigset sigset_none; static Sigset sigset_all = ~(Sigset)0; -static Sigset sigset_prof = 1<<(SIGPROF-1); static void unimplemented(int8 *name) @@ -129,7 +128,6 @@ runtime·minit(void) runtime·signalstack((byte*)m->gsignal->stackguard - StackGuard, 32*1024); runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil); - runtime·setprof(m->profilehz > 0); } // Called from dropm to undo the effect of an minit. @@ -481,37 +479,6 @@ runtime·memlimit(void) return 0; } -// NOTE(rsc): On OS X, when the CPU profiling timer expires, the SIGPROF -// signal is not guaranteed to be sent to the thread that was executing to -// cause it to expire. It can and often does go to a sleeping thread, which is -// not interesting for our profile. This is filed Apple Bug Report #9177434, -// copied to http://code.google.com/p/go/source/detail?r=35b716c94225. -// To work around this bug, we disable receipt of the profiling signal on -// a thread while in blocking system calls. This forces the kernel to deliver -// the profiling signal to an executing thread. -// -// The workaround fails on OS X machines using a 64-bit Snow Leopard kernel. -// In that configuration, the kernel appears to want to deliver SIGPROF to the -// sleeping threads regardless of signal mask and, worse, does not deliver -// the signal until the thread wakes up on its own. -// -// If necessary, we can switch to using ITIMER_REAL for OS X and handle -// the kernel-generated SIGALRM by generating our own SIGALRMs to deliver -// to all the running threads. SIGALRM does not appear to be affected by -// the 64-bit Snow Leopard bug. However, as of this writing Mountain Lion -// is in preview, making Snow Leopard two versions old, so it is unclear how -// much effort we need to spend on one buggy kernel. - -// Control whether profiling signal can be delivered to this thread. -void -runtime·setprof(bool on) -{ - if(on) - runtime·sigprocmask(SIG_UNBLOCK, &sigset_prof, nil); - else - runtime·sigprocmask(SIG_BLOCK, &sigset_prof, nil); -} void runtime·setsig(int32 i, GoSighandler *fn, bool restart)
-
src/pkg/runtime/os_freebsd.c
,os_linux.c
,os_netbsd.c
,os_openbsd.c
,os_plan9.c
,os_windows.c
:- これらのファイルから、空の
runtime·setprof
関数の定義が削除されました。
- これらのファイルから、空の
-
src/pkg/runtime/pprof/pprof.go
:- OS Xに関する
BUG
コメントが更新され、より一般的な表現になりました。
- OS Xに関する
-
src/pkg/runtime/pprof/pprof_test.go
:TestCPUProfile
関数から、OS X Snow Leopard 64-bit カーネルに関するスキップロジックが削除されました。testCPUProfile
関数が導入され、TestCPUProfile
とTestCPUProfileMultithreaded
から呼び出されるようになりました。TestCPUProfileMultithreaded
が追加され、マルチスレッド環境でのプロファイリングテストが強化されました。- OS Xでのプロファイリング失敗時に、エラーではなくログメッセージを出力し、テストを続行するロジックが追加されました。
-
src/pkg/runtime/proc.c
:entersyscall
およびexitsyscall
関数内で、m->profilehz > 0
の条件分岐とruntime·setprof
の呼び出しが削除されました。
-
src/pkg/runtime/runtime.h
:runtime·setprof
の関数宣言と、その機能に関する詳細なコメントが削除されました。
-
src/pkg/runtime/signal_unix.c
:runtime·resetcpuprofiler
関数内で、runtime·setprof
の呼び出しが削除されました。
これらの変更は、OS X固有のプロファイリングワークアラウンドを完全に削除し、Goランタイムのプロファイリングサブシステムを簡素化することを目的としています。
コアとなるコードの解説
このコミットのコアとなるコードの変更は、GoランタイムがOS X上でCPUプロファイリングをどのように扱うかという根本的なアプローチの変更を反映しています。
1. runtime·setprof
の削除と影響
src/pkg/runtime/os_darwin.c
からの削除:- 以前のGoランタイムでは、OS Xの
SIGPROF
シグナル配送の不正確さに対処するため、runtime·setprof
関数を使用して、スレッドがブロッキングシステムコールに入る際にSIGPROF
の受信をブロックし、システムコールから戻る際にアンブロックしていました。 - このコミットでは、この
runtime·setprof
関数自体がos_darwin.c
から完全に削除されました。これは、OS Xにおけるこのワークアラウンドが信頼性が低く、デッドロックの原因となる可能性があったため、もはや維持する価値がないと判断されたことを意味します。 sigset_prof
(SIGPROF
シグナルを表すシグナルセット)の削除も、このワークアラウンドが不要になったことの直接的な結果です。
- 以前のGoランタイムでは、OS Xの
lock_futex.c
,lock_sema.c
,proc.c
,signal_unix.c
からの呼び出し削除:runtime·setprof
が削除されたため、Goランタイム内の様々な場所(ロックプリミティブ、システムコール出入り口、プロファイラのリセット関数など)から、この関数への呼び出しがすべて削除されました。- 例えば、
runtime·lock
やruntime·notesleep
のような関数内で、スレッドが待機状態に入る前にruntime·setprof(false)
を呼び出し、待機状態から抜けた後にruntime·setprof(true)
を呼び出すことで、スリープ中のスレッドにSIGPROF
が配送されるのを防いでいました。これらの呼び出しが削除されたことで、これらのコードパスは簡素化され、OS X固有の複雑なシグナルマスク操作がなくなりました。 entersyscall
とexitsyscall
からの削除も同様に、システムコール中のプロファイリング制御が不要になったことを示しています。
2. pprof_test.go
の変更とマルチスレッドプロファイリングテストの追加
- OS Xでのテスト挙動の変更:
- 以前は、OS Xの特定のカーネルバージョン(64-bit Snow Leopard)でCPUプロファイリングが機能しないことが知られていたため、テストがスキップされていました。
- このコミットでは、そのスキップロジックが削除され、代わりにOS Xでのプロファイリング失敗時にログメッセージを出力し、テストを続行する(ただし、失敗を無視する)ようになりました。これは、OS XでのCPUプロファイリングが意図的に無効化されたため、テストが失敗してもそれは期待される挙動であるという方針転換を反映しています。
TestCPUProfileMultithreaded
の追加:- この新しいテストケースは、
GOMAXPROCS
を2に設定し、複数のゴルーチンが並行してCPUを消費するシナリオでCPUプロファイリングが正しく機能するかを検証します。 - これは、OS Xのワークアラウンドを削除したことで、他のプラットフォームでのマルチスレッドプロファイリングの正確性をより確実に保証するためのものです。プロファイリングの信頼性を高めるための重要なステップと言えます。
- この新しいテストケースは、
全体的な影響
これらの変更は、GoランタイムがOS XにおけるCPUプロファイリングの不正確さという長年の問題に対して、一時的に「機能停止」という決断を下したことを示しています。これは、不正確なプロファイル結果を提供し続けるよりも、一時的に機能を提供しない方が良いという品質重視の姿勢を反映しています。同時に、他のプラットフォームでのプロファイリングの信頼性を向上させ、将来的にOS Xでのより堅牢なプロファイリングソリューション(例えば、Windowsのようにプロファイリング専用のスレッドを導入するアプローチ)を開発するための基盤を整えることを目的としています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/d3066e47b13f3a46ae76a0612abbe25d4d80ddbf
- Go Issue 5519:
runtime: deadlock in profiler
- https://golang.org/issue/5519 - Go Issue 6047:
runtime: CPU profiler on OS X is inaccurate
- https://golang.org/issue/6047 - Go Code Review 12429045:
runtime/pprof: test multithreaded profile, remove OS X workarounds
- https://golang.org/cl/12429045
参考にした情報源リンク
- Go Issue 5519:
runtime: deadlock in profiler
- https://golang.org/issue/5519 - Go Issue 6047:
runtime: CPU profiler on OS X is inaccurate
- https://golang.org/issue/6047 - Go Code Review 12429045:
runtime/pprof: test multithreaded profile, remove OS X workarounds
- https://golang.org/cl/12429045 - Go言語のプロファイリングについて (一般的なGoプロファイリングの概念理解のため)
- Linux futex(2) man page (futexの理解のため)
- SIGPROF - Wikipedia (SIGPROFシグナルの理解のため)
- setitimer(2) - Linux man page (ITIMER_PROFの理解のため)
- sigprocmask(2) - Linux man page (シグナルマスクの理解のため)