System.nanoTime()は完全に役に立たないのですか?


153

ブログ投稿「JavaでのSystem.nanoTime()の注意」に記載されているように、x86システムでは、JavaのSystem.nanoTime()はCPU固有のカウンターを使用して時間値を返します。次に、通話時間を測定するために使用する次のケースを考えます。

long time1= System.nanoTime();
foo();
long time2 = System.nanoTime();
long timeSpent = time2-time1;

マルチコアシステムでは、time1を測定した後、スレッドが別のプロセッサにスケジュールされ、そのカウンタが以前のCPUのカウンタよりも小さい場合があります。したがって、time1 より小さい time2の値を取得できます。したがって、timeSpentで負の値を取得します。

この場合を考えると、今のところSystem.nanotimeはほとんど役に立たないのではないでしょうか。

システム時刻を変更してもナノタイムには影響しないことを知っています。それは私が上で説明した問題ではありません。問題は、各CPUがオンになって以来、異なるカウンターを保持することです。このカウンタは、最初のCPUと比較して2番目のCPUで低くなることがあります。スレッドは、time1を取得した後、OSによって2番目のCPUにスケジュールされる可能性があるため、timeSpentの値は正しくなく、負の値になる場合もあります。


2
答えはありませんが、同意します。多分それはJVMのバグと考えられるべきです。
アーロン・ディグラ2009

2
その投稿は正しくなく、TSCを使用しないと動作が遅くなりますが、バグに対処する必要があります 。
bestss

1
そしてもちろん、CPUがセッションの途中で表示される仮想マシンで実行することもできます:D
限定的な償い

回答:


206

この回答は、当時のオペレーティングシステムで実行されていた当時のSun JDKが実際に何をしたかという観点から2011年に書かれました。それはずっと前だった!leventovの答えは、より最新の視点を提供します。

その投稿は間違っており、nanoTime安全です。この投稿には、Sunのリアルタイムおよび並行性の男であるDavid Holmesのブログ投稿にリンクするコメントがあります。それは言う:

System.nanoTime()は、QueryPerformanceCounter / QueryPerformanceFrequency APIを使用して実装されます[...] QPCによって使用されるデフォルトのメカニズムは、ハードウェアアブストラクションレイヤー(HAL)によって決定されます[...]このデフォルトは、ハードウェア全体だけでなく、OS全体でも変更されますバージョン。たとえば、Windows XP Service Pack 2は、TSCがSMPシステムの異なるプロセッサで同期されていないという問題と、その頻度が原因で、プロセッサのタイムスタンプカウンタ(TSC)ではなく電源管理タイマ(PMTimer)を使用するように変更しました。電源管理設定に基づいて変化する可能性があります(そのため、経過時間との関係も異なります)。

したがって、Windowsでは、これ WinXP SP2までは問題でし、現在は問題ではありません。

他のプラットフォームについて説明しているパートII(またはそれ以上)は見つかりませんが、その記事には、Linuxが同じ方法で同じ問題を検出して解決したというコメントが含まれており、clock_gettime(CLOCK_REALTIME)のFAQへのリンクがあります、それは言う:

  1. clock_gettime(CLOCK_REALTIME)はすべてのプロセッサ/コアで一貫していますか?(アーチは重要ですか?たとえば、ppc、arm、x86、amd64、sparc)。

それはすべきか、それがバグだらけと考えられています。

ただし、x86 / x86_64では、非同期または可変周波数TSCが時間の不整合を引き起こすことを確認できます。2.4カーネルではこれに対する保護は実際にありませんでした。また、2.6カーネルの初期はここでもあまりうまくいきませんでした。2.6.18以降では、これを検出するロジックが改善され、通常は安全なクロックソースにフォールバックします。

ppcは常に同期されたタイムベースを持っているので、それは問題になりません。

したがって、ホームズのリンクがそのnanoTime呼び出しを暗示するものとして読み取ることができる場合clock_gettime(CLOCK_REALTIME)、x86のカーネル2.6.18以降は常にPowerPCで安全です(Intelとは異なり、IBMとMotorolaはマイクロプロセッサの設計方法を実際に知っているため)。

悲しいことに、SPARCやSolarisについての言及はありません。そしてもちろん、IBM JVMが何をするのかはわかりません。しかし、最新のWindowsおよびLinux上のSun JVMはこれを正しく実現しています。

