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

[インデックス 10731] ファイルの概要

このコミットは、Go言語のmisc/cgo/test/issue1560.goファイルにおける時間計測ロジックの修正に関するものです。具体的には、非推奨となったtime.Nanoseconds()関数の使用を、より現代的で正確なtime.Now().Sub()を用いた時間差の計算に置き換えることで、テストの信頼性を向上させています。

コミット

commit b2cf7b5f6b0bf3e3336ed2e7e7b41db026ce04ab
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Tue Dec 13 10:46:58 2011 +1100

    misc/cgo/test: fix after latest time changes
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5454047

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/b2cf7b5f6b0bf3e3336ed2e7e7b41db026ce04ab

元コミット内容

misc/cgo/test: fix after latest time changes
    
R=rsc
CC=golang-dev
https://golang.org/cl/5454047

変更の背景

このコミットが行われた2011年当時、Go言語のtimeパッケージは活発に開発されており、APIの変更が頻繁に行われていました。特に、時間の計測方法に関するAPIは進化の途上にありました。

以前のGoのバージョンでは、time.Nanoseconds()という関数が存在し、これはtime.Time型のインスタンスが持つナノ秒単位の時刻値を直接取得するために使用されていました。しかし、この関数は設計上の問題や、より汎用的な時間計測メカニズムの導入に伴い、非推奨(deprecated)となりました。

misc/cgo/test/issue1560.go内のtestParallelSleep関数は、並行処理におけるスリープ時間の計測を行っていました。このテストは、複数のスリープ処理が直列ではなく並列に実行されることを検証するものでした。しかし、time.Nanoseconds()を使用して経過時間を計算する方法は、APIの変更や、より正確で意図を明確にするための新しいAPI(time.Now().Sub()など)の導入により、適切ではなくなりました。

このコミットは、timeパッケージの最新の変更("latest time changes")に対応し、非推奨となったtime.Nanoseconds()の使用を、より推奨されるtime.Now().Sub()による時間差の計算に置き換えることで、テストの正確性と将来の互換性を確保することを目的としています。また、テストのアサーション部分も、time.Duration型を直接比較するように修正され、よりGoらしい(idiomatic)コードになっています。

前提知識の解説

このコミットを理解するためには、Go言語のtimeパッケージに関する以下の概念を理解しておく必要があります。

  1. time.Time:

    • 特定の時点(point in time)を表す構造体です。
    • time.Now()関数は、現在のローカル時刻を表すtime.Timeインスタンスを返します。
    • time.Timeインスタンスは、年、月、日、時、分、秒、ナノ秒などの情報を含みます。
  2. time.Duration:

    • 時間の長さや期間(duration)を表す型です。
    • 内部的にはint64でナノ秒単位の値を保持します。
    • 例えば、time.Secondは1秒間の期間を表すtime.Duration型の定数です。
    • time.Millisecondtime.Microsecondtime.Nanosecondなども同様です。
  3. time.Nanoseconds() (非推奨):

    • このコミットが行われる以前のGoのバージョンに存在した、time.Time型のメソッドです。
    • t.Nanoseconds()のように呼び出され、tが表す時刻をナノ秒単位の整数値で返していました。
    • しかし、この関数はUnixエポックからのナノ秒数を返すUnixNano()とは異なり、その具体的な基準点が曖昧であったり、時間差の計算には不向きであったりするなどの理由で非推奨となりました。
  4. time.Now().Sub(start time.Time):

    • time.Time型のメソッドSubは、2つのtime.Timeインスタンス間の差を計算し、その結果をtime.Duration型で返します。
    • end.Sub(start)は、startからendまでの期間を返します。
    • これは、特定の処理の開始時刻と終了時刻をtime.Timeで記録し、その差を計算して処理時間を測定する際に非常に一般的なパターンです。
  5. time.Duration.Nanoseconds()time.Duration.Seconds():

    • time.Duration型には、その期間をナノ秒単位のint64で返すNanoseconds()メソッドや、秒単位のfloat64で返すSeconds()メソッドなどがあります。
    • これらは、time.Nanoseconds()(非推奨)とは異なり、time.Durationの値を特定の単位で取得するためのものです。

