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

[インデックス 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固有の課題がありました。

  1. OS XにおけるSIGPROFの挙動の不正確さ: OS Xでは、CPUプロファイリングに利用されるSIGPROFシグナルが、期待通りにCPUを消費しているスレッドに配送されず、スリープ中のスレッドに配送されることが頻繁にありました。これはAppleのOSのバグ(Apple Bug Report #9177434)として報告されており、Goランタイムが正確なCPUプロファイルを取得する上で大きな障害となっていました。
  2. 既存のワークアラウンドの限界と問題: この問題を回避するため、Goランタイムはスレッドがブロッキング状態に入る際にSIGPROFの受信をブロックし、ブロッキング状態から抜ける際にアンブロックするというワークアラウンドを導入していました。しかし、この方法は以下の問題を引き起こしていました。
    • スケジューラの複雑化: シグナルマスクの変更は、Goランタイムのスケジューラに不必要な複雑性をもたらしていました。
    • デッドロックの可能性: Issue 5519で報告されたように、このワークアラウンドがデッドロックの原因となる可能性がありました。
    • 信頼性の欠如: ワークアラウンドを適用しても、OS X上でのCPUプロファイルは依然として不正確であり、信頼できる結果を提供できていませんでした。特に、64-bit Snow Leopardカーネルでは、このワークアラウンドが機能しないことが確認されていました。
  3. Issue 6047: このIssueでは、OS Xでのプロファイリングの不正確さについて詳細が議論されており、Windowsのようにプロファイリング専用のスレッドを作成するアプローチが提案されていました。
  4. 品質と信頼性の重視: Go開発チームは、不正確な情報を提供するよりも、一時的に機能を提供しないことを選択しました。これにより、ユーザーが誤ったプロファイル結果に基づいて最適化を行うリスクを排除し、将来的に信頼性の高いプロファイリング機能を提供するための基盤を整えることを目指しました。

これらの背景から、OS X固有のワークアラウンドを削除し、より堅牢なプロファイリングメカニズムの再構築を目指すという判断が下されました。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  1. CPUプロファイリング:
    • プログラムがCPU時間をどこで消費しているかを特定するための手法です。
    • 一般的には、一定の間隔(例: 100Hz)で実行中のスレッドのスタックトレースをサンプリングし、どの関数がCPUを多く使用しているかを統計的に推測します。
    • Go言語では、runtime/pprofパッケージがこの機能を提供します。
  2. SIGPROFシグナル:
    • Unix系OSにおけるシグナルの一種で、プロファイリングタイマーが期限切れになったときにプロセスに送信されます。
    • 通常、このシグナルはCPUを消費しているスレッドに配送されることが期待されます。
  3. ITIMER_PROF:
    • プロファイリングタイマーの一種で、プロセスが消費したCPU時間に基づいてシグナルを生成します。
    • setitimerシステムコールで設定されます。
  4. シグナルマスク:
    • 各スレッドが受信をブロックするシグナルのセットです。
    • sigprocmaskシステムコールを使用して変更できます。シグナルマスクに設定されたシグナルは、そのスレッドには配送されず、保留されるか、他のスレッドに配送されます。
  5. Goランタイムのスケジューラ:
    • Go言語のプログラムは、Goランタイムのスケジューラによって管理されるゴルーチン(goroutine)上で実行されます。
    • スケジューラは、OSのスレッド(M: Machine)とゴルーチン(G: Goroutine)を多対多でマッピングし、効率的な並行実行を実現します。
    • ブロッキングシステムコール(例: ファイルI/O、ネットワークI/O、ロックの待機)は、OSスレッドをブロックする可能性があります。Goランタイムは、このような場合にOSスレッドを解放し、他のゴルーチンを実行できるようにします。
  6. runtime·setprof関数:
    • このコミットで削除される主要な関数の一つです。
    • OS Xにおいて、プロファイリングシグナル(SIGPROF)の受信をスレッドごとに有効/無効にするためのワークアラウンドとして使用されていました。
    • スレッドがブロッキングシステムコールに入る前にruntime·setprof(false)を呼び出してSIGPROFをブロックし、システムコールから戻った後にruntime·setprof(true)を呼び出してSIGPROFをアンブロックしていました。
  7. futex (Fast Userspace muTex):
    • Linuxカーネルが提供する同期プリミティブで、ユーザー空間でのロックやセマフォの実装に利用されます。
    • ユーザー空間で競合がない場合はカーネルへのシステムコールなしで高速に動作し、競合が発生した場合のみカーネルに処理を委ねます。
  8. sema (Semaphore):
    • 並行プログラミングにおける同期プリミティブの一つで、リソースへのアクセスを制御するために使用されます。
    • Goランタイムでは、内部的な同期メカニズムとしてセマフォが使用されることがあります。
  9. m->profilehz:
    • Goランタイムのm(Machine、OSスレッドを表す構造体)に存在するフィールドで、プロファイリングのサンプリングレート(Hz)を保持します。これが0より大きい場合、プロファイリングが有効であることを示します。
  10. entersyscall / exitsyscall:
    • Goランタイムの内部関数で、ゴルーチンがシステムコールに入る前と出た後に呼び出されます。
    • これらの関数は、スケジューラがゴルーチンの状態を適切に管理し、GC(ガベージコレクション)やトレースバックが正しく機能するようにするために重要です。

これらの概念を理解することで、コミットがなぜ行われたのか、そしてそれがGoランタイムの内部動作にどのような影響を与えるのかを深く把握できます。

技術的詳細

このコミットの技術的詳細は、主にOS XにおけるCPUプロファイリングの不正確さに対処するための、Goランタイム内部の変更に集約されます。

  1. runtime·setprof関数の削除と関連コードのクリーンアップ:
    • 最も顕著な変更は、runtime·setprof関数の削除です。この関数は、OS XにおいてSIGPROFシグナルの受信をスレッドごとにブロック/アンブロックするために使用されていました。
    • src/pkg/runtime/os_darwin.cからruntime·setprofの実装が完全に削除されました。これに伴い、sigset_profSIGPROFシグナルを表すシグナルセット)の定義も削除されています。
    • 他の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のバグとワークアラウンドに関する説明)が削除されました。
  2. ロックプリミティブからのプロファイリング制御の削除:
    • src/pkg/runtime/lock_futex.csrc/pkg/runtime/lock_sema.cにおいて、runtime·lockruntime·notesleepruntime·notetsleepなどのロックおよび同期プリミティブの関数内から、m->profilehz > 0のチェックとそれに続くruntime·setprof(false)およびruntime·setprof(true)の呼び出しが削除されました。
    • これは、スレッドがロックを待機したり、システムコールでスリープしたりする際に、プロファイリングシグナルを一時的に無効にするというOS X固有のワークアラウンドが不要になったためです。この変更により、これらの同期プリミティブのコードパスが簡素化され、デッドロックのリスクが軽減されます。
  3. システムコール出入り口からのプロファイリング制御の削除:
    • src/pkg/runtime/proc.c内のentersyscallおよびexitsyscall関数から、m->profilehz > 0のチェックとruntime·setprofの呼び出しが削除されました。
    • entersyscallはゴルーチンがシステムコールに入る直前に呼び出され、exitsyscallはシステムコールから戻った直後に呼び出されます。これらの場所でプロファイリングを制御していたのは、OS XのSIGPROF配送の不正確さに対処するためでした。この削除により、システムコールとプロファイリングの間の結合が解消されました。
  4. CPUプロファイラのリセットロジックの簡素化:
    • src/pkg/runtime/signal_unix.c内のruntime·resetcpuprofiler関数から、runtime·setprof(false)およびruntime·setprof(true)の呼び出しが削除されました。
    • この関数はCPUプロファイリングのレートを設定する際に呼び出されますが、もはやruntime·setprofによるシグナルマスクの操作は不要となりました。
  5. 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でのプロファイリングが意図的に無効化されたため、テストが失敗しても問題ないという方針転換を示しています。
  6. マルチスレッドプロファイリングテストの追加:
    • src/pkg/runtime/pprof/pprof_test.goTestCPUProfileMultithreadedという新しいテストケースが追加されました。
    • このテストは、GOMAXPROCSを2に設定し、複数のゴルーチンが並行してCPUを消費するシナリオでCPUプロファイリングが正しく機能するかを検証します。これは、OS Xのワークアラウンドを削除したことで、他のプラットフォームでのマルチスレッドプロファイリングの正確性をより確実に保証するためのものです。

