そのドキュメントによると、 System.nanoTime は 、固定されているが任意の起点 time からナノ秒を返します。ただし、以下のコードを試したすべての x64 マシンで、タイム ジャンプが発生し、固定された起点が移動しました。別の方法 (ここでは currentTimeMillis) を使用して正しい時刻を取得する方法には、何らかの欠陥がある可能性があります。ただし、相対時間 (期間) を測定するという主な目的にも悪影響があります。
さまざまなキューを LMAX の Disruptor と比較すると、レイテンシが非常にマイナスになることがあり、レイテンシを測定しようとしてこの問題に遭遇しました。このような場合、開始と終了のタイムスタンプは異なるスレッドによって作成されましたが、レイテンシはそれらのスレッドが終了した後に計算されました。
ここでの私のコードは、nanoTime を使用して時間がかかり、currentTimeMillis 時間で固定起点を計算し、呼び出し間でその起点を比較します。ここで質問しなければならないので、このコードの何が問題なのですか? 原産地固定契約の違反を観察するのはなぜですか? それともそうではありませんか?
import java.text.*;
/**
* test coherency between {@link System#currentTimeMillis()} and {@link System#nanoTime()}
*/
public class TimeCoherencyTest {
static final int MAX_THREADS = Math.max( 1, Runtime.getRuntime().availableProcessors() - 1);
static final long RUNTIME_NS = 1000000000L * 100;
static final long BIG_OFFSET_MS = 2;
static long startNanos;
static long firstNanoOrigin;
static {
initNanos();
}
private static void initNanos() {
long millisBefore = System.currentTimeMillis();
long millisAfter;
do {
startNanos = System.nanoTime();
millisAfter = System.currentTimeMillis();
} while ( millisAfter != millisBefore);
firstNanoOrigin = ( long) ( millisAfter - ( startNanos / 1e6));
}
static NumberFormat lnf = DecimalFormat.getNumberInstance();
static {
lnf.setMaximumFractionDigits( 3);
lnf.setGroupingUsed( true);
};
static class TimeCoherency {
long firstOrigin;
long lastOrigin;
long numMismatchToLast = 0;
long numMismatchToFirst = 0;
long numMismatchToFirstBig = 0;
long numChecks = 0;
public TimeCoherency( long firstNanoOrigin) {
firstOrigin = firstNanoOrigin;
lastOrigin = firstOrigin;
}
}
public static void main( String[] args) {
Thread[] threads = new Thread[ MAX_THREADS];
for ( int i = 0; i < MAX_THREADS; i++) {
final int fi = i;
final TimeCoherency tc = new TimeCoherency( firstNanoOrigin);
threads[ i] = new Thread() {
@Override
public void run() {
long start = getNow( tc);
long firstOrigin = tc.lastOrigin; // get the first origin for this thread
System.out.println( "Thread " + fi + " started at " + lnf.format( start) + " ns");
long nruns = 0;
while ( getNow( tc) < RUNTIME_NS) {
nruns++;
}
final long runTimeNS = getNow( tc) - start;
final long originDrift = tc.lastOrigin - firstOrigin;
nruns += 3; // account for start and end call and the one that ends the loop
final long skipped = nruns - tc.numChecks;
System.out.println( "Thread " + fi + " finished after " + lnf.format( nruns) + " runs in " + lnf.format( runTimeNS) + " ns (" + lnf.format( ( double) runTimeNS / nruns) + " ns/call) with"
+ "\n\t" + lnf.format( tc.numMismatchToFirst) + " different from first origin (" + lnf.format( 100.0 * tc.numMismatchToFirst / nruns) + "%)"
+ "\n\t" + lnf.format( tc.numMismatchToLast) + " jumps from last origin (" + lnf.format( 100.0 * tc.numMismatchToLast / nruns) + "%)"
+ "\n\t" + lnf.format( tc.numMismatchToFirstBig) + " different from first origin by more than " + BIG_OFFSET_MS + " ms"
+ " (" + lnf.format( 100.0 * tc.numMismatchToFirstBig / nruns) + "%)"
+ "\n\t" + "total drift: " + lnf.format( originDrift) + " ms, " + lnf.format( skipped) + " skipped (" + lnf.format( 100.0 * skipped / nruns) + " %)");
}};
threads[ i].start();
}
try {
for ( Thread thread : threads) {
thread.join();
}
} catch ( InterruptedException ie) {};
}
public static long getNow( TimeCoherency coherency) {
long millisBefore = System.currentTimeMillis();
long now = System.nanoTime();
if ( coherency != null) {
checkOffset( now, millisBefore, coherency);
}
return now - startNanos;
}
private static void checkOffset( long nanoTime, long millisBefore, TimeCoherency tc) {
long millisAfter = System.currentTimeMillis();
if ( millisBefore != millisAfter) {
// disregard since thread may have slept between calls
return;
}
tc.numChecks++;
long nanoMillis = ( long) ( nanoTime / 1e6);
long nanoOrigin = millisAfter - nanoMillis;
long oldOrigin = tc.lastOrigin;
if ( oldOrigin != nanoOrigin) {
tc.lastOrigin = nanoOrigin;
tc.numMismatchToLast++;
}
if ( tc.firstOrigin != nanoOrigin) {
tc.numMismatchToFirst++;
}
if ( Math.abs( tc.firstOrigin - nanoOrigin) > BIG_OFFSET_MS) {
tc.numMismatchToFirstBig ++;
}
}
}
今、私はいくつかの小さな変更を加えました。基本的に、nanoTime 呼び出しを 2 つの currentTimeMillis 呼び出しの間にまとめて、スレッドが再スケジュールされているかどうかを確認します (currentTimeMillis の解像度よりも長くかかるはずです)。この場合、ループ サイクルは無視します。実際、nanoTime が十分に高速であることがわかっている場合 (Ivy Bridge などの新しいアーキテクチャのように)、currentTimeMillis を nanoTime で囲むことができます。
これで、10 ミリ秒を超える長いジャンプはなくなりました。代わりに、スレッドごとに最初のオリジンから 2 ミリ秒以上離れたときにカウントします。私がテストしたマシンでは、100 秒の実行時間で、呼び出しの間に常に 200.000 近くのジャンプがあります。currentTimeMillis または nanoTime のいずれかが不正確であると思われるのは、このような場合です。