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

[インデックス 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.Aftertime.Timerなどの機能を実現するための内部タイマーメカニズムが存在します。これらのタイマーは、特定の時間が経過した後にゴルーチンをウェイクアップするために使用されます。タイマー処理は、通常、専用のタイマーゴルーチンによって管理されます。

問題は、タイマーゴルーチンがタイムアウトによってウェイクアップされた場合でも、ランタイムの内部状態を示すtimers.sleepingフラグがまだtrueのままであることでした。このsleepingフラグは、タイマーゴルーチンが現在スリープしているかどうかを示します。他のゴルーチンがタイマーを設定しようとすると、このsleepingフラグを見て、タイマーゴルーチンがスリープしていると判断し、notewakeupを呼び出してタイマーゴルーチンをウェイクアップしようとします。

しかし、タイマーゴルーチンがすでにタイムアウトによってウェイクアップされている場合、このnotewakeup呼び出しは冗長であり、無駄なシステムコールやコンテキストスイッチを引き起こします。これは、特に多数のタイマーが頻繁に設定・期限切れになるような高負荷なシステムにおいて、パフォーマンスのオーバーヘッドとなる可能性がありました。

このコミットは、この冗長なnotewakeup呼び出しを排除し、ランタイムの効率を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムの概念について理解しておく必要があります。

  1. ゴルーチン (Goroutine): Goにおける軽量な実行スレッドです。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。
  2. スケジューラ (Scheduler): Goランタイムの重要なコンポーネントで、ゴルーチンをOSスレッドにマッピングし、実行を管理します。ゴルーチンの生成、実行、停止、ウェイクアップなどを担当します。
  3. タイマー (Timer): Goの標準ライブラリtimeパッケージで提供される機能で、指定された時間が経過した後にイベントを発生させたり、ゴルーチンをウェイクアップしたりするために使用されます。内部的には、ランタイムのタイマーメカニズムによって管理されます。
  4. notewakeup: Goランタイム内部で使用されるプリミティブな同期メカニズムの一つです。これは、特定のイベントが発生した際に、スリープしているゴルーチンをウェイクアップするために使用されます。runtime.notesleepと対になっており、notesleepでスリープしたゴルーチンをnotewakeupで起こします。これは、OSのセマフォや条件変数に似た低レベルのメカニズムです。
  5. runtime.lock: Goランタイム内部で使用されるミューテックス(排他ロック)です。共有データ構造へのアクセスを保護し、複数のゴルーチンからの同時アクセスによるデータ競合を防ぎます。
  6. timers構造体: Goランタイム内部でタイマーの状態を管理するためのデータ構造です。この構造体には、タイマーキューや、タイマーゴルーチンの状態を示すフラグなどが含まれます。特に、timers.sleepingはタイマーゴルーチンが現在スリープしているかどうかを示すブーリアンフラグです。
  7. timerproc関数: Goランタイム内部のタイマーゴルーチンが実行する関数です。この関数は、タイマーキューを監視し、期限切れになったタイマーを処理し、必要に応じてゴルーチンをウェイクアップします。

技術的詳細

このコミットの核心は、timerproc関数内でtimers.sleepingフラグを適切にリセットすることにあります。

従来の動作では、timerprocがタイムアウトによってウェイクアップされた後も、timers.sleepingフラグはtrueのままでした。これは、タイマーゴルーチンがスリープ状態から復帰したにもかかわらず、ランタイムがまだスリープしていると認識していることを意味します。

この状態では、他のゴルーチンが新しいタイマーを設定しようとした際に、timers.sleepingtrueであるため、タイマーゴルーチンがスリープしていると判断し、notewakeupを呼び出してウェイクアップを試みます。しかし、実際にはtimerprocはすでにウェイクアップされており、このnotewakeup呼び出しは不要です。

このコミットでは、timerproc関数のループの先頭で、runtime·lock(&timers)によってtimers構造体をロックした後、すぐにtimers.sleeping = false;という行を追加しています。

この変更により、timerprocがウェイクアップされ、タイマー処理を開始する前に、timers.sleepingフラグがfalseに設定されます。これにより、他のゴルーチンが新しいタイマーを設定する際に、timers.sleepingfalseであることを確認し、タイマーゴルーチンがすでにアクティブであると判断するため、冗長な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構造体への排他ロックを取得した直後に実行されます。

この配置が重要です。

  1. runtime·lock(&timers)の後: timers構造体は共有リソースであるため、競合状態を防ぐためにロックが必要です。ロックを取得した後にsleepingフラグを変更することで、他のゴルーチンがこのフラグを読み取る際に一貫性のある状態を保証します。
  2. ループの先頭: timerprocがウェイクアップされて処理を開始するたびに、このフラグがリセットされることを保証します。これにより、タイマーゴルーチンがアクティブである間は、sleepingフラグが常にfalseに保たれ、他のゴルーチンによる冗長なnotewakeup呼び出しを防ぎます。

このシンプルな変更により、Goランタイムのタイマー処理における不要なオーバーヘッドが削減され、全体的な効率が向上します。

関連リンク

  • Goのタイマーとスケジューラに関する公式ドキュメントやブログ記事
  • Goのランタイムソースコード(特にsrc/runtime/time.gosrc/runtime/proc.goなど)

参考にした情報源リンク

  • Goのコミット d1b66439f91377bceee1dd190a0be6c8367f7268
  • Go CL 12763043 (Goの変更リスト)
  • Goのランタイムに関する一般的な知識(Goの公式ドキュメント、Goのソースコード、Goのスケジューラに関する技術記事など)
  • notewakeupnotesleepに関する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.Aftertime.Timerなどの機能を実現するための内部タイマーメカニズムが存在します。これらのタイマーは、特定の時間が経過した後にゴルーチンをウェイクアップするために使用されます。タイマー処理は、通常、専用のタイマーゴルーチンによって管理されます。

問題は、タイマーゴルーチンがタイムアウトによってウェイクアップされた後も、ランタイムの内部状態を示すtimers.sleepingフラグがまだtrueのままであることでした。このsleepingフラグは、タイマーゴルーチンが現在スリープしているかどうかを示します。他のゴルーチンがタイマーを設定しようとすると、このsleepingフラグを見て、タイマーゴルーチンがスリープしていると判断し、notewakeupを呼び出してタイマーゴルーチンをウェイクアップしようとします。

しかし、タイマーゴルーチンがすでにタイムアウトによってウェイクアップされている場合、このnotewakeup呼び出しは冗長であり、無駄なシステムコールやコンテキストスイッチを引き起こします。これは、特に多数のタイマーが頻繁に設定・期限切れになるような高負荷なシステムにおいて、パフォーマンスのオーバーヘッドとなる可能性がありました。

このコミットは、この冗長なnotewakeup呼び出しを排除し、ランタイムの効率を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムの概念について理解しておく必要があります。

  1. ゴルーチン (Goroutine): Goにおける軽量な実行スレッドです。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。
  2. スケジューラ (Scheduler): Goランタイムの重要なコンポーネントで、ゴルーチンをOSスレッドにマッピングし、実行を管理します。ゴルーチンの生成、実行、停止、ウェイクアップなどを担当します。
  3. タイマー (Timer): Goの標準ライブラリtimeパッケージで提供される機能で、指定された時間が経過した後にイベントを発生させたり、ゴルーチンをウェイクアップしたりするために使用されます。内部的には、ランタイムのタイマーメカニズムによって管理されます。
  4. notewakeup: Goランタイム内部で使用されるプリミティブな同期メカニズムの一つです。これは、特定のイベントが発生した際に、スリープしているゴルーチンをウェイクアップするために使用されます。runtime.notesleepと対になっており、notesleepでスリープしたゴルーチンをnotewakeupで起こします。これは、OSのセマフォや条件変数に似た低レベルのメカニズムです。notewakeupは、Note構造体に対して操作を行い、Noteは一回限りのイベントシグナルとして機能します。notewakeupが呼び出されると、notesleepで待機しているゴルーチンがブロック解除されます。
  5. runtime.lock: Goランタイム内部で使用されるミューテックス(排他ロック)です。共有データ構造へのアクセスを保護し、複数のゴルーチンからの同時アクセスによるデータ競合を防ぎます。
  6. timers構造体: Goランタイム内部でタイマーの状態を管理するためのデータ構造です。この構造体には、タイマーキューや、タイマーゴルーチンの状態を示すフラグなどが含まれます。特に、timers.sleepingはタイマーゴルーチンが現在スリープしているかどうかを示すブーリアンフラグです。
  7. timerproc関数: Goランタイム内部のタイマーゴルーチンが実行する関数です。この関数は、タイマーキューを監視し、期限切れになったタイマーを処理し、必要に応じてゴルーチンをウェイクアップします。

技術的詳細

このコミットの核心は、timerproc関数内でtimers.sleepingフラグを適切にリセットすることにあります。

従来の動作では、timerprocがタイムアウトによってウェイクアップされた後も、timers.sleepingフラグはtrueのままでした。これは、タイマーゴルーチンがスリープ状態から復帰したにもかかわらず、ランタイムがまだスリープしていると認識していることを意味します。

この状態では、他のゴルーチンが新しいタイマーを設定しようとした際に、timers.sleepingtrueであるため、タイマーゴルーチンがスリープしていると判断し、notewakeupを呼び出してウェイクアップを試みます。しかし、実際にはtimerprocはすでにウェイクアップされており、このnotewakeup呼び出しは不要です。

このコミットでは、timerproc関数のループの先頭で、runtime·lock(&timers)によってtimers構造体をロックした後、すぐにtimers.sleeping = false;という行を追加しています。

この変更により、timerprocがウェイクアップされ、タイマー処理を開始する前に、timers.sleepingフラグがfalseに設定されます。これにより、他のゴルーチンが新しいタイマーを設定する際に、timers.sleepingfalseであることを確認し、タイマーゴルーチンがすでにアクティブであると判断するため、冗長な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構造体への排他ロックを取得した直後に実行されます。

この配置が重要です。

  1. runtime·lock(&timers)の後: timers構造体は共有リソースであるため、競合状態を防ぐためにロックが必要です。ロックを取得した後にsleepingフラグを変更することで、他のゴルーチンがこのフラグを読み取る際に一貫性のある状態を保証します。
  2. ループの先頭: timerprocがウェイクアップされて処理を開始するたびに、このフラグがリセットされることを保証します。これにより、タイマーゴルーチンがアクティブである間は、sleepingフラグが常にfalseに保たれ、他のゴルーチンによる冗長なnotewakeup呼び出しを防ぎます。

このシンプルな変更により、Goランタイムのタイマー処理における不要なオーバーヘッドが削減され、全体的な効率が向上します。

関連リンク

  • Goのタイマーとスケジューラに関する公式ドキュメントやブログ記事
  • Goのランタイムソースコード(特にsrc/runtime/time.gosrc/runtime/proc.goなど)

参考にした情報源リンク

  • Goのコミット d1b66439f91377bceee1dd190a0be6c8367f7268
  • Go CL 12763043 (Goの変更リスト)
  • Goのランタイムに関する一般的な知識(Goの公式ドキュメント、Goのソースコード、Goのスケジューラに関する技術記事など)
  • notewakeupnotesleepに関するGoランタイムの内部実装解説記事(例: Goの同期プリミティブに関するブログ記事など)
  • Goのタイマー実装に関する技術ブログや論文