編集:この回答は、引用した情報源に基づいています。しかし、それでも実際には完全に間違っているのではないかと心配しています。いくつかのより最新の情報は本当に価値があります。Linuxの時計に関する4年前の新しい記事へのリンクを見つけました。


13
WinXP SP2でさえも問題があるようです。元のコードサンプルを実行void foo() { Thread.sleep(40); }し、シングルAthlon 64 X2 4200+プロセッサを使用して負の時間(-380ミリ秒!)を取得した
Luke Usherwood

私はこれについて更新されているとは思いません、wrt。Linux、BSD、またはその他のプラットフォームでの動作
Tomer Gabel

6
良い答えは、このトピックのより最近の探査へのリンクを追加する必要がありますshipilev.net/blog/2014/nanotrusting-nanotime
Nitsan Wakart

1
@SOFe:ああ、それは残念です。幸いなことに、それはウェブアーカイブにあります。現在のバージョンを追跡できるかどうかを確認します。
トムアンダーソン

1
注:OpenJDKのはOpenJDKの8u192までスペックアップに掲げていなかった、参照bugs.openjdk.java.net/browse/JDK-8184271を。少なくともOpenJDK 8またはOpenJDK 11+の最新バージョンを使用してください。
leventov

35

私は少し検索しましたが、もし1つが奇妙なものであれば、それは役に立たないと考えられるかもしれません...特定の状況では...それは、要件がどれほど時間に敏感であるかに依存します...

Java Sunサイトからこの引用をチェックしてください:

リアルタイムクロックとSystem.nanoTime()は、どちらも同じシステムコール、つまり同じクロックに基づいています。

Java RTSでは、すべての時間ベースのAPI(たとえば、タイマー、定期的なスレッド、デッドラインモニタリングなど)は高解像度タイマーに基づいています。また、リアルタイムの優先順位とともに、リアルタイムの制約に対して適切なコードが適切なタイミングで実行されるようにすることができます。対照的に、通常のJava SE APIは、高解像度の時間を処理できるいくつかのメソッドのみを提供し、特定の時間に実行される保証はありません。コードのさまざまなポイント間でSystem.nanoTime()を使用して経過時間の測定を実行することは、常に正確である必要があります。

Javaには、nanoTime()メソッドに関する警告もあります。

この方法は、経過時間を測定するためにのみ使用でき、システムまたは壁時計時間の他の概念とは関係ありません。返される値は、固定された任意の時間からのナノ秒を表します(おそらく将来、値が負になる可能性があります)。この方法はナノ秒の精度を提供しますが、必ずしもナノ秒の精度ではありません。値の変更頻度については保証されません。約292.3年(2 63 ナノ秒)を超える連続する呼び出しの差は、数値のオーバーフローのために経過時間を正確に計算しません。

導き出せる唯一の結論は、nanoTime()は正確な値として信頼できないということです。したがって、ほんの数ナノ秒離れた時間を測定する必要がない場合、結果の戻り値が負の値であっても、この方法で十分です。ただし、より高い精度が必要な場合は、JAVA RTSの使用を推奨しているようです。

だからあなたの質問に答えるには... nanoTime()は役に立たないわけではありません...あらゆる状況で使用するのに最も賢明な方法ではありません。


3
>このメソッドは、結果の戻り値が負であっても十分です。私はこれを取得しません、timespentの値が負の場合、foo()にかかる時間を測定するのにどのように役立ちますか?
pdeva

3
あなたが心配しているのは差の絶対値だけなので、それは大丈夫です。つまり、測定値が時間tであり、t = t2-t1である場合、| t | ....値が負の場合、マルチコアの問題があっても、影響はほとんどありません。とにかくナノ秒。
メゾイド2009

3
@Aaronをバックアップするには、t2とt1の両方が負になる可能性がありますが、(t2-t1)が負であってはなりません。
jfs 2009

2
aaron:それがまさに私のポイントです。t2-t1が負になることはありません。そうしないとバグが発生します。
pdeva 2009

12
@pdeva-しかし、あなたはドキュメントが言うことを誤解しています。あなたは非問題を提起しています。「0」と見なされるある時点があります。nanoTime()によって返される値は、その時間に対して正確です。その単調に増加するタイムライン。そのタイムラインのマイナス部分から一連の数値を取得しているだけかもしれません。-100-99-98(実際には、明らかにはるかに大きな値)。彼らは正しい方向に進んでいる(増加している)ので、ここで問題はありません。
ToolmakerSteve

18

