java - Hvorfor glider System.nanoTime () og System.currentTimeMillis () fra hinanden så hurtigt?

Indlæg af Hanne Mølgaard Plasc

Problem



Til diagnostiske formål vil jeg være i stand til at registrere ændringer i systemets klokkeslæt i et langvarigt serverprogram. Siden System.currentTimeMillis() er baseret på væguretiden og System.nanoTime() er baseret på en systemtimer, der er uafhængig (*) af vægurtid, tænkte jeg, at jeg kunne bruge ændringer i forskellen mellem disse værdier for at registrere system tidsændringer.


Jeg skrev en hurtig test app for at se, hvor stabil forskellen mellem disse værdier er, og til min overraskelse afviger værdierne straks for mig på niveauet af flere millisekunder per sekund. Et par gange så jeg meget hurtigere afvigelser. Dette er på et Win7 64-bit desktop med Java 6. Jeg har ikke prøvet dette testprogram under Linux (eller Solaris eller MacOS) for at se, hvordan det fungerer. For nogle kørsler af denne app er divergensen positiv, for nogle kører det er negativt. Det ser ud til at afhænge af, hvad der mere skrivebordet laver, men det er svært at sige.


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");
    }
  }
}


Unøjagtigheder med Thread.sleep() tid samt afbrydelser bør være helt irrelevante for timer drift.


Begge disse Java 'System' -opkald er beregnet til brug som måling - en til måling af forskelle i væguretiden og den anden for at måle absolutte intervaller, så når realtidsuret ikke ændres, skal disse værdier ændres i meget tæt på samme hastighed, ikke? Er det en fejl eller en svaghed eller en fejl i Java? Er der noget i OS eller hardware, der forhindrer Java i at være mere præcis?


Jeg forventer fuldt ud drift og jitter (**) mellem disse uafhængige målinger, men jeg forventede langt mindre end et minuts drift. 1 msek per sekund af drift, hvis monotonisk, er næsten 90 sekunder! Min værste tilfælde observerede drift var måske ti gange det. Hver gang jeg kører dette program ser jeg drift på den allerførste måling. Indtil videre har jeg ikke kørt programmet i mere end ca. 30 minutter.


Jeg forventer at se lidt lille tilfældighed i de trykte værdier på grund af jitter, men i næsten alle kørsler af programmet ser jeg en stabil stigning i forskellen, ofte så meget som 3 msec per sekund af stigning og et par gange meget mere end det .


Har nogen version af Windows en mekanisme svarende til Linux, der justerer systemets urthastighed for langsomt at bringe klokkeslættet til synkronisering med den eksterne urkilde? Ville en sådan ting påvirke både timere eller kun væguretiden?


(*) Jeg forstår, at System.nanoTime() på nogle arkitekturer nødvendigvis skal bruge samme mekanisme som System.currentTimeMillis(). Jeg tror også, det er rimeligt at antage, at en moderne Windows-server ikke er en sådan hardwarearkitektur. Er dette en dårlig antagelse?


(**) Selvfølgelig vil System.currentTimeMillis() normalt have en meget større jitter end System.nanoTime(), da dens granularitet ikke er 1 msek på de fleste systemer.

Bedste reference


Du kan måske finde denne Sun/Oracle blog post om JVM timers at være af interesse. [23]


Her er et par stykker fra den artikel om JVM timers under Windows:



  System.currentTimeMillis() implementeres ved hjælp af metoden GetSystemTimeAsFileTime, som i det væsentlige blot læser den daglige værdi med lav opløsning, som Windows opretholder. At læse denne globale variabel er naturligvis meget hurtig - omkring 6 cyklusser ifølge rapporterede oplysninger. Denne tidssvarende værdi opdateres med konstant hastighed, uanset hvordan timerafbrydelsen er programmeret - afhængigt af platformen vil dette enten være 10ms eller 15ms (denne værdi synes at være bundet til standardafbrydelsesperioden).

  
  System.nanoTime() implementeres ved hjælp af API'en QueryPerformanceCounter/QueryPerformanceFrequency (hvis tilgængelig, ellers returnerer den currentTimeMillis*10^6). QueryPerformanceCounter (QPC) implementeres på forskellige måder afhængigt af den hardware, den kører. Typisk vil den enten bruge den programmerbare interval-timer (PIT) eller ACPI Power Management Timer (PMT) eller CPU-niveau tidsstempel-tæller (TSC). Adgang til PIT/PMT kræver udførelse af langsomme I/O-portinstruktioner, og som følge heraf er udførelsestiden for QPC i størrelsesordenen af ​​mikrosekunder. I modsætning hertil er TSC i rækkefølge af 100 clock cykler (for at læse TSC fra chippen og konvertere den til en tidsværdi baseret på driftsfrekvensen). Du kan se om dit system bruger ACPI PMT ved at kontrollere, om QueryPerformanceFrequency returnerer signaturværdien på 3.579.545 (dvs. 3.57MHz) .Hvis du ser en værdi omkring 1,19Mhz så bruger dit system den gamle 8245 PIT-chip. Ellers bør du se en værdi, der svarer til din CPU-frekvens (modulo enhver hastighedsregulering eller strømstyring, der kan være i kraft.)


Andre referencer 1


Jeg er ikke sikker på, hvor meget dette rent faktisk vil hjælpe. Men dette er et område med aktiv forandring i Windows/Intel/AMD/Java verdenen. Behovet for nøjagtig og præcis tidsmåling har været tydelig i flere (mindst 10) år. Både Intel og AMD har reageret ved at ændre, hvordan TSC fungerer. Begge virksomheder har nu noget, der hedder Invariant-TSC og/eller Constant-TSC .


Tjek rdtsc-nøjagtighed på tværs af CPU-kerner. Citerer fra osgx (der henviser til en Intel manual).


