[インデックス 17183] ファイルの概要
このコミットは、Goランタイムにおけるタイマー処理の最適化に関するものです。具体的には、タイマーゴルーチンがタイムアウトによってウェイクアップされた際に、他のゴルーチンが不必要にnotewakeup
を呼び出すのを防ぐための変更です。これにより、過剰なシステムコールやコンテキストスイッチが削減され、パフォーマンスが向上します。
コミット
commit d1b66439f91377bceee1dd190a0be6c8367f7268
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Tue Aug 13 14:14:24 2013 +0400
runtime: eliminate excessive notewakeup calls in timers
If the timer goroutine is wakeup by timeout,
other goroutines will still notewakeup because sleeping is still set.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/12763043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d1b66439f91377bceee1dd190a0be6c8367f7268
元コミット内容
このコミットの元の内容は、Goランタイムのタイマー処理において、過剰なnotewakeup
呼び出しを排除することです。タイマーゴルーチンがタイムアウトによってウェイクアップされた場合でも、sleeping
フラグがまだ設定されているため、他のゴルーチンが引き続きnotewakeup
を呼び出してしまう問題に対処しています。
変更の背景
Goランタイムには、time.After
やtime.Timer
などの機能を実現するための内部タイマーメカニズムが存在します。これらのタイマーは、特定の時間が経過した後にゴルーチンをウェイクアップするために使用されます。タイマー処理は、通常、専用のタイマーゴルーチンによって管理されます。
問題は、タイマーゴルーチンがタイムアウトによってウェイクアップされた場合でも、ランタイムの内部状態を示すtimers.sleeping
フラグがまだtrue
のままであることでした。このsleeping
フラグは、タイマーゴルーチンが現在スリープしているかどうかを示します。他のゴルーチンがタイマーを設定しようとすると、このsleeping
フラグを見て、タイマーゴルーチンがスリープしていると判断し、notewakeup
を呼び出してタイマーゴルーチンをウェイクアップしようとします。
しかし、タイマーゴルーチンがすでにタイムアウトによってウェイクアップされている場合、このnotewakeup
呼び出しは冗長であり、無駄なシステムコールやコンテキストスイッチを引き起こします。これは、特に多数のタイマーが頻繁に設定・期限切れになるような高負荷なシステムにおいて、パフォーマンスのオーバーヘッドとなる可能性がありました。
このコミットは、この冗長なnotewakeup
呼び出しを排除し、ランタイムの効率を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念について理解しておく必要があります。
- ゴルーチン (Goroutine): Goにおける軽量な実行スレッドです。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。
- スケジューラ (Scheduler): Goランタイムの重要なコンポーネントで、ゴルーチンをOSスレッドにマッピングし、実行を管理します。ゴルーチンの生成、実行、停止、ウェイクアップなどを担当します。
- タイマー (Timer): Goの標準ライブラリ
time
パッケージで提供される機能で、指定された時間が経過した後にイベントを発生させたり、ゴルーチンをウェイクアップしたりするために使用されます。内部的には、ランタイムのタイマーメカニズムによって管理されます。 notewakeup
: Goランタイム内部で使用されるプリミティブな同期メカニズムの一つです。これは、特定のイベントが発生した際に、スリープしているゴルーチンをウェイクアップするために使用されます。runtime.notesleep
と対になっており、notesleep
でスリープしたゴルーチンをnotewakeup
で起こします。これは、OSのセマフォや条件変数に似た低レベルのメカニズムです。runtime.lock
: Goランタイム内部で使用されるミューテックス(排他ロック)です。共有データ構造へのアクセスを保護し、複数のゴルーチンからの同時アクセスによるデータ競合を防ぎます。timers
構造体: Goランタイム内部でタイマーの状態を管理するためのデータ構造です。この構造体には、タイマーキューや、タイマーゴルーチンの状態を示すフラグなどが含まれます。特に、timers.sleeping
はタイマーゴルーチンが現在スリープしているかどうかを示すブーリアンフラグです。timerproc
関数: Goランタイム内部のタイマーゴルーチンが実行する関数です。この関数は、タイマーキューを監視し、期限切れになったタイマーを処理し、必要に応じてゴルーチンをウェイクアップします。
技術的詳細
このコミットの核心は、timerproc
関数内でtimers.sleeping
フラグを適切にリセットすることにあります。
従来の動作では、timerproc
がタイムアウトによってウェイクアップされた後も、timers.sleeping
フラグはtrue
のままでした。これは、タイマーゴルーチンがスリープ状態から復帰したにもかかわらず、ランタイムがまだスリープしていると認識していることを意味します。
この状態では、他のゴルーチンが新しいタイマーを設定しようとした際に、timers.sleeping
がtrue
であるため、タイマーゴルーチンがスリープしていると判断し、notewakeup
を呼び出してウェイクアップを試みます。しかし、実際にはtimerproc
はすでにウェイクアップされており、このnotewakeup
呼び出しは不要です。
このコミットでは、timerproc
関数のループの先頭で、runtime·lock(&timers)
によってtimers
構造体をロックした後、すぐにtimers.sleeping = false;
という行を追加しています。
この変更により、timerproc
がウェイクアップされ、タイマー処理を開始する前に、timers.sleeping
フラグがfalse
に設定されます。これにより、他のゴルーチンが新しいタイマーを設定する際に、timers.sleeping
がfalse
であることを確認し、タイマーゴルーチンがすでにアクティブであると判断するため、冗長なnotewakeup
呼び出しを回避できます。
この最適化は、特にタイマーが頻繁に発生するようなシナリオにおいて、システムコールの回数を減らし、CPU使用率を低下させることで、Goアプリケーションの全体的なパフォーマンスを向上させます。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/time.goc
ファイルの一箇所のみです。
--- a/src/pkg/runtime/time.goc
+++ b/src/pkg/runtime/time.goc
@@ -175,6 +175,7 @@ timerproc(void)
for(;;) {
runtime·lock(&timers);
+ timers.sleeping = false;
now = runtime·nanotime();
for(;;) {
if(timers.len == 0) {
コアとなるコードの解説
変更が加えられたのは、timerproc
関数の内部ループの開始部分です。
timerproc(void)
{
// ... (省略) ...
for(;;) { // タイマーゴルーチンのメインループ
runtime·lock(&timers); // timers構造体をロック
timers.sleeping = false; // ここが追加された行
now = runtime·nanotime();
for(;;) {
if(timers.len == 0) {
// ... (省略) ...
}
// ... (省略) ...
}
// ... (省略) ...
}
}
追加された行 timers.sleeping = false;
は、timerproc
がタイマー処理のループに入る直前、かつtimers
構造体への排他ロックを取得した直後に実行されます。
この配置が重要です。
runtime·lock(&timers)
の後:timers
構造体は共有リソースであるため、競合状態を防ぐためにロックが必要です。ロックを取得した後にsleeping
フラグを変更することで、他のゴルーチンがこのフラグを読み取る際に一貫性のある状態を保証します。- ループの先頭:
timerproc
がウェイクアップされて処理を開始するたびに、このフラグがリセットされることを保証します。これにより、タイマーゴルーチンがアクティブである間は、sleeping
フラグが常にfalse
に保たれ、他のゴルーチンによる冗長なnotewakeup
呼び出しを防ぎます。
このシンプルな変更により、Goランタイムのタイマー処理における不要なオーバーヘッドが削減され、全体的な効率が向上します。
関連リンク
- Goのタイマーとスケジューラに関する公式ドキュメントやブログ記事
- Goのランタイムソースコード(特に
src/runtime/time.go
やsrc/runtime/proc.go
など)
参考にした情報源リンク
- Goのコミット d1b66439f91377bceee1dd190a0be6c8367f7268
- Go CL 12763043 (Goの変更リスト)
- Goのランタイムに関する一般的な知識(Goの公式ドキュメント、Goのソースコード、Goのスケジューラに関する技術記事など)
notewakeup
やnotesleep
に関するGoランタイムの内部実装解説記事(例: Goの同期プリミティブに関するブログ記事など)- Goのタイマー実装に関する技術ブログや論文# [インデックス 17183] ファイルの概要
このコミットは、Goランタイムにおけるタイマー処理の最適化に関するものです。具体的には、タイマーゴルーチンがタイムアウトによってウェイクアップされた際に、他のゴルーチンが不必要にnotewakeup
を呼び出すのを防ぐための変更です。これにより、過剰なシステムコールやコンテキストスイッチが削減され、パフォーマンスが向上します。
コミット
commit d1b66439f91377bceee1dd190a0be6c8367f7268
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Tue Aug 13 14:14:24 2013 +0400
runtime: eliminate excessive notewakeup calls in timers
If the timer goroutine is wakeup by timeout,
other goroutines will still notewakeup because sleeping is still set.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/12763043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d1b66439f91377bceee1dd190a0be6c8367f7268
元コミット内容
このコミットの元の内容は、Goランタイムのタイマー処理において、過剰なnotewakeup
呼び出しを排除することです。タイマーゴルーチンがタイムアウトによってウェイクアップされた場合でも、sleeping
フラグがまだ設定されているため、他のゴルーチンが引き続きnotewakeup
を呼び出してしまう問題に対処しています。
変更の背景
Goランタイムには、time.After
やtime.Timer
などの機能を実現するための内部タイマーメカニズムが存在します。これらのタイマーは、特定の時間が経過した後にゴルーチンをウェイクアップするために使用されます。タイマー処理は、通常、専用のタイマーゴルーチンによって管理されます。
問題は、タイマーゴルーチンがタイムアウトによってウェイクアップされた後も、ランタイムの内部状態を示すtimers.sleeping
フラグがまだtrue
のままであることでした。このsleeping
フラグは、タイマーゴルーチンが現在スリープしているかどうかを示します。他のゴルーチンがタイマーを設定しようとすると、このsleeping
フラグを見て、タイマーゴルーチンがスリープしていると判断し、notewakeup
を呼び出してタイマーゴルーチンをウェイクアップしようとします。
しかし、タイマーゴルーチンがすでにタイムアウトによってウェイクアップされている場合、このnotewakeup
呼び出しは冗長であり、無駄なシステムコールやコンテキストスイッチを引き起こします。これは、特に多数のタイマーが頻繁に設定・期限切れになるような高負荷なシステムにおいて、パフォーマンスのオーバーヘッドとなる可能性がありました。
このコミットは、この冗長なnotewakeup
呼び出しを排除し、ランタイムの効率を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念について理解しておく必要があります。
- ゴルーチン (Goroutine): Goにおける軽量な実行スレッドです。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。
- スケジューラ (Scheduler): Goランタイムの重要なコンポーネントで、ゴルーチンをOSスレッドにマッピングし、実行を管理します。ゴルーチンの生成、実行、停止、ウェイクアップなどを担当します。
- タイマー (Timer): Goの標準ライブラリ
time
パッケージで提供される機能で、指定された時間が経過した後にイベントを発生させたり、ゴルーチンをウェイクアップしたりするために使用されます。内部的には、ランタイムのタイマーメカニズムによって管理されます。 notewakeup
: Goランタイム内部で使用されるプリミティブな同期メカニズムの一つです。これは、特定のイベントが発生した際に、スリープしているゴルーチンをウェイクアップするために使用されます。runtime.notesleep
と対になっており、notesleep
でスリープしたゴルーチンをnotewakeup
で起こします。これは、OSのセマフォや条件変数に似た低レベルのメカニズムです。notewakeup
は、Note
構造体に対して操作を行い、Note
は一回限りのイベントシグナルとして機能します。notewakeup
が呼び出されると、notesleep
で待機しているゴルーチンがブロック解除されます。runtime.lock
: Goランタイム内部で使用されるミューテックス(排他ロック)です。共有データ構造へのアクセスを保護し、複数のゴルーチンからの同時アクセスによるデータ競合を防ぎます。timers
構造体: Goランタイム内部でタイマーの状態を管理するためのデータ構造です。この構造体には、タイマーキューや、タイマーゴルーチンの状態を示すフラグなどが含まれます。特に、timers.sleeping
はタイマーゴルーチンが現在スリープしているかどうかを示すブーリアンフラグです。timerproc
関数: Goランタイム内部のタイマーゴルーチンが実行する関数です。この関数は、タイマーキューを監視し、期限切れになったタイマーを処理し、必要に応じてゴルーチンをウェイクアップします。
技術的詳細
このコミットの核心は、timerproc
関数内でtimers.sleeping
フラグを適切にリセットすることにあります。
従来の動作では、timerproc
がタイムアウトによってウェイクアップされた後も、timers.sleeping
フラグはtrue
のままでした。これは、タイマーゴルーチンがスリープ状態から復帰したにもかかわらず、ランタイムがまだスリープしていると認識していることを意味します。
この状態では、他のゴルーチンが新しいタイマーを設定しようとした際に、timers.sleeping
がtrue
であるため、タイマーゴルーチンがスリープしていると判断し、notewakeup
を呼び出してウェイクアップを試みます。しかし、実際にはtimerproc
はすでにウェイクアップされており、このnotewakeup
呼び出しは不要です。
このコミットでは、timerproc
関数のループの先頭で、runtime·lock(&timers)
によってtimers
構造体をロックした後、すぐにtimers.sleeping = false;
という行を追加しています。
この変更により、timerproc
がウェイクアップされ、タイマー処理を開始する前に、timers.sleeping
フラグがfalse
に設定されます。これにより、他のゴルーチンが新しいタイマーを設定する際に、timers.sleeping
がfalse
であることを確認し、タイマーゴルーチンがすでにアクティブであると判断するため、冗長なnotewakeup
呼び出しを回避できます。
この最適化は、特にタイマーが頻繁に発生するようなシナリオにおいて、システムコールの回数を減らし、CPU使用率を低下させることで、Goアプリケーションの全体的なパフォーマンスを向上させます。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/time.goc
ファイルの一箇所のみです。
--- a/src/pkg/runtime/time.goc
+++ b/src/pkg/runtime/time.goc
@@ -175,6 +175,7 @@ timerproc(void)
for(;;) {
runtime·lock(&timers);
+ timers.sleeping = false;
now = runtime·nanotime();
for(;;) {
if(timers.len == 0) {
コアとなるコードの解説
変更が加えられたのは、timerproc
関数の内部ループの開始部分です。
timerproc(void)
{
// ... (省略) ...
for(;;) { // タイマーゴルーチンのメインループ
runtime·lock(&timers); // timers構造体をロック
timers.sleeping = false; // ここが追加された行
now = runtime·nanotime();
for(;;) {
if(timers.len == 0) {
// ... (省略) ...
}
// ... (省略) ...
}
// ... (省略) ...
}
}
追加された行 timers.sleeping = false;
は、timerproc
がタイマー処理のループに入る直前、かつtimers
構造体への排他ロックを取得した直後に実行されます。
この配置が重要です。
runtime·lock(&timers)
の後:timers
構造体は共有リソースであるため、競合状態を防ぐためにロックが必要です。ロックを取得した後にsleeping
フラグを変更することで、他のゴルーチンがこのフラグを読み取る際に一貫性のある状態を保証します。- ループの先頭:
timerproc
がウェイクアップされて処理を開始するたびに、このフラグがリセットされることを保証します。これにより、タイマーゴルーチンがアクティブである間は、sleeping
フラグが常にfalse
に保たれ、他のゴルーチンによる冗長なnotewakeup
呼び出しを防ぎます。
このシンプルな変更により、Goランタイムのタイマー処理における不要なオーバーヘッドが削減され、全体的な効率が向上します。
関連リンク
- Goのタイマーとスケジューラに関する公式ドキュメントやブログ記事
- Goのランタイムソースコード(特に
src/runtime/time.go
やsrc/runtime/proc.go
など)
参考にした情報源リンク
- Goのコミット d1b66439f91377bceee1dd190a0be6c8367f7268
- Go CL 12763043 (Goの変更リスト)
- Goのランタイムに関する一般的な知識(Goの公式ドキュメント、Goのソースコード、Goのスケジューラに関する技術記事など)
notewakeup
やnotesleep
に関するGoランタイムの内部実装解説記事(例: Goの同期プリミティブに関するブログ記事など)- Goのタイマー実装に関する技術ブログや論文