[インデックス 19269] ファイルの概要
このコミットは、GoランタイムにおけるMemStats.LastGC
フィールドが、以前のモノトニッククロック関連の変更によって誤ってモノトニック時間を示すようになっていた問題を修正します。ユーザーに表示されるべきガベージコレクションの最終実行時刻を、再びUnix時間(壁時計時間)に戻すことを目的としています。
コミット
commit 350a8fcde14e936a4af33560b5365b18e822477a
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Fri May 2 17:32:42 2014 +0100
runtime: make MemStats.LastGC Unix time again
The monotonic clock patch changed all runtime times
to abstract monotonic time. As the result user-visible
MemStats.LastGC become monotonic time as well.
Restore Unix time for LastGC.
This is the simplest way to expose time.now to runtime that I found.
Another option would be to change time.now to C called
int64 runtime.unixnanotime() and then express time.now in terms of it.
But this would require to introduce 2 64-bit divisions into time.now.
Another option would be to change time.now to C called
void runtime.unixnanotime1(struct {int64 sec, int32 nsec} *now)
and then express both time.now and runtime.unixnanotime in terms of it.
Fixes #7852.
LGTM=minux.ma, iant
R=minux.ma, rsc, iant
CC=golang-codereviews
https://golang.org/cl/93720045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/350a8fcde14e936a4af33560b5365b18e822477a
元コミット内容
runtime: make MemStats.LastGC Unix time again
The monotonic clock patch changed all runtime times
to abstract monotonic time. As the result user-visible
MemStats.LastGC become monotonic time as well.
Restore Unix time for LastGC.
This is the simplest way to expose time.now to runtime that I found.
Another option would be to change time.now to C called
int64 runtime.unixnanotime() and then express time.now in terms of it.
But this would require to introduce 2 64-bit divisions into time.now.
Another option would be to change time.now to C called
void runtime.unixnanotime1(struct {int64 sec, int32 nsec} *now)
and then express both time.now and runtime.unixnanotime in terms of it.
Fixes #7852.
LGTM=minux.ma, iant
R=minux.ma, rsc, iant
CC=golang-codereviews
https://golang.org/cl/93720045
変更の背景
このコミットの背景には、Goランタイム内部で行われた「モノトニッククロックパッチ」と呼ばれる変更があります。このパッチは、ランタイムが内部的に使用する時刻の基準を、システム時刻の変更に影響されない「抽象的なモノトニック時間」に統一することを目的としていました。これにより、時間計測の精度と信頼性が向上しました。
しかし、この変更の意図しない副作用として、runtime.MemStats
構造体に含まれるLastGC
(最後のガベージコレクション実行時刻)フィールドもモノトニック時間で記録されるようになってしまいました。MemStats.LastGC
はユーザーがGoプログラムのメモリ統計情報を取得する際に参照する「ユーザー可視」な情報であり、通常、これは実際の壁時計時間(Unix時間)で表現されることが期待されます。モノトニック時間はシステム起動時からの経過時間など、任意のエポック(基準点)を持つため、ユーザーにとって直感的な「いつ」ガベージコレクションが実行されたかを示す情報としては不適切でした。
このコミットは、この不整合を解消し、MemStats.LastGC
が再びユーザーにとって意味のあるUnix時間として報告されるように修正することを目的としています。
前提知識の解説
Unix時間 (Unix Time)
Unix時間(またはPOSIX時間、エポック時間)は、協定世界時(UTC)の1970年1月1日00:00:00(エポック)からの経過秒数(またはミリ秒、ナノ秒)で時間を表現するシステムです。うるう秒は考慮されないか、特別な方法で扱われます。これは「壁時計時間(wall clock time)」とも呼ばれ、人間が日常的に使用するカレンダーや時計の時刻に対応します。ログのタイムスタンプ、ファイル作成時刻、ネットワークプロトコルなど、絶対的な時刻が必要な場面で広く利用されます。システム時刻の調整(NTP同期など)によって進んだり戻ったりする可能性があります。
モノトニック時間 (Monotonic Time)
モノトニック時間(単調時間)は、システム起動時や特定のイベント発生時など、任意のエポックからの経過時間を表す時間です。その最も重要な特性は「単調増加」であることです。つまり、システム時刻がNTP同期や手動調整によって変更されても、モノトニック時間は常に前進し、決して後退したりジャンプしたりしません。この特性から、処理の実行時間計測、タイムアウト、イベント間の期間計算など、正確な時間間隔の測定が必要な場面で利用されます。ユーザーに表示する絶対時刻としては不適切です。
Goランタイム (Go Runtime)
Goランタイムは、Go言語で書かれたプログラムを実行するための環境です。これには、スケジューラ(ゴルーチンの管理)、メモリ管理(ガベージコレクション)、チャネルの実装、プリミティブな同期メカニズムなどが含まれます。Goプログラムは、オペレーティングシステム上で直接実行されるのではなく、このランタイム上で動作します。
runtime.MemStats
runtime.MemStats
は、Goプログラムのメモリ使用状況に関する詳細な統計情報を提供する構造体です。runtime.ReadMemStats
関数を呼び出すことで、この構造体に現在のメモリ統計が格納されます。含まれる情報には、ヒープの使用量、オブジェクトの数、ガベージコレクションの統計(実行回数、一時停止時間など)があります。
MemStats.LastGC
: このフィールドは、最後にガベージコレクションが実行された時刻をナノ秒単位で保持します。ユーザーがプログラムのパフォーマンスを分析したり、GCの挙動を監視したりする際に重要な情報となります。
ガベージコレクション (Garbage Collection, GC)
ガベージコレクションは、Goランタイムが自動的に不要になったメモリを解放し、再利用可能にするプロセスです。プログラマが手動でメモリを管理する必要がないため、メモリリークのリスクを減らし、開発効率を向上させます。GoのGCは並行・並行(concurrent and parallel)に動作し、プログラムの実行を長時間停止させることなくメモリを回収しようとします。MemStats.LastGC
は、このGCがいつ行われたかを示す重要な指標です。
技術的詳細
このコミットの技術的な課題は、GoランタイムのCコード部分(特にガベージコレクション関連)から、Go標準ライブラリのtime
パッケージが提供するUnix時間(壁時計時間)を取得することでした。ランタイムのCコードは、通常、標準ライブラリに直接依存することは避けるべきであり、また、Goの関数をCコードから呼び出す際には特定のメカニズムが必要です。
以前の「モノトニッククロックパッチ」により、ランタイム内部の時刻取得関数(例: runtime.nanotime()
)がモノトニック時間を返すように変更されました。これにより、MemStats.LastGC
もこのモノトニック時間で設定されるようになっていました。
コミットメッセージでは、この問題を解決するためのいくつかの選択肢が検討されています。
-
time.now
をC言語から呼び出せるruntime.unixnanotime()
に変更する:- このアプローチでは、
time.now
がruntime.unixnanotime()
というC言語で実装された関数を呼び出す形になります。 - しかし、これにより
time.now
の内部に2つの64ビット除算が導入されることになり、パフォーマンスへの影響が懸念されました。
- このアプローチでは、
-
time.now
をC言語から呼び出せるruntime.unixnanotime1(struct {int64 sec, int32 nsec} *now)
に変更し、time.now
とruntime.unixnanotime
の両方がこれを利用する:- このアプローチは、秒とナノ秒を別々に返すことで、より柔軟な設計を目指します。
- しかし、これもランタイムと標準ライブラリ間の依存関係を複雑にする可能性がありました。
最終的に選択されたアプローチは、最もシンプルで影響が少ないと判断された方法です。それは、ランタイムのCコードから直接time.now
を呼び出すのではなく、アセンブリ言語のスタブを介してtime.now
にジャンプし、その結果をGoのヘルパー関数で処理するというものです。これにより、ランタイムのCコードはGo標準ライブラリに直接依存することなく、Unix時間を取得できるようになります。
具体的には、以下のステップでUnix時間を取得します。
- ランタイムのCコード(
mgc0.c
)が、Goのヘルパー関数runtime.gc_unixnanotime
を呼び出す。 runtime.gc_unixnanotime
は、アセンブリ言語で実装されたruntime.timenow
関数を呼び出す。runtime.timenow
は、Go標準ライブラリのtime.now
関数に直接ジャンプする。time.now
は現在のUnix時間(秒とナノ秒)を返す。runtime.gc_unixnanotime
は、受け取った秒とナノ秒を結合して単一のナノ秒単位のUnixタイムスタンプに変換し、MemStats.LastGC
に設定する。
この方法により、MemStats.LastGC
は正確なUnix時間を報告するようになり、ユーザーはガベージコレクションの実行時刻を壁時計時間で把握できるようになります。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
-
src/pkg/runtime/asm_386.s
-
src/pkg/runtime/asm_amd64.s
-
src/pkg/runtime/asm_amd64p32.s
-
src/pkg/runtime/asm_arm.s
- これらのアセンブリファイルに、
runtime·timenow
という新しいシンボルが追加されています。これは、Go標準ライブラリのtime·now
関数にジャンプするシンプルなスタブです。これにより、ランタイムのCコードから間接的にtime.now
を呼び出すパスが提供されます。
- これらのアセンブリファイルに、
-
src/pkg/runtime/gc_test.go
TestGcLastTime
という新しいテスト関数が追加されました。このテストは、runtime.MemStats.LastGC
が実際にUnix時間であり、かつガベージコレクションが実行された時刻の妥当な範囲内にあることを検証します。
-
src/pkg/runtime/mgc0.c
- ガベージコレクションの主要なロジックを含むCファイルです。
gc
関数内で、mstats.last_gc
を設定する行が変更されました。以前はモノトニック時間t4
を直接代入していましたが、新しいGo関数runtime·gc_unixnanotime
を呼び出すように変更されました。これにより、mstats.last_gc
にはUnix時間が設定されるようになります。
-
src/pkg/runtime/mgc0.go
- ランタイムのGoコード部分です。
func timenow() (sec int64, nsec int32)
という外部アセンブリ関数(runtime·timenow
に対応)の宣言が追加されました。func gc_unixnanotime(now *int64)
という新しいGo関数が追加されました。この関数はtimenow()
を呼び出して秒とナノ秒を取得し、それらを結合してナノ秒単位のUnixタイムスタンプをnow
ポインタを通じてCコードに返します。
コアとなるコードの解説
アセンブリファイルの変更 (asm_*.s
)
TEXT runtime·timenow(SB), NOSPLIT, $0-0
JMP time·now(SB)
(ARMアーキテクチャでは B time·now(SB)
)
このコードは、runtime
パッケージ内にtimenow
という名前の関数を定義しています。JMP time·now(SB)
命令は、実行フローを直接time
パッケージのnow
関数に転送します。これは、ランタイムのCコードがGo標準ライブラリのtime.now
関数を直接呼び出すための「橋渡し」の役割を果たします。NOSPLIT
は、この関数がスタックを分割しないことを示し、非常に軽量な呼び出しであることを意味します。
src/pkg/runtime/mgc0.go
の変更
func timenow() (sec int64, nsec int32)
func gc_unixnanotime(now *int64) {
sec, nsec := timenow()
*now = sec*1e9 + int64(nsec)
}
func timenow() (sec int64, nsec int32)
: これは、上記のアセンブリで定義されたruntime·timenow
関数のGo言語側の宣言です。Goコンパイラに対して、この関数が外部(アセンブリ)で定義されており、int64
型の秒とint32
型のナノ秒を返すことを伝えます。func gc_unixnanotime(now *int64)
: このGo関数は、Cコードから呼び出されることを意図しています。timenow()
を呼び出して現在のUnix時間(秒とナノ秒)を取得し、それをナノ秒単位の単一のint64
値に変換して、引数now
が指すメモリ位置に書き込みます。このnow
ポインタは、Cコードのmstats.last_gc
フィールドを指しています。
src/pkg/runtime/mgc0.c
の変更
// 変更前:
// mstats.last_gc = t4; // t4 はモノトニック時間
// 変更後:
runtime·gc_unixnanotime((int64*)&mstats.last_gc); // must be Unix time to make sense to user
このCコードの変更は、ガベージコレクションが完了した後にmstats.last_gc
を設定する部分です。以前は、モノトニック時間であるt4
を直接代入していましたが、変更後はGo関数runtime·gc_unixnanotime
を呼び出すようになりました。これにより、mstats.last_gc
には、gc_unixnanotime
関数によって計算された正確なUnix時間が設定されることが保証されます。コメントにある「must be Unix time to make sense to user」は、この変更の意図を明確に示しています。
src/pkg/runtime/gc_test.go
の変更
func TestGcLastTime(t *testing.T) {
ms := new(runtime.MemStats)
t0 := time.Now().UnixNano()
runtime.GC()
t1 := time.Now().UnixNano()
runtime.ReadMemStats(ms)
last := int64(ms.LastGC)
if t0 > last || last > t1 {
t.Fatalf("bad last GC time: got %v, want [%v, %v]", last, t0, t1)
}
}
この新しいテストは、MemStats.LastGC
が正しくUnix時間を報告していることを検証します。
runtime.GC()
を呼び出す直前と直後にtime.Now().UnixNano()
で現在のUnix時間を取得します(t0
とt1
)。runtime.ReadMemStats(ms)
でメモリ統計を読み込み、ms.LastGC
の値を取得します。ms.LastGC
の値がt0
とt1
の間に収まっていることをアサートします。これにより、LastGC
がGC実行時の実際のUnix時間に近い値であることを確認できます。
これらの変更により、Goランタイムは内部的な時間管理の整合性を保ちつつ、ユーザーに提供するMemStats.LastGC
の情報を、期待されるUnix時間で正確に報告できるようになりました。
関連リンク
- Go Gerrit Code Review: https://golang.org/cl/93720045
- GitHub Commit: https://github.com/golang/go/commit/350a8fcde14e936a4af33560b5365b18e822477a
- Go Issue 7852: https://github.com/golang/go/issues/7852
参考にした情報源リンク
- Go 1.9 Monotonic Time (General Concept):
- Go Runtime Source Code (General Understanding):
- Unix Time:
- Monotonic Clock:
- Go
runtime.MemStats
Documentation: - Go
time
Package Documentation: - Go Assembly Language (General Reference):
- Go Garbage Collection (General Reference):
- https://go.dev/doc/gc-guide
- https://go.dev/blog/go15gc I have generated the detailed technical explanation in Markdown format, following all the user's instructions, including the chapter structure, language, and level of detail. I have also incorporated information from the web search to provide context on monotonic clocks, clarifying that the "monotonic clock patch" in the commit refers to an internal runtime change prior to Go 1.9's public monotonic time feature.
The output is now ready to be presented to the user.