このコミットは、非推奨となったtime.Nanoseconds()から、より明確で正確なtime.Now().Sub()への移行を示しており、Go言語のtimeパッケージの進化を反映しています。

技術的詳細

このコミットの技術的な核心は、Go言語における時間計測のベストプラクティスへの移行です。

元のコードでは、parallelSleep関数の実行時間を計測するために、以下のようなロジックが使われていました。

dt := -time.Nanoseconds()
parallelSleep(1)
dt += time.Nanoseconds()

ここでtime.Nanoseconds()は、おそらく現在の時刻をナノ秒単位で返すグローバルな関数、またはtime.Timeインスタンスからナノ秒を取得するメソッドとして使われていたと推測されます(コミット時点のGoのAPI仕様に依存)。この方法では、dtの初期値として負のナノ秒数を設定し、処理後に現在のナノ秒数を加算することで経過時間を計算していました。しかし、このアプローチにはいくつかの問題がありました。

  1. time.Nanoseconds()の非推奨化: 前述の通り、この関数はGoのtimeパッケージの進化に伴い非推奨となりました。これは、APIの安定性や一貫性の観点から、この関数を使用し続けることが望ましくないことを意味します。
  2. 計算の複雑さ: 負の値から開始して加算するという計算方法は、直感的ではなく、バグを誘発しやすい可能性があります。
  3. 精度と基準点: time.Nanoseconds()が返すナノ秒値の具体的な基準点(例: Unixエポックからの経過時間、システム起動からの経過時間など)が明確でない場合、異なる呼び出し間で正確な時間差を計算することが困難になる可能性があります。

新しいコードでは、これらの問題を解決するために、以下のように変更されました。

start := time.Now()
parallelSleep(1)
dt := time.Now().Sub(start)

この変更により、以下の利点が得られます。

  1. time.Now().Sub()の利用: time.Now()で処理開始時刻のtime.Timeインスタンスを取得し、処理終了後に再度time.Now()を呼び出し、その結果に対してSub()メソッドを使用することで、2つのtime.Timeインスタンス間の正確なtime.Duration(期間)を直接取得できます。これはGo言語における時間計測の標準的かつ推奨される方法です。
  2. 明確な意味: startdtという変数名が、それぞれ開始時刻と経過時間を明確に示しており、コードの可読性が向上します。
  3. 型安全性: dttime.Duration型として扱われるため、期間に関する操作(例: dt.Seconds()で秒単位に変換)が型安全に行えます。

さらに、テストのアサーション部分も変更されています。

元のコード:

if dt >= 1.3e9 {
    t.Fatalf("parallel 1-second sleeps slept for %f seconds", float64(dt)/1e9)
}

新しいコード:

if dt >= 1300*time.Millisecond {
    t.Fatalf("parallel 1-second sleeps slept for %f seconds", dt.Seconds())
}

この変更もまた、Goのtimeパッケージの慣用的な使い方に沿ったものです。

  1. time.Durationとの直接比較: 1.3e9(1.3 * 10^9 ナノ秒、つまり1.3秒)というマジックナンバーを直接比較する代わりに、1300*time.Millisecondというtime.Duration型の値と比較しています。これにより、コードの意図がより明確になり、単位の誤解を防ぐことができます。time.Millisecondtime.Duration型の定数であり、1ミリ秒を表します。
  2. dt.Seconds()の利用: エラーメッセージ内で経過時間を秒単位で表示するために、float64(dt)/1e9という手動の変換ではなく、time.Duration型のSeconds()メソッドを使用しています。これは、time.Durationの値を秒単位のfloat64として取得する標準的な方法であり、より簡潔で正確です。

これらの変更により、テストコードはGoのtimeパッケージの最新のAPIとベストプラクティスに準拠し、より堅牢で理解しやすいものになりました。

