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

[インデックス 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に紐付けられない可能性のあるスレッド)で行われる場合があり、その際にmnil(ヌル)になる、またはm->mcachenilになる状況が発生し、プロファイリング処理が不正な状態に陥る可能性がありました。このコミットは、この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を実行している最中に行われます。

mmcache

Goランタイムの内部では、現在のOSスレッド(M)へのポインタがグローバル変数またはスレッドローカルストレージを通じてアクセス可能です。このポインタは通常mという名前で参照されます。mcacheは、各Mに紐付けられたローカルなメモリキャッシュであり、小さなオブジェクトのアロケーションを高速化するために使用されます。mcacheが存在しない場合、メモリ割り当てが遅くなったり、ランタイムが予期しない状態になったりする可能性があります。

Windowsにおけるプロファイリングの特殊性

Unix系OSでは、SIGPROFのようなシグナルが特定のOSスレッドに送信され、そのスレッドの実行を一時停止させてプロファイリング情報を収集します。しかし、Windowsではシグナル機構が異なるため、GoランタイムはWindows API(例: SetThreadContextSuspendThreadなど)を利用してプロファイリングを実現します。この際、プロファイリングのためのサンプリングを行うスレッドが、必ずしもGoランタイムのMに紐付けられた通常のワーカースレッドであるとは限りません。専用のプロファイリングスレッドが起動され、それがmmcacheを持たない状態でプロファイリングコールバックを呼び出す可能性がありました。

技術的詳細

このコミットが修正しているのは、runtime·sigprofというGoランタイム内部の関数です。この関数は、CPUプロファイリングのサンプリングイベントが発生した際に呼び出されるハンドラです。

元のコードでは、runtime·sigprofの冒頭で、現在のM(m)がnilであるか、またはそのMのmcachenilである場合に、プロファイリング処理をスキップしていました。これは、プロファイリングのコンテキストが不完全であると判断された場合に、クラッシュや不正な動作を防ぐためのガード条件でした。

if(m == nil || m->mcache == nil)
    return;

しかし、Windows環境では、CPUプロファイリングが専用のOSスレッドによって行われることがあります。この専用スレッドは、Goランタイムの通常のスケジューリングメカニズムによって管理されるMとは異なり、mnilである状態でruntime·sigprofが呼び出される可能性がありました。このような場合でも、Windowsのプロファイリングは正当な処理であり、スキップされるべきではありませんでした。

この修正は、このガード条件に!Windowsという条件を追加することで、Windows環境でのプロファイリング時にはこのチェックをバイパスするように変更しました。

if(!Windows && (m == nil || m->mcache == nil))
    return;

これにより、Windowsではmnilまたはm->mcachenilであっても、プロファイリング処理が続行されるようになります。これは、Windowsのプロファイリングがmmcacheの存在に依存しない、あるいは異なる方法でコンテキストを管理しているため、このチェックが不要または不適切であるという認識に基づいています。

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

変更は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が真): 以前と同様に、mnilであるか、m->mcachenilである場合はプロファイリング処理をスキップします。これは、これらのOSではmmcacheが常に有効であるべきという前提に基づいています。
  • Windows OS(!Windowsが偽): (m == nil || m->mcache == nil)の条件が評価されなくなり、常にif文のブロックがスキップされます。これにより、Windowsではmmcacheの状態に関わらず、runtime·sigprof関数が実行を続行し、プロファイリングデータが収集されるようになります。

コメント// Windows does profiling in a dedicated thread w/o m.は、この変更の理由を明確に示しており、Windowsのプロファイリングがmに依存しない専用スレッドで行われることを説明しています。この修正により、Windows環境でのCPUプロファイリングが正しく機能するようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特にsrc/runtimeディレクトリ)
  • Go言語のCPUプロファイリングに関する公式ドキュメントやブログ記事
  • GoランタイムのM-P-Gモデルに関する解説記事
  • Windowsにおけるスレッドとプロファイリングのメカニズムに関する一般的な情報