これらの変更は、OS Xにおける特定のOSレベルの挙動に依存していた複雑なワークアラウンドを排除し、Goランタイムのプロファイリングコードベースを簡素化することを目的としています。これにより、OS Xでのプロファイリングは一時的に利用できなくなりますが、他のプラットフォームでのプロファイリングの信頼性が向上し、将来的にOS Xでのより堅牢なプロファイリングソリューション(例えば、プロファイリング専用スレッドの導入)を開発するための道が開かれました。

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

このコミットにおけるコアとなるコードの変更箇所は、主にruntime·setprof関数の呼び出しの削除と、OS X固有のプロファイリング関連コードの削除です。

  1. 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.cruntime·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);
       	}
       }
      
  2. 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)
      
  3. src/pkg/runtime/os_freebsd.c, os_linux.c, os_netbsd.c, os_openbsd.c, os_plan9.c, os_windows.c:

    • これらのファイルから、空の runtime·setprof 関数の定義が削除されました。
  4. src/pkg/runtime/pprof/pprof.go:

    • OS Xに関するBUGコメントが更新され、より一般的な表現になりました。
  5. src/pkg/runtime/pprof/pprof_test.go:

    • TestCPUProfile 関数から、OS X Snow Leopard 64-bit カーネルに関するスキップロジックが削除されました。
    • testCPUProfile 関数が導入され、TestCPUProfileTestCPUProfileMultithreaded から呼び出されるようになりました。
    • TestCPUProfileMultithreaded が追加され、マルチスレッド環境でのプロファイリングテストが強化されました。
    • OS Xでのプロファイリング失敗時に、エラーではなくログメッセージを出力し、テストを続行するロジックが追加されました。
  6. src/pkg/runtime/proc.c:

    • entersyscall および exitsyscall 関数内で、m->profilehz > 0 の条件分岐と runtime·setprof の呼び出しが削除されました。
  7. src/pkg/runtime/runtime.h:

    • runtime·setprof の関数宣言と、その機能に関する詳細なコメントが削除されました。
  8. 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_profSIGPROFシグナルを表すシグナルセット)の削除も、このワークアラウンドが不要になったことの直接的な結果です。
  • lock_futex.c, lock_sema.c, proc.c, signal_unix.c からの呼び出し削除:
    • runtime·setprofが削除されたため、Goランタイム内の様々な場所(ロックプリミティブ、システムコール出入り口、プロファイラのリセット関数など)から、この関数への呼び出しがすべて削除されました。
    • 例えば、runtime·lockruntime·notesleepのような関数内で、スレッドが待機状態に入る前にruntime·setprof(false)を呼び出し、待機状態から抜けた後にruntime·setprof(true)を呼び出すことで、スリープ中のスレッドにSIGPROFが配送されるのを防いでいました。これらの呼び出しが削除されたことで、これらのコードパスは簡素化され、OS X固有の複雑なシグナルマスク操作がなくなりました。
    • entersyscallexitsyscallからの削除も同様に、システムコール中のプロファイリング制御が不要になったことを示しています。

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のようにプロファイリング専用のスレッドを導入するアプローチ)を開発するための基盤を整えることを目的としています。

関連リンク

参考にした情報源リンク