議論する必要はありません。ただソースを使用してください。ここでは、Linux用のSE 6について、独自の結論を導きます。

jlong os::javaTimeMillis() {
  timeval time;
  int status = gettimeofday(&time, NULL);
  assert(status != -1, "linux error");
  return jlong(time.tv_sec) * 1000  +  jlong(time.tv_usec / 1000);
}


jlong os::javaTimeNanos() {
  if (Linux::supports_monotonic_clock()) {
    struct timespec tp;
    int status = Linux::clock_gettime(CLOCK_MONOTONIC, &tp);
    assert(status == 0, "gettime error");
    jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
    return result;
  } else {
    timeval time;
    int status = gettimeofday(&time, NULL);
    assert(status != -1, "linux error");
    jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
    return 1000 * usecs;
  }
}

7
これは、使用するAPIの機能を知っている場合にのみ役立ちます。使用されるAPIはオペレーティングシステムによって実装されます。このコードは正しいです。使用されるAPIの仕様(clock_gettime / gettimeofday)ですが、他の人が指摘しているように、一部の最新でないオペレーティングシステムにはバグのある実装があります。
Blaisorblade、2012

18

Java 7以降System.nanoTime()、JDK仕様により安全であることが保証されています。 System.nanoTime()のJavadocは、JVM内で(つまり、すべてのスレッドにわたって)観測されたすべての呼び出しが単調であることを明確にしています。

返される値は、固定された任意の起点時間からのナノ秒を表します(おそらく将来、値が負になる可能性があります)。Java仮想マシンのインスタンスでのこのメソッドのすべての呼び出しで同じオリジンが使用されます。他の仮想マシンインスタンスは異なるオリジンを使用する可能性があります。

JVM / JDK実装は、基礎となるOSユーティリティが呼び出されたときに観察される可能性がある不整合(たとえば、トムアンダーソンの回答で言及されているもの)を解決する責任があります。

この質問(2009年から2012年に書かれた)に対する他の古い回答の大部分は、おそらくJava 5またはJava 6に関連していたが、Javaの最新バージョンには関連しなくなったFUDを表しています。

ただし、JDK nanoTime()の安全性は保証されていますが、OpenJDKにはいくつかのバグがあり、特定のプラットフォームまたは特定の状況(JDK-8040140JDK-8184271など)でこの保証を守らないことが指摘されています。現在、OpenJDKには未解決の(既知の)バグはありませんnanoTime()が、そのような新しいバグの発見や、OpenJDKの新しいリリースでの退行は、誰にもショックを与えるものではありません。

そのことを念頭に置いて、時限ブロッキング、インターバル待機、タイムアウトなどに使用nanoTime()するコードは、例外をスローするのではなく、負の時間差(タイムアウト)をゼロとして処理する必要があります。それは内のすべてのクラスのすべての時限待機メソッドの動作と一致しているので、この練習も好ましいjava.util.concurrent.*、例えばSemaphore.tryAcquire()Lock.tryLock()BlockingQueue.poll()、など

それにもかかわらず、nanoTime()まだへなど、時限ブロッキング、間隔待ち、タイムアウトを実装するために好まれるべきでcurrentTimeMillis()、後者は「時間は後方に行く」現象(例えば、サーバの時刻補正による)の対象となるので、つまりはcurrentTimeMillis()時間間隔を測定するには適していませんまったく。詳細については、この回答を参照してください。

nanoTime()コード実行時間の測定に直接使用するのではなく、専用のベンチマークフレームワークとプロファイラーを使用することをお勧めます。たとえば、ウォールクロックプロファイリングモードのJMH非同期プロファイラーです


10

免責事項:私はこのライブラリの開発者です

あなたはこれが好きかもしれません:

http://juliusdavies.ca/nanotime/

ただし、DLLまたはUnixの.so(共有オブジェクト)ファイルを現在のユーザーのホームディレクトリにコピーして、JNIを呼び出せるようにします。

いくつかの背景情報は私のサイトにあります:

http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html


@ジュリアスあなたのサイトからライブラリを削除しましたか?
Nitin Dandriyal

6

LinuxはCPU間の不一致を修正しますが、Windowsは修正しません。System.nanoTime()の精度は約1マイクロ秒に過ぎないと想定することをお勧めします。より長いタイミングを取得する簡単な方法は、foo()を1000回以上呼び出し、その時間を1000で除算することです。


2
リファレンス(LinuxおよびWindowsでの動作)を提供していただけますか?
jfs 2009

