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

[インデックス 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.freezetheworldruntime.stoptheworldruntime.starttheworldhandoffpgcstopmfindrunnableschedulesyscallsysmonruntime·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·stoptheworldruntime·starttheworldhandoffpgcstopmfindrunnableschedulesyscallsysmonruntime·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構造体の内部メンバーとなったことを反映しています。

関連リンク

参考にした情報源リンク

  • 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での同期プリエンプション導入に関する公式情報