根据其文档,System.nanoTime返回自某个固定但任意起始时间以来的纳秒数。然而,在我尝试下面代码的所有x64机器上,都存在时间跳跃,使得固定的起始时间发生了变化。可能是我使用另一种方法(这里是currentTimeMillis)获取正确时间的方法存在缺陷。然而,测量相对时间(持续时间)的主要目的也受到了负面影响。
我遇到了这个问题,当比较不同队列与LMAX的Disruptor时,我有时会得到非常负面的延迟。在这些情况下,开始和结束时间戳是由不同的线程创建的,但延迟是在这些线程完成后计算的。
我的代码使用nanoTime获取时间,使用currentTimeMillis时间计算固定的起始时间,并在调用之间比较该起始时间。既然我必须在这里提出一个问题:这段代码有什么问题?为什么它观察到违反“固定起始时间”合同的情况?或者它没有?
现在我做了一些小改动。基本上,我在两个currentTimeMillis调用之间加入nanoTime调用的括号,以查看线程是否已被重新调度(这应该需要比currentTimeMillis分辨率更长的时间)。在这种情况下,我忽略循环周期。实际上,如果我们知道nanoTime足够快(如Ivy Bridge等新架构),我们可以将currentTimeMillis与nanoTime配对使用。
现在长时间大于10毫秒的跳跃已经消失了。相反,我们计算当每个线程距离第一个起点超过2ms时。在我测试的机器上,对于运行时间为100s,总会有接近200,000次调用之间的跳跃。正是针对这些情况,我认为currentTimeMillis或nanoTime可能不准确。
我遇到了这个问题,当比较不同队列与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 ++;
}
}
}
现在我做了一些小改动。基本上,我在两个currentTimeMillis调用之间加入nanoTime调用的括号,以查看线程是否已被重新调度(这应该需要比currentTimeMillis分辨率更长的时间)。在这种情况下,我忽略循环周期。实际上,如果我们知道nanoTime足够快(如Ivy Bridge等新架构),我们可以将currentTimeMillis与nanoTime配对使用。
现在长时间大于10毫秒的跳跃已经消失了。相反,我们计算当每个线程距离第一个起点超过2ms时。在我测试的机器上,对于运行时间为100s,总会有接近200,000次调用之间的跳跃。正是针对这些情况,我认为currentTimeMillis或nanoTime可能不准确。