[インデックス 18640] ファイルの概要
このコミットは、Go言語のランタイムにおけるタイマーの実装に関する変更です。具体的には、Linux/ARMアーキテクチャにおいて、タイマーの基盤となるクロックとしてCLOCK_REALTIME
からCLOCK_MONOTONIC
への切り替えを行っています。
コミット
commit 7206f50f719cdac2a93e2beb723908bff69d7f22
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Feb 25 23:03:01 2014 +0900
runtime: use monotonic clock for timers on linux/arm
Update #6007
LGTM=dvyukov
R=golang-codereviews, dvyukov
CC=golang-codereviews
https://golang.org/cl/67730048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7206f50f719cdac2a93e2beb723908bff69d7f22
元コミット内容
runtime: use monotonic clock for timers on linux/arm
Update #6007
LGTM=dvyukov
R=golang-codereviews, dvyukov
CC=golang-codereviews
https://golang.org/cl/67730048
変更の背景
この変更の背景には、Goランタイムがタイマーやスケジューリングのために使用する時間の精度と信頼性の問題がありました。特に、システムクロック(CLOCK_REALTIME
)がNTP(Network Time Protocol)などによって調整されたり、手動で変更されたりすると、その影響がGoランタイムのタイマーに及び、予期せぬ動作やパフォーマンスの問題を引き起こす可能性がありました。
Goランタイムは、ゴルーチン(goroutine)のスケジューリング、ネットワークI/Oのタイムアウト、time.Sleep
などの機能で内部的にタイマーを使用しています。これらの機能が正確に動作するためには、システムクロックの変更に影響されない、単調増加する時間源が必要です。
コミットメッセージにあるUpdate #6007
は、この変更がGoのIssue 6007に関連していることを示唆しています。Issue 6007は、Goのタイマーがシステムクロックの変更に影響される問題について議論しており、CLOCK_MONOTONIC
の使用が解決策として提案されていました。
前提知識の解説
システムクロックの種類
Linuxシステムには、主に以下の2種類のシステムクロックが存在します。
-
CLOCK_REALTIME
(リアルタイムクロック):- これは「壁時計時間(wall-clock time)」とも呼ばれ、実際のカレンダー時間(年、月、日、時、分、秒)を表します。
- システム起動時からの経過時間ではなく、特定の基準点(通常は1970年1月1日00:00:00 UTC)からの経過時間を表します。
- NTPサーバーとの同期や、システム管理者の手動操作によって、進んだり遅れたり、あるいはジャンプしたりする可能性があります。
- 時刻の変更は、タイマーや時間計測に依存するアプリケーションの動作に影響を与える可能性があります。例えば、時刻が過去に巻き戻されると、タイマーが予期せず長く待機したり、すでに期限切れと判断されるべきイベントがまだ発生していないと見なされたりする可能性があります。
-
CLOCK_MONOTONIC
(モノトニッククロック):- これは「単調増加クロック」とも呼ばれ、システムが起動してからの経過時間を表します。
- システムクロックの変更(NTP同期や手動調整)には影響されず、常に単調に増加します。つまり、過去に巻き戻ることはありません。
- システムのサスペンド時には停止し、レジューム時に再開する場合があります(
CLOCK_MONOTONIC_RAW
はサスペンド中も進み続けますが、CLOCK_MONOTONIC
は通常サスペンドの影響を受けます)。 - 主に、時間間隔の計測やタイムアウトの実装など、絶対時刻ではなく相対的な時間経過が重要な場面で使用されます。
Goランタイムのタイマー
Goランタイムは、内部的に非常に効率的なタイマー管理システムを持っています。これは、ゴルーチンのスケジューリング、ネットワークI/Oのタイムアウト、time.Sleep
、time.After
、time.NewTimer
などの標準ライブラリ関数を支える基盤となります。
Goのタイマーは、最小ヒープ(min-heap)データ構造を使用して管理されており、次に期限が来るタイマーが常にヒープのルートに位置するように設計されています。これにより、効率的に次のタイマーイベントを処理できます。タイマーの期限が来ると、関連するゴルーチンが実行可能状態になり、Goスケジューラによって実行されます。
タイマーの正確性は、その基盤となるシステムクロックに大きく依存します。システムクロックが不安定だと、タイマーの精度が損なわれ、アプリケーションの動作に悪影響を及ぼす可能性があります。
ARMアーキテクチャ
ARM(Advanced RISC Machine)は、モバイルデバイス、組み込みシステム、IoTデバイスなどで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。低消費電力と高性能を両立できる点が特徴です。Go言語は、ARMを含む多くのアーキテクチャをサポートしており、それぞれのアーキテクチャに特化したランタイムコードを持っています。
技術的詳細
このコミットの技術的な核心は、Linux/ARM環境におけるGoランタイムのnanotime
関数の実装変更です。nanotime
関数は、Goランタイムがナノ秒単位の現在時刻を取得するために使用する内部関数です。
変更前は、nanotime
関数がCLOCK_REALTIME
を使用して時刻を取得していました。これは、Linuxシステムコールclock_gettime
を呼び出す際に、CLOCK_REALTIME
に対応する定数(通常は0
)を引数として渡すことで実現されていました。
変更後は、nanotime
関数がCLOCK_MONOTONIC
を使用するように修正されました。これは、clock_gettime
システムコールにCLOCK_MONOTONIC
に対応する定数(通常は1
)を渡すことで実現されます。
この変更により、Goランタイムのタイマーは、システムクロックの調整やジャンプの影響を受けなくなります。これにより、タイマーの精度と信頼性が向上し、特に長時間稼働するサーバーアプリケーションや、厳密な時間管理が求められるシステムにおいて、より堅牢な動作が期待できます。
ARMアーキテクチャに特化してこの変更が適用されたのは、おそらく当時のGoのサポート状況や、特定のARMベースのシステムでCLOCK_REALTIME
の不安定性が顕著であったためと考えられます。他のアーキテクチャやOSでは、既にCLOCK_MONOTONIC
が使用されていたか、あるいは同様の問題が後から修正された可能性があります。
コアとなるコードの変更箇所
変更は、src/pkg/runtime/sys_linux_arm.s
ファイル内のruntime·nanotime
関数の実装にあります。このファイルは、Linux/ARMアーキテクチャに特化したGoランタイムのアセンブリコードを含んでいます。
--- a/src/pkg/runtime/sys_linux_arm.s
+++ b/src/pkg/runtime/sys_linux_arm.s
@@ -175,7 +175,7 @@ TEXT time·now(SB), NOSPLIT, $32
// int64 nanotime(void) so really
// void nanotime(int64 *nsec)
TEXT runtime·nanotime(SB),NOSPLIT,$32
- MOVW $0, R0 // CLOCK_REALTIME
+ MOVW $1, R0 // CLOCK_MONOTONIC
MOVW $8(R13), R1 // timespec
MOVW $SYS_clock_gettime, R7
SWI $0
コアとなるコードの解説
上記のコードスニペットは、ARMアセンブリ言語で書かれています。
TEXT runtime·nanotime(SB),NOSPLIT,$32
:- これは
runtime·nanotime
というGoの関数を定義しています。SB
はシンボルベースレジスタ、NOSPLIT
はスタックフレームを分割しないことを示し、$32
はスタックフレームのサイズ(32バイト)を示します。
- これは
MOVW $0, R0 // CLOCK_REALTIME
(変更前):MOVW
は「Move Word」命令で、即値0
をARMプロセッサのレジスタR0
に移動します。R0
は、Linuxシステムコールを呼び出す際の最初の引数を渡すために使用されるレジスタです。- コメントにあるように、
0
はCLOCK_REALTIME
に対応する定数です。これは、clock_gettime
システムコールに「リアルタイムクロックの時刻を取得してほしい」と指示していました。
MOVW $1, R0 // CLOCK_MONOTONIC
(変更後):- 変更後、即値
1
がR0
に移動されます。 1
はCLOCK_MONOTONIC
に対応する定数です。これにより、clock_gettime
システムコールは「単調増加クロックの時刻を取得してほしい」と指示されるようになります。
- 変更後、即値
MOVW $8(R13), R1 // timespec
:R13
はスタックポインタ(SP)レジスタです。8(R13)
は、スタックポインタから8バイトオフセットしたアドレスを指します。- このアドレスには、
timespec
構造体(秒とナノ秒で時間を表す構造体)を格納するためのメモリ領域が確保されています。R1
は、clock_gettime
システムコールの2番目の引数として、このtimespec
構造体へのポインタを渡すために使用されます。
MOVW $SYS_clock_gettime, R7
:SYS_clock_gettime
は、clock_gettime
システムコールの番号を表す定数です。- この定数が
R7
レジスタに移動されます。ARMでは、システムコール番号はR7
レジスタに格納されます。
SWI $0
:SWI
は「Software Interrupt」命令で、システムコールを実行します。$0
は、通常、システムコールをトリガーするためのソフトウェア割り込み番号です。
この一連のアセンブリ命令は、Goランタイムがclock_gettime(CLOCK_ID, ×pec)
というC言語の関数呼び出しに相当する処理を、Linux/ARM環境で実行する方法を示しています。変更の核心は、CLOCK_ID
としてCLOCK_REALTIME
(0
)ではなくCLOCK_MONOTONIC
(1
)を使用するように定数を変更した点にあります。
関連リンク
- Go Issue 6007: https://github.com/golang/go/issues/6007 (このコミットが解決しようとした問題の詳細が議論されている可能性があります)
clock_gettime
man page: https://man7.org/linux/man-pages/man2/clock_gettime.2.html (Linuxシステムコールclock_gettime
の詳細)
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/runtime/sys_linux_arm.s
) - Linux
clock_gettime
システムコールのドキュメント - ARMアセンブリ言語の基本
- Go言語のタイマーとスケジューラに関する一般的な情報源
- Go Issue 6007の議論内容(Web検索で確認)
[インデックス 18640] ファイルの概要
このコミットは、Go言語のランタイムにおけるタイマーの実装に関する変更です。具体的には、Linux/ARMアーキテクチャにおいて、タイマーの基盤となるクロックとしてCLOCK_REALTIME
からCLOCK_MONOTONIC
への切り替えを行っています。
コミット
commit 7206f50f719cdac2a93e2beb723908bff69d7f22
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Feb 25 23:03:01 2014 +0900
runtime: use monotonic clock for timers on linux/arm
Update #6007
LGTM=dvyukov
R=golang-codereviews, dvyukov
CC=golang-codereviews
https://golang.org/cl/67730048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7206f50f719cdac2a93e2beb723908bff69d7f22
元コミット内容
runtime: use monotonic clock for timers on linux/arm
Update #6007
LGTM=dvyukov
R=golang-codereviews, dvyukov
CC=golang-codereviews
https://golang.org/cl/67730048
変更の背景
この変更の背景には、Goランタイムがタイマーやスケジューリングのために使用する時間の精度と信頼性の問題がありました。特に、システムクロック(CLOCK_REALTIME
)がNTP(Network Time Protocol)などによって調整されたり、手動で変更されたりすると、その影響がGoランタイムのタイマーに及び、予期せぬ動作やパフォーマンスの問題を引き起こす可能性がありました。
Goランタイムは、ゴルーチン(goroutine)のスケジューリング、ネットワークI/Oのタイムアウト、time.Sleep
などの機能で内部的にタイマーを使用しています。これらの機能が正確に動作するためには、システムクロックの変更に影響されない、単調増加する時間源が必要です。
コミットメッセージにあるUpdate #6007
は、この変更がGoのIssue 6007に関連していることを示唆しています。Issue 6007は、Goのタイマーがシステムクロックの変更に影響される問題について議論しており、CLOCK_MONOTONIC
の使用が解決策として提案されていました。Go 1.3では、time.Sleep
、Ticker
、Timer
関数がLinuxを含む様々なプラットフォームでCLOCK_MONOTONIC
を利用するように変更され、システムクロックの変更に対する堅牢性が向上しました。さらに、Go 1.9ではtime.Time
値が内部的にモノトニッククロックの読み取りを追跡するようになり、システムウォールクロックが調整された場合でも、2つのtime.Time
値間の正確な期間計算が可能になりました。
前提知識の解説
システムクロックの種類
Linuxシステムには、主に以下の2種類のシステムクロックが存在します。
-
CLOCK_REALTIME
(リアルタイムクロック):- これは「壁時計時間(wall-clock time)」とも呼ばれ、実際のカレンダー時間(年、月、日、時、分、秒)を表します。
- システム起動時からの経過時間ではなく、特定の基準点(通常は1970年1月1日00:00:00 UTC)からの経過時間を表します。
- NTPサーバーとの同期や、システム管理者の手動操作によって、進んだり遅れたり、あるいはジャンプしたりする可能性があります。
- 時刻の変更は、タイマーや時間計測に依存するアプリケーションの動作に影響を与える可能性があります。例えば、時刻が過去に巻き戻されると、タイマーが予期せず長く待機したり、すでに期限切れと判断されるべきイベントがまだ発生していないと見なされたりする可能性があります。
-
CLOCK_MONOTONIC
(モノトニッククロック):- これは「単調増加クロック」とも呼ばれ、システムが起動してからの経過時間を表します。
- システムクロックの変更(NTP同期や手動調整)には影響されず、常に単調に増加します。つまり、過去に巻き戻ることはありません。
- システムのサスペンド時には停止し、レジューム時に再開する場合があります(
CLOCK_MONOTONIC_RAW
はサスペンド中も進み続けますが、CLOCK_MONOTONIC
は通常サスペンドの影響を受けます)。 - 主に、時間間隔の計測やタイムアウトの実装など、絶対時刻ではなく相対的な時間経過が重要な場面で使用されます。
- Go 1.9以降では、Linuxシステムで利用可能な場合は
CLOCK_BOOTTIME
が優先されるようになりました。CLOCK_BOOTTIME
はCLOCK_MONOTONIC
に似ていますが、システムがサスペンド状態にある間の時間も考慮するため、コンピュータが一時停止している期間をまたぐ期間測定において、より正確な値を提供します。
Goランタイムのタイマー
Goランタイムは、内部的に非常に効率的なタイマー管理システムを持っています。これは、ゴルーチンのスケジューリング、ネットワークI/Oのタイムアウト、time.Sleep
、time.After
、time.NewTimer
などの標準ライブラリ関数を支える基盤となります。
Goのタイマーは、最小ヒープ(min-heap)データ構造を使用して管理されており、次に期限が来るタイマーが常にヒープのルートに位置するように設計されています。これにより、効率的に次のタイマーイベントを処理できます。タイマーの期限が来ると、関連するゴルーチンが実行可能状態になり、Goスケジューラによって実行されます。
タイマーの正確性は、その基盤となるシステムクロックに大きく依存します。システムクロックが不安定だと、タイマーの精度が損なわれ、アプリケーションの動作に悪影響を及ぼす可能性があります。
ARMアーキテクチャ
ARM(Advanced RISC Machine)は、モバイルデバイス、組み込みシステム、IoTデバイスなどで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。低消費電力と高性能を両立できる点が特徴です。Go言語は、ARMを含む多くのアーキテクチャをサポートしており、それぞれのアーキテクチャに特化したランタイムコードを持っています。
技術的詳細
このコミットの技術的な核心は、Linux/ARM環境におけるGoランタイムのnanotime
関数の実装変更です。nanotime
関数は、Goランタイムがナノ秒単位の現在時刻を取得するために使用する内部関数です。
変更前は、nanotime
関数がCLOCK_REALTIME
を使用して時刻を取得していました。これは、Linuxシステムコールclock_gettime
を呼び出す際に、CLOCK_REALTIME
に対応する定数(通常は0
)を引数として渡すことで実現されていました。
変更後は、nanotime
関数がCLOCK_MONOTONIC
を使用するように修正されました。これは、clock_get_time
システムコールにCLOCK_MONOTONIC
に対応する定数(通常は1
)を渡すことで実現されます。
この変更により、Goランタイムのタイマーは、システムクロックの調整やジャンプの影響を受けなくなります。これにより、タイマーの精度と信頼性が向上し、特に長時間稼働するサーバーアプリケーションや、厳密な時間管理が求められるシステムにおいて、より堅牢な動作が期待できます。
ARMアーキテクチャに特化してこの変更が適用されたのは、おそらく当時のGoのサポート状況や、特定のARMベースのシステムでCLOCK_REALTIME
の不安定性が顕著であったためと考えられます。他のアーキテクチャやOSでは、既にCLOCK_MONOTONIC
が使用されていたか、あるいは同様の問題が後から修正された可能性があります。
コアとなるコードの変更箇所
変更は、src/pkg/runtime/sys_linux_arm.s
ファイル内のruntime·nanotime
関数の実装にあります。このファイルは、Linux/ARMアーキテクチャに特化したGoランタイムのアセンブリコードを含んでいます。
--- a/src/pkg/runtime/sys_linux_arm.s
+++ b/src/pkg/runtime/sys_linux_arm.s
@@ -175,7 +175,7 @@ TEXT time·now(SB), NOSPLIT, $32
// int64 nanotime(void) so really
// void nanotime(int64 *nsec)
TEXT runtime·nanotime(SB),NOSPLIT,$32
- MOVW $0, R0 // CLOCK_REALTIME
+ MOVW $1, R0 // CLOCK_MONOTONIC
MOVW $8(R13), R1 // timespec
MOVW $SYS_clock_gettime, R7
SWI $0
コアとなるコードの解説
上記のコードスニペットは、ARMアセンブリ言語で書かれています。
TEXT runtime·nanotime(SB),NOSPLIT,$32
:- これは
runtime·nanotime
というGoの関数を定義しています。SB
はシンボルベースレジスタ、NOSPLIT
はスタックフレームを分割しないことを示し、$32
はスタックフレームのサイズ(32バイト)を示します。
- これは
MOVW $0, R0 // CLOCK_REALTIME
(変更前):MOVW
は「Move Word」命令で、即値0
をARMプロセッサのレジスタR0
に移動します。R0
は、Linuxシステムコールを呼び出す際の最初の引数を渡すために使用されるレジスタです。- コメントにあるように、
0
はCLOCK_REALTIME
に対応する定数です。これは、clock_gettime
システムコールに「リアルタイムクロックの時刻を取得してほしい」と指示していました。
MOVW $1, R0 // CLOCK_MONOTONIC
(変更後):- 変更後、即値
1
がR0
に移動されます。 1
はCLOCK_MONOTONIC
に対応する定数です。これにより、clock_gettime
システムコールは「単調増加クロックの時刻を取得してほしい」と指示されるようになります。
- 変更後、即値
MOVW $8(R13), R1 // timespec
:R13
はスタックポインタ(SP)レジスタです。8(R13)
は、スタックポインタから8バイトオフセットしたアドレスを指します。- このアドレスには、
timespec
構造体(秒とナノ秒で時間を表す構造体)を格納するためのメモリ領域が確保されています。R1
は、clock_gettime
システムコールの2番目の引数として、このtimespec
構造体へのポインタを渡すために使用されます。
MOVW $SYS_clock_gettime, R7
:SYS_clock_gettime
は、clock_gettime
システムコールの番号を表す定数です。- この定数が
R7
レジスタに移動されます。ARMでは、システムコール番号はR7
レジスタに格納されます。
SWI $0
:SWI
は「Software Interrupt」命令で、システムコールを実行します。$0
は、通常、システムコールをトリガーするためのソフトウェア割り込み番号です。
この一連のアセンブリ命令は、Goランタイムがclock_gettime(CLOCK_ID, ×pec)
というC言語の関数呼び出しに相当する処理を、Linux/ARM環境で実行する方法を示しています。変更の核心は、CLOCK_ID
としてCLOCK_REALTIME
(0
)ではなくCLOCK_MONOTONIC
(1
)を使用するように定数を変更した点にあります。
関連リンク
- Go Issue 6007: https://github.com/golang/go/issues/6007
clock_gettime
man page: https://man7.org/linux/man-pages/man2/clock_gettime.2.html
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/runtime/sys_linux_arm.s
) - Linux
clock_gettime
システムコールのドキュメント - ARMアセンブリ言語の基本
- Go言語のタイマーとスケジューラに関する一般的な情報源
- Go Issue 6007の議論内容(Web検索で確認)