[インデックス 17267] ファイルの概要
このコミットは、Goランタイムから古いプリエンプションチェック、特にruntime.gcwaiting
に関連するチェックを削除するものです。Go 1.2で導入された新しいプリエンプションメカニズムにより、これらの明示的なチェックが不要になったため、コードの簡素化と効率化が図られています。また、runtime.gcwaiting
変数がグローバル変数からruntime.sched
構造体のメンバーへと変更されています。
コミット
- コミットハッシュ:
f195ae94caf21af5ed4409a3471de2821a6e0c81
- Author: Dmitriy Vyukov dvyukov@google.com
- Date: Thu Aug 15 14:32:10 2013 +0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f195ae94caf21af5ed4409a3471de2821a6e0c81
元コミット内容
runtime: remove old preemption checks
runtime.gcwaiting checks are not needed anymore
R=golang-dev, khr
CC=golang-dev
https://golang.org/cl/12934043
変更の背景
このコミットは、Goランタイムにおけるガベージコレクション(GC)の動作と、ゴルーチン(goroutine)のスケジューリング方法の進化を反映しています。Go 1.2より前は、Goスケジューラは協調的(cooperative)であり、ゴルーチンは自発的に(例えばruntime.Gosched()
を呼び出すことで)またはチャネル操作やシステムコールなどの特定の操作を実行する際にのみ制御を譲渡していました。
ガベージコレクションが「Stop-the-World (STW)」フェーズに入る際、すべてのゴルーチンを一時停止させる必要がありました。このために、runtime.gcwaiting
というフラグが使用され、GCが待機中であることを示していました。しかし、CPUバウンドな長時間実行ループを持つゴルーチンが、自発的な制御の譲渡やスケジューラが介入できるような操作を行わない場合、GCのSTWフェーズが不必要に長引く可能性がありました。
Go 1.2では、この問題を解決するために「同期プリエンプション(synchronous preemption)」が導入されました。これは、コンパイラが関数のプロローグ(関数の開始部分)にプリエンプションチェックを挿入するメカニズムです。これにより、ゴルーチンが関数を呼び出すたびに、gcwaiting
フラグが設定されているかどうかを確認し、設定されていればスケジューラに制御を譲渡するようになりました。この新しいメカニズムにより、GCがゴルーチンを一時停止させるための明示的なruntime.gcwaiting
チェックとそれに続くruntime.gosched()
呼び出しが不要になったため、それらを削除することが変更の背景となります。
また、runtime.gcwaiting
変数がグローバル変数からスケジューラの状態を管理するruntime.sched
構造体のメンバーに移動され、より構造化された管理が行われるようになりました。
前提知識の解説
- Goランタイム: Goプログラムの実行を管理するシステム。スケジューラ、ガベージコレクタ、メモリ管理などが含まれます。
- ガベージコレクション (GC): プログラムが使用しなくなったメモリを自動的に解放するプロセス。GoのGCは並行的に動作しますが、一部のフェーズではすべてのゴルーチンを一時停止させる「Stop-the-World (STW)」が必要になります。
- Stop-the-World (STW): ガベージコレクションの特定のフェーズで、すべてのアプリケーションゴルーチンの実行を一時的に停止させること。STWの時間が長いと、アプリケーションの応答性に悪影響を与えます。
- ゴルーチン (Goroutine): Goにおける軽量な実行スレッド。Goスケジューラによって管理され、OSスレッド上で多重化されて実行されます。
- プリエンプション (Preemption): スケジューラが、実行中のタスク(この場合はゴルーチン)の同意なしに、その実行を中断し、別のタスクにCPUを割り当てること。Go 1.2で導入されたのは「同期プリエンプション」です。
- 同期プリエンプション: Go 1.2で導入されたプリエンプションの一種。コンパイラが関数のプロローグにチェックコードを挿入し、GCがSTWを要求している場合などに、ゴルーチンが自発的にスケジューラに制御を譲渡するようにします。これにより、GCのSTW時間を短縮し、レイテンシを改善します。
runtime.gosched()
: 現在のゴルーチンの実行を一時停止し、他の実行可能なゴルーチンにCPUを譲渡するGoランタイム関数。協調的スケジューリングにおいて、ゴルーチンが自発的に制御を譲渡する際に使用されます。runtime.gcwaiting
: Goランタイム内部で使用されていたフラグで、ガベージコレクタがすべてのゴルーチンが一時停止するのを待っている状態であることを示していました。
技術的詳細
このコミットの主要な変更点は、Go 1.2で導入された同期プリエンプションの恩恵を受けて、以前のruntime.gcwaiting
フラグに基づく明示的なプリエンプションチェック(if(runtime·gcwaiting) runtime·gosched();
)が不要になったことです。
Go 1.2以降、コンパイラはすべての関数のプロローグにプリエンプションチェックを自動的に挿入します。このチェックは、現在のゴルーチンがプリエンプションを要求されているかどうか(例えば、GCがSTWを要求している場合)を効率的に判断し、必要であればスケジューラに制御を戻します。これにより、アプリケーションコードのどこかで関数呼び出しが発生する限り、ゴルーチンはGCのSTWフェーズ中に確実に一時停止できるようになりました。
この新しいメカニズムにより、チャネル操作(chansend
, chanrecv
, selectgo
, closechan
)、マップ操作(mapaccess
, mapassign
, mapiternext
)、メモリ割り当て(mallocgc
)などのランタイム関数内で手動で挿入されていたruntime.gcwaiting
のチェックとruntime.gosched()
の呼び出しが冗長になりました。これらのチェックは、GCが待機している場合にゴルーチンが自発的にスケジューラに制御を譲渡することを目的としていましたが、同期プリエンプションがその役割をより効率的かつ網羅的に果たすようになったため、削除されました。
さらに、runtime.gcwaiting
変数は、以前はグローバル変数として定義されていましたが、このコミットでruntime.sched
構造体のメンバー(runtime.sched.gcwaiting
)として再配置されました。これは、GCの待機状態がスケジューラの全体的な状態の一部として管理されるべきであるという設計思想の変更を反映しています。これにより、ランタイムの状態管理が一元化され、より整合性が高まります。runtime.freezetheworld
、runtime.stoptheworld
、runtime.starttheworld
、handoffp
、gcstopm
、findrunnable
、schedule
、syscall
、sysmon
、runtime·schedtrace
といったGCやスケジューリングに関連する関数内で、この変数の参照が新しいパスに更新されています。
コアとなるコードの変更箇所
このコミットでは、以下のファイルが変更されています。
src/pkg/runtime/chan.c
: チャネル操作に関連する関数からruntime.gcwaiting
チェックとruntime.gosched()
呼び出しを削除。src/pkg/runtime/hashmap.c
: マップ操作に関連する関数からruntime.gcwaiting
チェックとruntime.gosched()
呼び出しを削除。src/pkg/runtime/malloc.goc
: メモリ割り当て関数からruntime.gcwaiting
チェックとruntime.gosched()
呼び出しを削除。src/pkg/runtime/proc.c
:runtime.gcwaiting
変数をruntime.sched
構造体内に移動し、関連する参照を更新。src/pkg/runtime/runtime.h
: グローバルなruntime.gcwaiting
変数の宣言を削除。
合計で5つのファイルが変更され、14行が追加され、36行が削除されています。これは主に冗長なチェックの削除と変数の再配置によるものです。
コアとなるコードの解説
src/pkg/runtime/chan.c
, src/pkg/runtime/hashmap.c
, src/pkg/runtime/malloc.goc
これらのファイルでは、以下のようなパターンで記述されていたruntime.gcwaiting
のチェックとruntime.gosched()
の呼び出しが削除されています。
// 削除されたコードの例
if(runtime·gcwaiting)
runtime·gosched();
これは、チャネルの送受信、選択、クローズ、マップのアクセス、割り当て、イテレーション、そしてメモリ割り当てといった、ランタイムの重要な操作の直前に行われていました。同期プリエンプションの導入により、これらの場所で明示的にゴルーチンを一時停止させる必要がなくなったため、コードが簡素化されました。
src/pkg/runtime/proc.c
このファイルでは、runtime.gcwaiting
変数の定義が変更されています。
変更前:
uint32 runtime·gcwaiting;
変更後:
Sched
構造体内にgcwaiting
メンバーが追加されました。
struct Sched {
// ...
uint32 gcwaiting; // gc is waiting to run
// ...
};
そして、グローバルなruntime.gcwaiting
の宣言は削除されました。
これに伴い、proc.c
内のruntime.gcwaiting
へのすべての参照がruntime.sched.gcwaiting
に更新されています。例えば、runtime·freezetheworld
関数では、GC待機状態を設定する際に以下のように変更されています。
変更前:
runtime·atomicstore((uint32*)&runtime·gcwaiting, 1);
変更後:
runtime·atomicstore((uint32*)&runtime·sched.gcwaiting, 1);
同様に、runtime·stoptheworld
、runtime·starttheworld
、handoffp
、gcstopm
、findrunnable
、schedule
、syscall
、sysmon
、runtime·schedtrace
といった関数内のruntime.gcwaiting
への参照もすべてruntime.sched.gcwaiting
に修正されています。これにより、GCの待機状態がスケジューラのコンテキスト内で管理されるようになり、ランタイムの状態管理がより一貫性を持つようになりました。
src/pkg/runtime/runtime.h
このヘッダーファイルでは、グローバルなruntime.gcwaiting
変数の外部宣言が削除されています。
削除された宣言:
extern uint32 runtime·gcwaiting; // gc is waiting to run
これは、runtime.gcwaiting
がもはやグローバル変数ではなく、runtime.sched
構造体の内部メンバーとなったことを反映しています。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/12934043
参考にした情報源リンク
- unskilled.blog: Go 1.2におけるプリエンプションに関する情報
- stackoverflow.com: Goスケジューラの協調的性質とプリエンプションに関する議論
- google.com (Go issue #543):
m->gcwaiting
など、Goランタイムの内部状態に関する議論 - dzone.com: GoのGCとSTW、プリエンプションに関する解説記事
- google.com (Go 1.2リリースノートなど): Go 1.2での同期プリエンプション導入に関する公式情報