[英]Why do System.nanoTime() and System.currentTimeMillis() drift apart so rapidly?
出於診斷目的,我希望能夠在長時間運行的服務器應用程序中檢測系統時鍾的變化。 由於System.currentTimeMillis()
基於掛鍾時間而System.nanoTime()
是基於掛鍾時間獨立(*)的系統計時器,我以為我可以使用這些值之間的差異來檢測系統時間變化。
我寫了一個快速測試應用程序,看看這些值之間的差異是多么穩定,令我驚訝的是,這些值在我每秒幾毫秒的水平上立刻發散。 有幾次我看到了更快的分歧。 這是在帶有Java 6的Win7 64位桌面上。我沒有在Linux(或Solaris或MacOS)下嘗試過這個測試程序來查看它的執行情況。 對於這個應用程序的一些運行,分歧是積極的,對於某些運行它是負面的。 它似乎取決於桌面正在做什么,但很難說。
public class TimeTest {
private static final int ONE_MILLION = 1000000;
private static final int HALF_MILLION = 499999;
public static void main(String[] args) {
long start = System.nanoTime();
long base = System.currentTimeMillis() - (start / ONE_MILLION);
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Don't care if we're interrupted
}
long now = System.nanoTime();
long drift = System.currentTimeMillis() - (now / ONE_MILLION) - base;
long interval = (now - start + HALF_MILLION) / ONE_MILLION;
System.out.println("Clock drift " + drift + " ms after " + interval
+ " ms = " + (drift * 1000 / interval) + " ms/s");
}
}
}
Thread.sleep()
時間的不准確以及中斷應該與定時器漂移完全無關。
這兩個Java“系統”調用都用作測量 - 一個用於測量掛鍾時間的差異,另一個用於測量絕對間隔,因此當實時時鍾未被更改時,這些值應該更改在非常接近相同的速度,對吧? 這是Java中的錯誤或弱點還是失敗? 操作系統或硬件中是否存在阻止Java更准確的內容?
我完全期望這些獨立測量之間存在一些漂移和抖動(**),但我預計每天漂移不到一分鍾。 每秒1毫秒的漂移,如果是單調的,幾乎是90秒! 我觀察到的最壞情況可能是十倍。 每次我運行這個程序時,我都會看到第一次測量的偏差。 到目前為止,我還沒有運行該程序超過30分鍾。
由於抖動,我希望在打印的值中看到一些小的隨機性,但在程序的幾乎所有運行中,我看到差異的穩定增加,通常高達每秒3毫秒的增加,並且遠遠超過每秒3毫秒。 。
任何版本的Windows都有類似於Linux的機制,可以調整系統時鍾速度,以便慢慢使時鍾與外部時鍾源同步嗎? 這樣的事情會影響定時器,還是只影響掛鍾定時器?
(*)據我所知,在某些體系結構中, System.nanoTime()
必然會使用與System.currentTimeMillis()
相同的機制。 我也相信,假設任何現代Windows服務器都不是這樣的硬件架構是公平的。 這是一個不好的假設嗎?
(**)當然, System.currentTimeMillis()
通常會有比System.nanoTime()
更大的抖動,因為在大多數系統上它的粒度不是1毫秒。
您可能會發現有關JVM計時器的Sun / Oracle博客文章很有用。
以下是該文章中有關Windows下JVM計時器的幾個段落:
System.currentTimeMillis()
是使用GetSystemTimeAsFileTime
方法實現的,該方法基本上只讀取Windows維護的低分辨率時間值。 根據報告的信息,讀取這個全局變量自然非常快 - 大約6個周期。 無論定時器中斷如何編程,這個時間值都以恆定速率更新 - 取決於平台,這將是10ms或15ms(此值似乎與默認中斷周期相關)。
System.nanoTime()
使用QueryPerformanceCounter
/QueryPerformanceFrequency
API實現(如果可用,否則返回currentTimeMillis*10^6
)。QueryPerformanceCounter
(QPC)以不同的方式實現,具體取決於它運行的硬件。 通常,它將使用可編程間隔定時器(PIT)或ACPI電源管理定時器(PMT)或CPU級時間戳計數器(TSC)。 訪問PIT / PMT需要執行慢速I / O端口指令,因此QPC的執行時間大約為微秒。 相反,讀取TSC大約為100個時鍾周期(從芯片讀取TSC並將其轉換為基於工作頻率的時間值)。 您可以通過檢查QueryPerformanceFrequency是否返回3,579,545(即3.57MHz)的簽名值來判斷您的系統是否使用ACPI PMT。 如果您看到大約1.19Mhz的值,那么您的系統正在使用舊的8245 PIT芯片。 否則,您應該看到一個大約相當於CPU頻率的值(模數可能有效的任何速度限制或電源管理。)
我不確定這實際上有多大幫助。 但這是Windows / Intel / AMD / Java世界中一個積極變化的領域。 在幾個(至少10年)內,對准確和精確的時間測量的需求已經很明顯。 英特爾和AMD都通過改變TSC的工作方式做出了回應。 兩家公司現在都有一種叫做Invariant-TSC和/或Constant-TSC的東西 。
檢查CPU內核的rdtsc精度 。 引用osgx(誰是指英特爾手冊)。
“16.11.1不變的TSC
較新處理器中的時間戳計數器可以支持增強,稱為不變TSC。 處理器對不變TSC的支持由PUID.80000007H:EDX [8]指示。
不變的TSC將在所有ACPI P-,C-中以恆定速率運行。 和T狀態。 這是向前發展的建築行為。 在具有不變TSC支持的處理器上,OS可以將TSC用於掛鍾計時器服務(而不是ACPI或HPET計時器)。 TSC讀取效率更高,並且不會產生與環轉換或訪問平台資源相關的開銷。“
另見http://www.citihub.com/requesting-timestamp-in-applications/ 。 引自作者
如果CPUID 8000_0007.edx [8] = 1,則確保TSC速率在所有P狀態,C狀態和停止授權轉換(例如STPCLK限制)之間保持不變; 因此,TSC適合用作時間源。
處理器對不變TSC的支持由CPUID.80000007H:EDX [8]指示。 不變的TSC將在所有ACPI P-,C-中以恆定速率運行。 和T狀態。 這是向前發展的建築行為。 在具有不變TSC支持的處理器上,OS可以將TSC用於掛鍾計時器服務(而不是ACPI或HPET計時器)。 TSC讀取效率更高,並且不會產生與環轉換或訪問平台資源相關的開銷。“
現在非常重要的一點是,最新的JVM似乎利用了新的可靠TSC機制。 沒有多少在線顯示這一點。 不過,請查看http://code.google.com/p/disruptor/wiki/PerformanceResults 。
“為了測量延遲,我們采用三級流水線並生成低於飽和度的事件。這是通過在注入下一個事件並重復注入5000萬次之前等待1微秒來實現的。要達到這個精度水平,有必要使用來自CPU的時間戳計數器。我們選擇具有不變TSC的CPU,因為較舊的處理器由於省電和睡眠狀態而受到頻率變化的影響.Intel Nehalem和后來的處理器使用不變的TSC,可以通過運行的最新Oracle JVM訪問Ubuntu 11.04。此測試沒有使用CPU綁定“
請注意,“Disruptor”的作者與處理Azul和其他JVM的人有密切聯系。
另請參閱“幕后的Java飛行記錄”。 本演示文稿提到了新的不變TSC指令。
System.currentTimeMillis()
和System.nanoTime()
不一定由同一硬件提供。 由GetSystemTimeAsFileTime()
支持的System.currentTimeMillis()
具有100ns分辨率元素。 它的來源是系統計時器。 System.nanoTime()
由系統的高性能計數器支持。 提供此計數器有各種不同的硬件。 因此,其分辨率因底層硬件而異。
在任何情況下都不能假設這兩個源是同相的。 相互測量兩個值將公開不同的運行速度。 如果將System.currentTimeMillis()
的更新視為真正的時間進度,則System.nanoTime()
的輸出有時可能更慢,有時更快,也會變化。
必須進行仔細校准才能鎖定這兩個時間源。
可以在Windows時間戳項目中找到有關這兩個時間源之間關系的更詳細說明。
任何版本的Windows都有類似於Linux的機制,可以調整系統時鍾速度,以便慢慢使時鍾與外部時鍾源同步嗎? 這樣的事情會影響定時器,還是只影響掛鍾定時器?
Windows時間戳項目可以滿足您的要求。 據我所知,它只會影響掛鍾計時器。
“返回最精確的可用系統計時器的當前值,以納秒為單位。
“此方法只能用於測量經過的時間,與系統或掛鍾時間的任何其他概念無關。返回的值表示納秒,因為某些固定但是任意時間(可能在將來,因此值可能為負)這種方法提供納秒精度,但不一定是納秒精度。不保證值的變化頻率。連續調用的差異超過大約292年(2 ** 63納秒)將無法准確計算由於數值而導致的經過時間溢出。”
請注意,它表示“精確”,而不是“准確”。
它不是“Java中的bug”或任何“bug”。 這是一個定義。 JVM開發人員四處尋找系統中最快的時鍾/定時器並使用它。 如果那與系統時鍾鎖定步驟那么好,但如果不是,那就是cookie崩潰的方式。 比如說,計算機系統將具有准確的系統時鍾,但內部具有更高速率的定時器,這與CPU時鍾速率或某些此類速率相關,這完全合情合理。 由於時鍾頻率通常是變化的以使功耗最小化,因此該內部定時器的增量速率會變化。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.