[英]System.nanoTime vs System.currentTimeMillis
根據其文檔,System.nanoTime返回納秒,因為一些固定但任意的原始時間 。 但是,在所有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調用括在兩個currentTimeMillis調用之間,以查看該線程是否已被重新調度(這應該超過currentTimeMillis分辨率)。 在這種情況下,我忽略了循環周期。 實際上,如果我們知道nanoTime足夠快(就像像Ivy Bridge這樣的新架構),我們可以將currentTimeMillis括在nanoTime中。
現在長> 10ms的跳躍消失了。 相反,我們計算每個線程距離第一個原點超過2毫秒的時間。 在我測試過的機器上,對於100s的運行時間,調用之間總會有接近200,000次跳轉。 對於那些我認為currentTimeMillis或nanoTime可能不准確的情況。
如前所述,每次計算新原點意味着您會遇到錯誤。
// ______ delay _______
// v v
long origin = (long)(System.currentTimeMillis() - System.nanoTime() / 1e6);
// ^
// truncation
如果您修改程序以便計算原點差異,您會發現它非常小。 我測量的平均值約為200ns,這對於延遲時間來說是正確的。
使用乘法而不是除法(這應該沒有溢出再過幾百年)你也會發現計算的未通過等式檢查的起源數量要大得多,大約99%。 如果錯誤的原因是由於時間延遲,它們只會在延遲恰好與最后一個相同時通過。
一個更簡單的測試是累積經過一段時間后對nanoTime的調用所花費的時間,看看它是否用第一次和最后一次調用結賬:
public class SimpleTimeCoherencyTest {
public static void main(String[] args) {
final long anchorNanos = System.nanoTime();
long lastNanoTime = System.nanoTime();
long accumulatedNanos = lastNanoTime - anchorNanos;
long numCallsSinceAnchor = 1L;
for(int i = 0; i < 100; i++) {
TestRun testRun = new TestRun(accumulatedNanos, lastNanoTime);
Thread t = new Thread(testRun);
t.start();
try {
t.join();
} catch(InterruptedException ie) {}
lastNanoTime = testRun.lastNanoTime;
accumulatedNanos = testRun.accumulatedNanos;
numCallsSinceAnchor += testRun.numCallsToNanoTime;
}
System.out.println(numCallsSinceAnchor);
System.out.println(accumulatedNanos);
System.out.println(lastNanoTime - anchorNanos);
}
static class TestRun
implements Runnable {
volatile long accumulatedNanos;
volatile long lastNanoTime;
volatile long numCallsToNanoTime;
TestRun(long acc, long last) {
accumulatedNanos = acc;
lastNanoTime = last;
}
@Override
public void run() {
long lastNanos = lastNanoTime;
long currentNanos;
do {
currentNanos = System.nanoTime();
accumulatedNanos += currentNanos - lastNanos;
lastNanos = currentNanos;
numCallsToNanoTime++;
} while(currentNanos - lastNanoTime <= 100000000L);
lastNanoTime = lastNanos;
}
}
}
該測試確實表明原點是相同的(或至少錯誤是零均值)。
據我所知, System.currentTimeMillis()
的方法確實有時會跳轉,這取決於底層操作系統。 我有時會觀察到這種行為。
因此,您的代碼給我的印象是您嘗試重復獲取System.nanoTime()
和System.currentTimeMillis()
之間的偏移量。 您應該通過調用System.currentTimeMillis()
只嘗試觀察此偏移量,然后才能說System.nanoTimes()
有時會導致跳轉。
順便說一句,我不會假裝規范(javadoc描述與某個固定點相關的System.nanoTime()
)總是完美實現。 您可以查看此討論 ,其中多核CPU或CPU頻率的更改可能會對System.nanoTime()
的所需行為產生負面影響。 但有一件事是肯定的。 System.currentTimeMillis()
更容易受到任意跳轉的影響。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.