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

[インデックス 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もこのモノトニック時間で設定されるようになっていました。

コミットメッセージでは、この問題を解決するためのいくつかの選択肢が検討されています。

  1. time.nowをC言語から呼び出せるruntime.unixnanotime()に変更する:

    • このアプローチでは、time.nowruntime.unixnanotime()というC言語で実装された関数を呼び出す形になります。
    • しかし、これによりtime.nowの内部に2つの64ビット除算が導入されることになり、パフォーマンスへの影響が懸念されました。
  2. time.nowをC言語から呼び出せるruntime.unixnanotime1(struct {int64 sec, int32 nsec} *now)に変更し、time.nowruntime.unixnanotimeの両方がこれを利用する:

    • このアプローチは、秒とナノ秒を別々に返すことで、より柔軟な設計を目指します。
    • しかし、これもランタイムと標準ライブラリ間の依存関係を複雑にする可能性がありました。

最終的に選択されたアプローチは、最もシンプルで影響が少ないと判断された方法です。それは、ランタイムのCコードから直接time.nowを呼び出すのではなく、アセンブリ言語のスタブを介してtime.nowにジャンプし、その結果をGoのヘルパー関数で処理するというものです。これにより、ランタイムのCコードはGo標準ライブラリに直接依存することなく、Unix時間を取得できるようになります。

具体的には、以下のステップでUnix時間を取得します。

  1. ランタイムのCコード(mgc0.c)が、Goのヘルパー関数runtime.gc_unixnanotimeを呼び出す。
  2. runtime.gc_unixnanotimeは、アセンブリ言語で実装されたruntime.timenow関数を呼び出す。
  3. runtime.timenowは、Go標準ライブラリのtime.now関数に直接ジャンプする。
  4. time.nowは現在のUnix時間(秒とナノ秒)を返す。
  5. 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時間を報告していることを検証します。

  1. runtime.GC()を呼び出す直前と直後にtime.Now().UnixNano()で現在のUnix時間を取得します(t0t1)。
  2. runtime.ReadMemStats(ms)でメモリ統計を読み込み、ms.LastGCの値を取得します。
  3. ms.LastGCの値がt0t1の間に収まっていることをアサートします。これにより、LastGCがGC実行時の実際のUnix時間に近い値であることを確認できます。

これらの変更により、Goランタイムは内部的な時間管理の整合性を保ちつつ、ユーザーに提供するMemStats.LastGCの情報を、期待されるUnix時間で正確に報告できるようになりました。

関連リンク

参考にした情報源リンク

The output is now ready to be presented to the user.