残念ながら、提案された方法は、+ /-100msのウォールクロック更新スロットに入る各イベントが1秒未満の操作に対してゼロを返すことが多いため、一般に非常に不正確です。継続時間がゼロの9つの操作の合計は、まあ、ゼロであり、9で割ると...ゼロになります。逆に、System.nanoTime()を使用すると、比較的正確な(ゼロ以外の)イベント期間が提供されます。これを合計してイベント数で割ると、非常に正確な平均が得られます。
ダレルティーグ

@DarrellTeagueは、1000個のイベントを合計して合計することは、エンドツーエンドの時間と同じです。
Peter Lawrey 2017年

@DarrellTeague System.nanoTime()は、ほとんどのシステムで1マイクロ秒以上(100,000マイクロ秒ではない)の精度です。多くの操作の平均化は、数マイクロ秒に到達したときにのみ関連し、特定のシステムでのみ有効です。
Peter Lawrey 2017年

1
「合計」イベントで使用される言語について混乱があったため、お詫びします。はい、たとえば1000秒未満の操作の開始時に時刻がマークされている場合、それらは実行され、次に終了時にマークが付けられて分割されます。これは、開発中のシステムで機能して、所定の期間の適切な概算を取得します。イベント。
ダレルティーグ

5

絶対に役に立たない。タイミング愛好家はマルチコアの問題を正しく指摘していますが、実際のアプリケーションでは、currentTimeMillis()よりも根本的に優れていることがよくあります。

フレームの更新でグラフィックスの位置を計算するとき、nanoTime()はプログラムで非常に滑らかな動きにつながります。

そして、私はマルチコアマシンでのみテストします。


5

System.nanoTime()を使用て負の経過時間が報告されるのを見ました。明確にするために、問題のコードは次のとおりです。

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

変数「elapsedNanos」には負の値がありました。(中間の呼び出しにも293年もかからなかったことは確かです。これはlongに格納されたnanosのオーバーフローポイントです。)

これは、AIXを実行しているIBM P690(マルチコア)ハードウェアでIBM v1.5 JRE 64ビットを使用して発生しました。このエラーが一度だけ発生するのを見たので、それは非常にまれなようです。原因はわかりません-ハードウェア固有の問題ですか、JVMの欠陥ですか-わかりません。また、nanoTime()の精度が一般的にどのような意味を持つのかもわかりません。

元の質問に答えるために、nanoTimeは役に立たないと思います。サブミリ秒のタイミングを提供しますが、実際に(理論的ではなく)不正確であり、考慮する必要があるリスクがあります。


悲しいかな、OS /ハードウェアの問題のようです。ドキュメントには、コア値が負の値になる可能性があるが、(大きい負の値から小さい負の値)は正の値である必要があると記載されています。実際、同じスレッド内では、nanoTime()呼び出しは常に正または負の値を返す必要があると想定しています。これをさまざまなUnixおよびWindowsシステムで長年にわたって見たことはありませんが、特にハードウェア/ OSがこの一見アトミックな操作をプロセッサ間で分割している場合は可能です。
ダレルティーグ

@BasilVandegriendそれはどこにもバグではありません。ドキュメントによると、例の2番目のSystem.nanoTime()が別のCPUで実行されることはめったになく、そのCPUで計算されたnanoTime値は、最初のCPUで計算された値よりも低い場合があります。したがって、elapsedNanosの-ve値は可能です
エンドレス

2

これは、Windows XPおよびJRE 1.5.0_06を実行しているCore 2 Duoの問題ではないようです。

3つのスレッドを使用したテストでは、System.nanoTime()が逆戻りすることはありません。プロセッサは両方ともビジーであり、スレッドは時々スリープ状態になり、移動するスレッドを引き起こします。

[編集]私はそれが物理的に別個のプロセッサでのみ発生することを推測します、すなわち、カウンターは同じダイ上の複数のコアに対して同期されます。


2
おそらく常に発生するわけではありませんが、nanotime()の実装方法により、可能性は常に存在します。
pdeva 2009

私はそれが物理的に別個のプロセッサでのみ発生する、つまり、カウンターが同じダイ上の複数のコアに対して同期していると思います。
starblue

それでも、特定の実装、IIRCによって異なります。しかし、それはOSが処理することになっているものです。
Blaisorblade、2012

1
同じx86プロセッサーの複数のコアのRDTSCカウンターは必ずしも同期されていません。一部の最新システムでは、異なるコアを異なる速度で実行できます。
ジュール