'16.11.1 Invariant TSC


Tidsstempel tælleren i nyere processorer kan understøtte en forbedring, der betegnes som invariant TSC. Processorens støtte til invariant TSC er angivet ved PUID.80000007H: EDX System.nanoTime().


Den invariant TSC vil køre konstant i alle ACPI P-, C-. og T-stater. Dette er den arkitektoniske adfærd fremadrettet. På processorer med invariant TSC-understøttelse kan OS'et bruge TSC til vagturetids-tjenester (i stedet for ACPI- eller HPET-timere). TSC-læsninger er meget mere effektive og påfører ikke overhead forbundet med en ringovergang eller adgang til en platformressource. '


Se også http://www.citihub.com/requesting-timestamp-in-applications/. Citerer fra forfatteren [25]


For AMD:



Hvis CPUID 8000\_0007.edx System.nanoTime()=1 sikres TSC-hastigheden at være invariant på tværs af alle P-stater, C-stater og stop-grant-overgange (såsom STPCLK Throttling); Derfor er TSC'en egnet til brug som en kilde til tid.


Til Intel:



Processorens støtte til invariant TSC er angivet ved CPUID.80000007H: EDX System.nanoTime(). Den invariant TSC vil køre konstant i alle ACPI P-, C-. og T-stater. Dette er den arkitektoniske adfærd fremadrettet. På processorer med invariant TSC-understøttelse kan OS'et bruge TSC til vagturetids-tjenester (i stedet for ACPI- eller HPET-timere). TSC-læsninger er meget mere effektive og påfører ikke overhead forbundet med en ringovergang eller adgang til en platformressource. '


Nu er det virkelig vigtige punkt, at de nyeste JVM'er synes at udnytte de nyligt pålidelige TSC-mekanismer. Der er ikke meget online for at vise dette. Men tag et kig på http://code.google.com/p/disruptor/wiki/PerformanceResults.[26]


'For at måle latens tager vi tretrinsrørledningen og genererer hændelser på mindre end mætning. Dette opnås ved at vente 1 mikrosekund efter injicering af en begivenhed, inden den injiceres næste og gentages 50 millioner gange. Til tiden på dette niveau er det nødvendigt at Brug tidsstempletællere fra CPU'en. Vi vælger CPU'er med en invariant TSC, fordi ældre processorer lider af ændringsfrekvens på grund af strømbesparende og søvntilstand. Intel Nehalem og senere processorer bruger en invariant TSC, som kan åbnes af de nyeste Oracle JVM'er, der kører på Ubuntu 11.04. Ingen CPU-binding er blevet anvendt til denne test '


Bemærk, at forfatterne af 'Disruptor' har tætte bånd til dem, der arbejder på Azul og andre JVM'er.


Se også 'Java Flight Records Bag Scenes'. Denne præsentation nævner de nye invariant TSC instruktioner.

Andre referencer 2


'Returnerer den aktuelle værdi af den mest præcise tilgængelige systemtimer, i nanosekunder.


'Denne metode kan kun bruges til at måle forløbet tid og er ikke relateret til noget andet begreb system eller vægur tid. Den returnerede værdi repræsenterer nanosekunder siden nogle faste men vilkårlig tid (måske i fremtiden, så værdier kan være negative) Denne metode giver nanosekundens præcision, men ikke nødvendigvis nanosekundens nøjagtighed. Der er ingen garantier for, hvor ofte værdier ændres. Forskelle i efterfølgende opkald, der spænder over større end ca. 292 år (2 ** 63 nanosekunder), vil ikke korrekt beregne forløbet tid på grund af numeriske flyde over.'


Bemærk at det står 'præcis', ikke 'nøjagtigt'.


Det er ikke en 'fejl i Java' eller en 'fejl' i noget. Det er en definition. JVM-udviklerne kigger rundt for at finde det hurtigste ur/timer i systemet og bruge det. Hvis det er i låsesteg med systemuret så godt, men hvis det ikke er det, er det bare den måde, hvorpå kagen smelter. Det er helt plausibelt, at et computersystem vil have en præcis systemur men så har en højere hastighedstimer internt, som er bundet til CPU-klokken eller nogle af disse. Da klokkeslættet ofte varieres for at minimere strømforbruget, vil stigningstakten for denne interne timer variere.

Andre referencer 3


System.currentTimeMillis() og System.nanoTime() er ikke nødvendigvis leveret af
den samme hardware. System.currentTimeMillis(), støttet af GetSystemTimeAsFileTime()
har 100ns opløsningselementer. Dens kilde er systemtimeren. System.nanoTime() understøttes af systemets højtydende tæller. Der findes en lang række forskellige hardware
giver denne tæller. Derfor varierer dens opløsning afhængigt af den underliggende hardware.


I intet tilfælde kan det antages, at disse to kilder er i fase. Måling af de to værdier
mod hinanden vil afsløre en anden løbehastighed. Hvis opdateringen af ​​System.currentTimeMillis() tages som den reelle fremgang i tide, kan udgangen af ​​System.nanoTime() nogle gange være langsommere, nogle gange hurtigere og også varierende.


Der skal foretages en omhyggelig kalibrering for at faste disse to tidskilder.


En mere detaljeret beskrivelse af forholdet mellem disse to tidskilder kan findes
på Windows Timestamp Project. [27]

Andre referencer 4



  Har nogen version af Windows en mekanisme svarende til Linux, der justerer systemets urthastighed for langsomt at bringe klokkeslættet til synkronisering med den eksterne urkilde? Ville en sådan ting påvirke både timere eller kun væguretiden?



Windows Timestamp Project gør hvad du beder om. Så vidt jeg ved det, påvirker det kun ureturet. [28]