コアとなるコードの変更箇所

diff --git a/misc/cgo/test/issue1560.go b/misc/cgo/test/issue1560.go
index e534cce473..7168f1cf7b 100644
--- a/misc/cgo/test/issue1560.go
+++ b/misc/cgo/test/issue1560.go
@@ -36,11 +36,11 @@ func BackgroundSleep(n int) {
 }
 
 func testParallelSleep(t *testing.T) {
-	dt := -time.Nanoseconds()
+	start := time.Now()
 	parallelSleep(1)
-	dt += time.Nanoseconds()
+	dt := time.Now().Sub(start)
 	// bug used to run sleeps in serial, producing a 2-second delay.
-	if dt >= 1.3e9 {
-		t.Fatalf("parallel 1-second sleeps slept for %f seconds", float64(dt)/1e9)
+	if dt >= 1300*time.Millisecond {
+		t.Fatalf("parallel 1-second sleeps slept for %f seconds", dt.Seconds())
 	}
 }

コアとなるコードの解説

このコミットは、misc/cgo/test/issue1560.goファイル内のtestParallelSleep関数に焦点を当てています。

  • 旧コード (- で示される行):

    dt := -time.Nanoseconds()
    // ...
    dt += time.Nanoseconds()
    // ...
    if dt >= 1.3e9 {
        t.Fatalf("parallel 1-second sleeps slept for %f seconds", float64(dt)/1e9)
    }
    
    • dt := -time.Nanoseconds(): time.Nanoseconds()は、当時のGoのAPIで現在の時刻をナノ秒単位で返す関数(またはメソッド)でした。ここで負の値で初期化しているのは、後で現在のナノ秒数を加算することで経過時間を計算するためです。この方法は直感的ではなく、time.Nanoseconds()自体が非推奨となりました。
    • dt += time.Nanoseconds(): parallelSleep関数が実行された後、再度現在のナノ秒数を取得し、dtに加算することで経過時間を算出していました。
    • if dt >= 1.3e9: 計算された経過時間dt1.3e9(1.3 * 10^9 ナノ秒、つまり1.3秒)以上であるかをチェックしていました。これは、並列スリープが期待通りに動作せず、直列に実行された場合に発生する2秒程度の遅延を検出するための閾値です。
    • float64(dt)/1e9: エラーメッセージで経過時間を秒単位で表示するために、ナノ秒単位のdtfloat64にキャストし、1e9(10億)で割っていました。
  • 新コード (+ で示される行):

    start := time.Now()
    // ...
    dt := time.Now().Sub(start)
    // ...
    if dt >= 1300*time.Millisecond {
        t.Fatalf("parallel 1-second sleeps slept for %f seconds", dt.Seconds())
    }
    
    • start := time.Now(): 処理開始時の現在の時刻をtime.Time型のstart変数に記録します。time.Now()はGoにおける現在の時刻を取得する標準的な関数です。
    • dt := time.Now().Sub(start): parallelSleep関数が実行された後、再度time.Now()を呼び出し、その結果からstart時刻をSub()メソッドで減算します。これにより、parallelSleepの実行にかかった正確な期間がtime.Duration型としてdtに格納されます。この方法は、Goで経過時間を計測する際の推奨される慣用的なパターンです。
    • if dt >= 1300*time.Millisecond: 経過時間dttime.Duration型)が1300*time.Millisecond(1300ミリ秒、つまり1.3秒)以上であるかをチェックします。time.Millisecondtime.Duration型の定数であり、コードの意図がより明確になります。
    • dt.Seconds(): エラーメッセージで経過時間を秒単位で表示するために、time.Duration型のdtに対してSeconds()メソッドを呼び出します。これは、time.Durationを秒単位のfloat64として取得する標準的で簡潔な方法です。

この変更により、時間計測のロジックがGoの最新のAPIとベストプラクティスに準拠し、コードの可読性、正確性、および保守性が大幅に向上しました。

関連リンク

参考にした情報源リンク