[インデックス 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
パッケージに関する以下の概念を理解しておく必要があります。
-
time.Time
型:- 特定の時点(point in time)を表す構造体です。
time.Now()
関数は、現在のローカル時刻を表すtime.Time
インスタンスを返します。time.Time
インスタンスは、年、月、日、時、分、秒、ナノ秒などの情報を含みます。
-
time.Duration
型:- 時間の長さや期間(duration)を表す型です。
- 内部的には
int64
でナノ秒単位の値を保持します。 - 例えば、
time.Second
は1秒間の期間を表すtime.Duration
型の定数です。 time.Millisecond
、time.Microsecond
、time.Nanosecond
なども同様です。
-
time.Nanoseconds()
(非推奨):- このコミットが行われる以前のGoのバージョンに存在した、
time.Time
型のメソッドです。 t.Nanoseconds()
のように呼び出され、t
が表す時刻をナノ秒単位の整数値で返していました。- しかし、この関数はUnixエポックからのナノ秒数を返す
UnixNano()
とは異なり、その具体的な基準点が曖昧であったり、時間差の計算には不向きであったりするなどの理由で非推奨となりました。
- このコミットが行われる以前のGoのバージョンに存在した、
-
time.Now().Sub(start time.Time)
:time.Time
型のメソッドSub
は、2つのtime.Time
インスタンス間の差を計算し、その結果をtime.Duration
型で返します。end.Sub(start)
は、start
からend
までの期間を返します。- これは、特定の処理の開始時刻と終了時刻を
time.Time
で記録し、その差を計算して処理時間を測定する際に非常に一般的なパターンです。
-
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
の初期値として負のナノ秒数を設定し、処理後に現在のナノ秒数を加算することで経過時間を計算していました。しかし、このアプローチにはいくつかの問題がありました。
time.Nanoseconds()
の非推奨化: 前述の通り、この関数はGoのtime
パッケージの進化に伴い非推奨となりました。これは、APIの安定性や一貫性の観点から、この関数を使用し続けることが望ましくないことを意味します。- 計算の複雑さ: 負の値から開始して加算するという計算方法は、直感的ではなく、バグを誘発しやすい可能性があります。
- 精度と基準点:
time.Nanoseconds()
が返すナノ秒値の具体的な基準点(例: Unixエポックからの経過時間、システム起動からの経過時間など)が明確でない場合、異なる呼び出し間で正確な時間差を計算することが困難になる可能性があります。
新しいコードでは、これらの問題を解決するために、以下のように変更されました。
start := time.Now()
parallelSleep(1)
dt := time.Now().Sub(start)
この変更により、以下の利点が得られます。
time.Now().Sub()
の利用:time.Now()
で処理開始時刻のtime.Time
インスタンスを取得し、処理終了後に再度time.Now()
を呼び出し、その結果に対してSub()
メソッドを使用することで、2つのtime.Time
インスタンス間の正確なtime.Duration
(期間)を直接取得できます。これはGo言語における時間計測の標準的かつ推奨される方法です。- 明確な意味:
start
とdt
という変数名が、それぞれ開始時刻と経過時間を明確に示しており、コードの可読性が向上します。 - 型安全性:
dt
がtime.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
パッケージの慣用的な使い方に沿ったものです。
time.Duration
との直接比較:1.3e9
(1.3 * 10^9 ナノ秒、つまり1.3秒)というマジックナンバーを直接比較する代わりに、1300*time.Millisecond
というtime.Duration
型の値と比較しています。これにより、コードの意図がより明確になり、単位の誤解を防ぐことができます。time.Millisecond
はtime.Duration
型の定数であり、1ミリ秒を表します。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
: 計算された経過時間dt
が1.3e9
(1.3 * 10^9 ナノ秒、つまり1.3秒)以上であるかをチェックしていました。これは、並列スリープが期待通りに動作せず、直列に実行された場合に発生する2秒程度の遅延を検出するための閾値です。float64(dt)/1e9
: エラーメッセージで経過時間を秒単位で表示するために、ナノ秒単位のdt
をfloat64
にキャストし、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
: 経過時間dt
(time.Duration
型)が1300*time.Millisecond
(1300ミリ秒、つまり1.3秒)以上であるかをチェックします。time.Millisecond
はtime.Duration
型の定数であり、コードの意図がより明確になります。dt.Seconds()
: エラーメッセージで経過時間を秒単位で表示するために、time.Duration
型のdt
に対してSeconds()
メソッドを呼び出します。これは、time.Duration
を秒単位のfloat64
として取得する標準的で簡潔な方法です。
この変更により、時間計測のロジックがGoの最新のAPIとベストプラクティスに準拠し、コードの可読性、正確性、および保守性が大幅に向上しました。
関連リンク
- Go CL 5454047: https://golang.org/cl/5454047
参考にした情報源リンク
- Go言語の
time
パッケージに関するドキュメント(time.Now()
,time.Sub()
,time.Duration.Seconds()
など): time.Nanoseconds()
の非推奨化とUnixNano()
への移行に関する情報:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHXGnIcAbpAKDny9eHguJMk8bRxqyjLg3DWSg5U2OYIwBQrNdbQ-NdC7Zkq8D6z3g-VM1tYgOP9Z_tjEqbTAhzal0X-3huMCBQeEGHFzAH1Ul0ytzftmaeZ_69r
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQER9JG14MflawNSI9NwU-qVqkULWUHoEswgCjuwub4fqjFi_3eLYTAsNLGw6f8732OA7WwZ0tT06dnEULMulNzb0FnNpWqYEz--uPVGGQJnYu8sOf9nWkL1J8udvjXFwW2VV_E5hEDfTWiM25LTG_o56Gv7nl3BZOFrM7LZK3mDfVsxTml84BC0YLMQtmOgX0iDidVb1hi8PzQ=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE9xWoEKu1zXhRgLiN9pLIAQKUZ8p7_lSSNOxo6gYQ7ODOweKKmFWd1xFclLWF8TSvPnOdjTdcchMurkFiXlWrKXMF6vKdPmXf2VDiluWbvpT-X9XJPj6CuZo-KNmRtr0JQrI_JO8FoF_ncTr7fhG1wcMJH
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEdkSH8YBulqkEz49UzK0Ezsy3mF3X2SVIvcNwwuVMFaHJtF0dO2_sS0Xt9EszHiKQYBelVx8sv-WZC0cka1EIH0shnOBPv-4OakTuA3mVvqM11_PANCPByR7_k_zfi9bQTqBsBGYb_BjANDEz6OaQ=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGdWWFmHS5Bg1IQNOwT8ZjII41Audzl_de1uQ8tmzgSxyivY94bbDIaTPLQcxEtbTsYzWHO65gQTVbk3Ia9LPwfAivDvfa1rrv7pavPQTsb7GEK