2

いいえ、違います...それはCPUに依存します。高精度イベントタイマーをチェックして、CPUによって処理がどのように/なぜ異なるのかを確認してください。

基本的には、Javaのソースを読んで、バージョンが関数で何をしているのかを確認し、それがCPUに対して機能する場合は、それを実行します。

IBMは、これをパフォーマンスベンチマークに使用することを提案しています(2008年の投稿ですが、更新されています)。


すべての実装で動作が定義されているので、「注意が必要です」。
デビッドシュミット

2

ピーター・ローリーが良い答えを出しているのと本質的に同じ議論にリンクしています。 System.nanoTime()を使用して負の経過時間を取得するのはなぜですか?

多くの人々は、Java System.nanoTime()で負の時間を返す可能性があると述べました。他の人がすでに言ったことを繰り返し申し訳ありません。

  1. nanoTime()はクロックではなく、CPUサイクルカウンターです。
  2. 戻り値は、時間のように見えるように頻度で除算されます。
  3. CPU周波数が変動する可能性があります。
  4. スレッドが別のCPUでスケジュールされている場合、nanoTime()を取得する可能性があり、その結果、マイナスの差が生じます。それは論理的です。CPU間のカウンタは同期されません。
  5. 多くの場合、非常に誤解を招く結果が得られる可能性がありますが、デルタは負ではないため、判別できません。それについて考えてください。
  6. (未確認)命令を並べ替えると、同じCPUでも悪影響が出る可能性があると思います。これを防ぐには、命令をシリアル化するメモリバリアを呼び出す必要があります。

System.nanoTime()が実行された場所でcoreIDを返したとしたら、すばらしいでしょう。


1
3.と5.を除くすべての点が間違っています。1. nanoTime()はCPUサイクルカウンターではなく、ナノ時間です。2. nanoTime値が生成される方法はプラットフォーム固有です。4.いいえ、nanoTime()の仕様によると、差が負になることはありません。OpenJDKにnanoTime()に関するバグがなく、現時点で未解決の既知のバグがないと仮定します。6. nanoTimeの呼び出しは、ネイティブメソッドであり、JVMはプログラムの順序を尊重するため、スレッド内で並べ替えることはできません。JVMはネイティブメソッド呼び出しの順序を変更することはありません。これは、JVMの内部で何が発生するかがわからないため、そのような順序変更が安全であることを証明できないためです。
leventov 2019

5. nanoTime()の結果については、実際には誤解を招く可能性がありますが、この問題の他のポイントで説明されている理由ではありません。しかし、ここで提示された理由のためではなく、shipilev.net
2014

皮肉なことに、6。に関しては、特に並べ替えが原因でOpenJDKにバグがありました:bugs.openjdk.java.net/browse/JDK-8184271。OpenJDKでは、nanoTime()は組み込みであり、並べ替えが許可されていましたが、これはバグでした。
leventov

@ leventov、nanotime()は安全に使用できますか?つまり、負の値を返すことはできず、時間が経過する限り正確です。問題に満ちたAPI関数を公開する意味がわかりません。この投稿は証拠であり、2009年に開始され、2019年にもコメントされています。ミッションクリティカルなものについては、Symmetricom
Vortex

1
#4は、値ではなく負のについて述べています。「nanoTime()を取得する可能性があり、結果として負の差が生じます。」
leventov

1

Javaはクロスプラットフォームであり、nanoTimeはプラットフォームに依存しています。Javaを使用する場合-nanoTimeを使用しない場合。この関数を使用して、さまざまなjvm実装全体で実際のバグを見つけました。


0

Java 5のドキュメントでも、同じ方法でこのメソッドを使用することを推奨しています。

この方法は経過時間を測定するためにのみ使用でき、システムまたは壁時計時間の他の概念とは関係ありません。

Java 5 APIドキュメント


0

また、System.currentTimeMillies()システムクロックを変更すると変更されますが、変更されSystem.nanoTime()ないため、期間を測定する方が安全です。


-3

nanoTimeタイミングについては非常に安全ではありません。私は私の基本的な素数性テストアルゴリズムで試してみましたが、同じ入力に対して文字通り1秒離れた回答が得られました。そのばかげた方法を使用しないでください。時間ミリを取得するよりも正確で正確ですが、ほど悪くないものが必要ですnanoTime


ソースやより良い説明がないと、このコメントは
役に立ちません
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.