[インデックス 15395] ファイルの概要
このコミットは、Go言語のランタイムにおけるWindows環境でのCPUプロファイリングの不具合を修正するものです。具体的には、プロファイリング処理がWindowsの特定の動作と競合しないように、条件分岐を追加しています。
コミット
commit 1e957b6245d1c204e333738383a6f4aca0b9d86b
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Sat Feb 23 10:07:41 2013 +0400
runtime: fix windows cpu profiling
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/7407044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1e957b6245d1c204e333738383a6f4aca0b9d86b
元コミット内容
runtime: fix windows cpu profiling
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/7407044
変更の背景
Go言語のランタイムは、プログラムの実行を効率的に管理するための重要なコンポーネントです。CPUプロファイリングは、プログラムがCPU時間をどこで消費しているかを特定し、パフォーマンスのボトルネックを検出するために不可欠なツールです。Goランタイムは、pprof
ツールを通じてCPUプロファイリング機能を提供しています。
このコミットが行われた背景には、Windows環境でCPUプロファイリングが正しく機能しないという問題がありました。Goランタイムは、プロファイリングのためにシグナル(Unix系OSのSIGPROF
に相当)を利用して、定期的に実行中のゴルーチン(Goの軽量スレッド)のスタックトレースをサンプリングします。しかし、Windowsではシグナル機構がUnix系OSとは異なり、プロファイリングの仕組みも異なります。
特に、Goランタイムの内部では、プロファイリングを行う際に現在のM(Machine、OSスレッドを表すGoランタイムの構造体)と、そのMに紐付けられたmcache(メモリキャッシュ)が存在することを前提としていました。しかし、Windowsではプロファイリングが専用のOSスレッド(Mに紐付けられない可能性のあるスレッド)で行われる場合があり、その際にm
がnil
(ヌル)になる、またはm->mcache
がnil
になる状況が発生し、プロファイリング処理が不正な状態に陥る可能性がありました。このコミットは、このWindows特有の動作に対応するための修正です。
前提知識の解説
CPUプロファイリング
CPUプロファイリングとは、プログラムが実行中にCPUの時間をどのように消費しているかを分析する手法です。これにより、プログラムのどの部分が最も時間を要しているか(ホットスポット)を特定し、最適化の対象を見つけることができます。Go言語では、net/http/pprof
パッケージやruntime/pprof
パッケージを通じてプロファイリング機能が提供されており、go tool pprof
コマンドでプロファイリング結果を視覚化できます。
GoランタイムのMとGとP
Goランタイムは、ゴルーチン(G)、論理プロセッサ(P)、OSスレッド(M)という3つの主要な抽象化を用いて並行処理を管理します。
- G (Goroutine): Go言語の軽量スレッドです。数千から数百万のゴルーチンを同時に実行できます。
- P (Processor): 論理プロセッサを表します。GをM上で実行するためのコンテキストを提供します。Pの数は通常、CPUのコア数に設定されます。
- M (Machine): OSスレッドを表します。Pに紐付けられ、Gを実行します。MはOSによってスケジュールされます。
通常のGoプログラムの実行では、GはPにディスパッチされ、PはM上で実行されます。CPUプロファイリングのサンプリングは、通常、MがGを実行している最中に行われます。
m
とmcache
Goランタイムの内部では、現在のOSスレッド(M)へのポインタがグローバル変数またはスレッドローカルストレージを通じてアクセス可能です。このポインタは通常m
という名前で参照されます。mcache
は、各Mに紐付けられたローカルなメモリキャッシュであり、小さなオブジェクトのアロケーションを高速化するために使用されます。mcache
が存在しない場合、メモリ割り当てが遅くなったり、ランタイムが予期しない状態になったりする可能性があります。
Windowsにおけるプロファイリングの特殊性
Unix系OSでは、SIGPROF
のようなシグナルが特定のOSスレッドに送信され、そのスレッドの実行を一時停止させてプロファイリング情報を収集します。しかし、Windowsではシグナル機構が異なるため、GoランタイムはWindows API(例: SetThreadContext
やSuspendThread
など)を利用してプロファイリングを実現します。この際、プロファイリングのためのサンプリングを行うスレッドが、必ずしもGoランタイムのMに紐付けられた通常のワーカースレッドであるとは限りません。専用のプロファイリングスレッドが起動され、それがm
やmcache
を持たない状態でプロファイリングコールバックを呼び出す可能性がありました。
技術的詳細
このコミットが修正しているのは、runtime·sigprof
というGoランタイム内部の関数です。この関数は、CPUプロファイリングのサンプリングイベントが発生した際に呼び出されるハンドラです。
元のコードでは、runtime·sigprof
の冒頭で、現在のM(m
)がnil
であるか、またはそのMのmcache
がnil
である場合に、プロファイリング処理をスキップしていました。これは、プロファイリングのコンテキストが不完全であると判断された場合に、クラッシュや不正な動作を防ぐためのガード条件でした。
if(m == nil || m->mcache == nil)
return;
しかし、Windows環境では、CPUプロファイリングが専用のOSスレッドによって行われることがあります。この専用スレッドは、Goランタイムの通常のスケジューリングメカニズムによって管理されるMとは異なり、m
がnil
である状態でruntime·sigprof
が呼び出される可能性がありました。このような場合でも、Windowsのプロファイリングは正当な処理であり、スキップされるべきではありませんでした。
この修正は、このガード条件に!Windows
という条件を追加することで、Windows環境でのプロファイリング時にはこのチェックをバイパスするように変更しました。
if(!Windows && (m == nil || m->mcache == nil))
return;
これにより、Windowsではm
がnil
またはm->mcache
がnil
であっても、プロファイリング処理が続行されるようになります。これは、Windowsのプロファイリングがm
やmcache
の存在に依存しない、あるいは異なる方法でコンテキストを管理しているため、このチェックが不要または不適切であるという認識に基づいています。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/proc.c
ファイル内のruntime·sigprof
関数にあります。
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -1715,7 +1715,8 @@ runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp)
{\n \tint32 n;\n \n-\tif(m == nil || m->mcache == nil)\n+\t// Windows does profiling in a dedicated thread w/o m.\n+\tif(!Windows && (m == nil || m->mcache == nil))\n \t\treturn;\n \tif(prof.fn == nil || prof.hz == 0)\n \t\treturn;\n```
## コアとなるコードの解説
`runtime·sigprof`関数は、プロファイリングシグナル(またはそれに相当するイベント)が受信されたときにGoランタイムによって呼び出される関数です。この関数は、現在のプログラムカウンタ(`pc`)、スタックポインタ(`sp`)、リンクレジスタ(`lr`、ARMアーキテクチャなどで使用)、および現在のゴルーチン(`gp`)の情報を取得し、プロファイリングデータを記録します。
変更前のコードでは、この関数の冒頭で以下のチェックが行われていました。
```c
if(m == nil || m->mcache == nil)
return;
これは、「現在のOSスレッド(M)がGoランタイムによって管理されていないか、またはそのMに紐付けられたメモリキャッシュ(mcache)が初期化されていない場合、プロファイリング処理を中断する」という意図がありました。これは、不正なコンテキストでのプロファイリングを防ぐための安全策です。
しかし、Windows環境では、プロファイリングがGoランタイムの通常のM-P-Gスケジューリングとは独立した、専用のOSスレッドによってトリガーされる場合があります。このような専用スレッドは、GoランタイムのM構造体を持たない(m == nil
)か、または通常のmcache
を持たない可能性があります。にもかかわらず、これらのスレッドからのプロファイリングイベントは有効であり、処理されるべきでした。
そこで、このコミットでは条件に!Windows
を追加しました。
// Windows does profiling in a dedicated thread w/o m.
if(!Windows && (m == nil || m->mcache == nil))
return;
この変更により、以下のようになります。
- Windows以外のOS(
!Windows
が真): 以前と同様に、m
がnil
であるか、m->mcache
がnil
である場合はプロファイリング処理をスキップします。これは、これらのOSではm
とmcache
が常に有効であるべきという前提に基づいています。 - Windows OS(
!Windows
が偽):(m == nil || m->mcache == nil)
の条件が評価されなくなり、常にif
文のブロックがスキップされます。これにより、Windowsではm
やmcache
の状態に関わらず、runtime·sigprof
関数が実行を続行し、プロファイリングデータが収集されるようになります。
コメント// Windows does profiling in a dedicated thread w/o m.
は、この変更の理由を明確に示しており、Windowsのプロファイリングがm
に依存しない専用スレッドで行われることを説明しています。この修正により、Windows環境でのCPUプロファイリングが正しく機能するようになりました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- Go言語のプロファイリングに関するドキュメント: https://go.dev/doc/diagnose
- Go CL (Code Review) 7407044: https://golang.org/cl/7407044
参考にした情報源リンク
- Go言語のソースコード (特に
src/runtime
ディレクトリ) - Go言語のCPUプロファイリングに関する公式ドキュメントやブログ記事
- GoランタイムのM-P-Gモデルに関する解説記事
- Windowsにおけるスレッドとプロファイリングのメカニズムに関する一般的な情報