System.nanoTime () è completamente inutile?


153

Come documentato nel post sul blog Beware of System.nanoTime () in Java , su sistemi x86, System.nanoTime () di Java restituisce il valore temporale utilizzando un contatore specifico della CPU . Ora considera il seguente caso che uso per misurare il tempo di una chiamata:

long time1= System.nanoTime();
foo();
long time2 = System.nanoTime();
long timeSpent = time2-time1;

Ora in un sistema multi-core, potrebbe essere che dopo aver misurato il tempo1, il thread sia programmato su un processore diverso il cui contatore è inferiore a quello della CPU precedente. Quindi potremmo ottenere un valore in time2 che è inferiore a time1. Quindi otterremmo un valore negativo in timeSpent.

Considerando questo caso, non è che System.nanotime è praticamente inutile per ora?

So che cambiare l'ora del sistema non influisce sul nanotime. Questo non è il problema che descrivo sopra. Il problema è che ogni CPU manterrà un contatore diverso dall'accensione. Questo contatore può essere inferiore sulla seconda CPU rispetto alla prima CPU. Poiché il thread può essere programmato dal sistema operativo sulla seconda CPU dopo aver ottenuto time1, il valore di timeSpent potrebbe essere errato e persino negativo.


2
Non ho una risposta ma sono d'accordo con te. Forse dovrebbe essere considerato un bug nella JVM.
Aaron Digulla,

2
quel post non è corretto e non usare TSC è lento ma devi convivere con: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6440250 Inoltre TSC può essere reso utile tramite hypervisor ma poi è di nuovo lento.
bestsss

1
E, naturalmente, è possibile eseguire in una macchina virtuale in cui una CPU può essere visualizzata durante una sessione: D
Espiazione limitata

Risposte:


206

Questa risposta è stata scritta nel 2011 dal punto di vista di ciò che effettivamente ha fatto il Sun JDK dell'epoca in esecuzione sui sistemi operativi dell'epoca. Questo è stato molto tempo fa! La risposta di leventov offre una prospettiva più aggiornata.

Quel post è sbagliato ed nanoTimeè sicuro. C'è un commento sul post che collega a un post sul blog di David Holmes , un ragazzo in tempo reale e concorrente di Sun. Dice:

System.nanoTime () è implementato utilizzando l'API QueryPerformanceCounter / QueryPerformanceFrequency [...] Il meccanismo predefinito utilizzato da QPC è determinato dal livello Hardware Abstraction (HAL) [...] Questa impostazione predefinita cambia non solo su hardware ma anche su tutto il sistema operativo versioni. Ad esempio, Windows XP Service Pack 2 ha cambiato le cose per utilizzare il timer di gestione dell'alimentazione (PMTimer) anziché il contatore del timestamp del processore (TSC) a causa di problemi con il TSC non sincronizzati su processori diversi nei sistemi SMP e a causa della sua frequenza può variare (e quindi la sua relazione con il tempo trascorso) in base alle impostazioni di risparmio energia.

Quindi, su Windows, questo era un problema fino a WinXP SP2, ma non lo è ora.

Non riesco a trovare una parte II (o più) che parla di altre piattaforme, ma quell'articolo include un'osservazione che Linux ha riscontrato e risolto lo stesso problema allo stesso modo, con un collegamento alle FAQ per clock_gettime (CLOCK_REALTIME) , che dice:

  1. Clock_gettime (CLOCK_REALTIME) è coerente tra tutti i processori / core? (L'arco è importante, ad esempio ppc, arm, x86, amd64, sparc).

Si dovrebbe o è considerato buggy.

Tuttavia, su x86 / x86_64, è possibile vedere TSC freq non sincronizzati o variabili che causano incoerenze temporali. I kernel 2.4 in realtà non avevano alcuna protezione contro questo, e anche i primi kernel 2.6 non andavano troppo bene qui. A partire dal 2.6.18 in poi la logica per rilevare questo è migliore e di solito torneremo a una sorgente di clock sicura.

ppc ha sempre una base dei tempi sincronizzata, quindi non dovrebbe essere un problema.

Quindi, se il link di Holmes può essere letto come implicito che nanoTimechiama clock_gettime(CLOCK_REALTIME), allora è sicuro dal kernel 2.6.18 su x86 e sempre su PowerPC (perché IBM e Motorola, a differenza di Intel, sanno davvero come progettare microprocessori).

Purtroppo non si parla di SPARC o Solaris. E, naturalmente, non abbiamo idea di cosa facciano le JVM IBM. Ma le JVM di Sun su Windows e Linux moderni funzionano bene.

EDIT: questa risposta si basa sulle fonti che cita. Ma mi preoccupo ancora che in realtà potrebbe essere completamente sbagliato. Alcune informazioni più aggiornate sarebbero davvero preziose. Ho appena trovato un link a un nuovo articolo di quattro anni sugli orologi di Linux che potrebbe essere utile.


13
Anche WinXP SP2 sembra soffrire. Esecuzione void foo() { Thread.sleep(40); }Athlon 64 X2 4200+
dell'esempio di

Suppongo che non ci siano aggiornamenti su questo, wrt. comportamento su Linux, BSD o altre piattaforme?
Tomer Gabel,

6
Buona risposta, dovrebbe aggiungere un link alla più recente esplorazione di questo argomento: shipilev.net/blog/2014/nanotrusting-nanotime
Nitsan Wakart

1
@SOFe: Oh, è un peccato. È nell'archivio web , per fortuna. Vedrò se riesco a rintracciare una versione corrente.
Tom Anderson,

1
Nota: OpenJDK non ha rispettato le specifiche fino a OpenJDK 8u192, vedere bugs.openjdk.java.net/browse/JDK-8184271 . Assicurati di utilizzare almeno come nuova versione di OpenJDK 8 o OpenJDK 11+.
leventov,

35

Ho fatto un po 'di ricerca e ho scoperto che se uno è pedante, allora sì, potrebbe essere considerato inutile ... in determinate situazioni ... dipende da quanto sono sensibili le tue esigenze in termini di tempo ...

Dai un'occhiata a questo preventivo dal sito Java Sun:

L'orologio in tempo reale e System.nanoTime () sono entrambi basati sulla stessa chiamata di sistema e quindi sullo stesso orologio.

Con Java RTS, tutte le API basate sul tempo (ad esempio timer, thread periodici, monitoraggio delle scadenze e così via) si basano sul timer ad alta risoluzione. E, insieme alle priorità in tempo reale, possono garantire che il codice appropriato sia eseguito al momento giusto per vincoli in tempo reale. Al contrario, le normali API Java SE offrono solo alcuni metodi in grado di gestire tempi ad alta risoluzione, senza alcuna garanzia di esecuzione in un determinato momento. L'uso di System.nanoTime () tra vari punti del codice per eseguire misurazioni del tempo trascorso deve essere sempre accurato.

Java ha anche un avvertimento per il metodo nanoTime () :

Questo metodo può essere utilizzato solo per misurare il tempo trascorso e non è correlato a nessun'altra nozione di tempo di sistema o di orologio da parete. Il valore restituito rappresenta i nanosecondi da un tempo fisso ma arbitrario (forse in futuro, quindi i valori possono essere negativi). Questo metodo fornisce precisione dei nanosecondi, ma non necessariamente precisione dei nanosecondi. Non vengono fornite garanzie sulla frequenza con cui i valori cambiano. Le differenze nelle chiamate successive che si estendono per più di circa 292,3 anni (2 63 nanosecondi) non calcoleranno accuratamente il tempo trascorso a causa dell'overflow numerico.

Sembrerebbe che l'unica conclusione che si possa trarre è che nanoTime () non può essere considerato un valore accurato. Di conseguenza, se non è necessario misurare tempi distanti solo nano secondi, questo metodo è abbastanza valido anche se il valore restituito risultante è negativo. Tuttavia, se hai bisogno di maggiore precisione, sembrano consigliarti di utilizzare JAVA RTS.

Quindi, per rispondere alla tua domanda ... no nanoTime () non è inutile ... non è il metodo più prudente da utilizzare in ogni situazione.


3
> questo metodo è abbastanza buono anche se il valore restituito risultante è negativo. Non capisco, se il valore in timepent è negativo, come può essere utile per misurare il tempo impiegato in foo ()?
pdeva,

3
va bene perché tutto ciò che ti preoccupa è il valore assoluto della differenza. cioè se la tua misura è il tempo t dove t = t2 - t1 allora vuoi sapere | t | .... quindi cosa succede se il valore è negativo ... anche con il problema multi core l'impatto raramente sarà qualche nanosecondi comunque.
mezoide,

3
Per eseguire il backup di @Aaron: sia t2 che t1 potrebbero essere negativi ma (t2-t1) non devono essere negativi.
jfs,

2
aaron: questo è esattamente il mio punto. t2-t1 non dovrebbe mai essere negativo altrimenti abbiamo un bug.
pdeva,

12
@pdeva - ma stai fraintendendo cosa dice il documento. Stai sollevando un problema. C'è qualche punto nel tempo considerato "0". I valori restituiti da nanoTime () sono precisi rispetto a quel momento. È una sequenza temporale monotonicamente crescente. Potresti ottenere una serie di numeri dalla parte negativa di quella sequenza temporale. -100, -99, -98(Valori, ovviamente, molto più grandi in pratica). Stanno andando nella direzione corretta (in aumento), quindi non c'è nessun problema qui.
ToolmakerSteve,

18

Non c'è bisogno di discutere, basta usare la fonte. Qui, SE 6 per Linux, fai le tue conclusioni:

jlong os::javaTimeMillis() {
  timeval time;
  int status = gettimeofday(&time, NULL);
  assert(status != -1, "linux error");
  return jlong(time.tv_sec) * 1000  +  jlong(time.tv_usec / 1000);
}


jlong os::javaTimeNanos() {
  if (Linux::supports_monotonic_clock()) {
    struct timespec tp;
    int status = Linux::clock_gettime(CLOCK_MONOTONIC, &tp);
    assert(status == 0, "gettime error");
    jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
    return result;
  } else {
    timeval time;
    int status = gettimeofday(&time, NULL);
    assert(status != -1, "linux error");
    jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
    return 1000 * usecs;
  }
}

7
È utile solo se sai cosa fa l'API utilizzata. L'API utilizzata è implementata dal sistema operativo; questo codice è corretto. la specifica dell'API utilizzata (clock_gettime / gettimeofday), ma come altri hanno sottolineato, alcuni sistemi operativi non aggiornati hanno implementazioni errate.
Blaisorblade,

18

Dal momento che Java 7, System.nanoTime()è garantito per essere sicuro dalle specifiche JDK. System.nanoTime()Javadoc chiarisce che tutte le invocazioni osservate all'interno di una JVM (ovvero attraverso tutti i thread) sono monotoniche:

Il valore restituito rappresenta i nanosecondi da un tempo di origine fisso ma arbitrario (forse in futuro, quindi i valori possono essere negativi). La stessa origine viene utilizzata da tutte le invocazioni di questo metodo in un'istanza di una macchina virtuale Java; è probabile che altre istanze di macchine virtuali utilizzino un'origine diversa.

L'implementazione di JVM / JDK è responsabile della risoluzione delle incoerenze che potrebbero essere osservate quando vengono chiamate le utility del sistema operativo sottostante (ad esempio quelle menzionate nella risposta di Tom Anderson ).

La maggior parte delle altre vecchie risposte a questa domanda (scritta nel 2009-2012) esprime FUD che probabilmente era rilevante per Java 5 o Java 6 ma non è più rilevante per le versioni moderne di Java.

Vale la pena ricordare, tuttavia, che nonostante nanoTime()la sicurezza di JDK , ci sono stati diversi bug in OpenJDK che lo hanno impedito di mantenere questa garanzia su determinate piattaforme o in determinate circostanze (ad esempio JDK-8040140 , JDK-8184271 ). Non ci sono bug aperti (conosciuti) in OpenJDK wrtnanoTime() , ma la scoperta di un nuovo bug o una regressione in una nuova versione di OpenJDK non dovrebbe scioccare nessuno.

Tenendo presente ciò, il codice che utilizza nanoTime()per il blocco temporizzato, l'intervallo di attesa, i timeout, ecc. Dovrebbe preferibilmente trattare le differenze di tempo negative (timeout) come zeri piuttosto che generare eccezioni. Questa pratica è anche preferibile perché è coerente con il comportamento di tutti i metodi attesa temporizzata in tutte le classi java.util.concurrent.*, ad esempio Semaphore.tryAcquire(), Lock.tryLock(), BlockingQueue.poll(), etc.

Tuttavia, nanoTime()dovrebbe essere ancora preferito per l'implementazione di blocchi temporizzati, intervalli di attesa, timeout, ecc. currentTimeMillis()Perché quest'ultimo è soggetto al fenomeno del "tempo che va indietro" (ad esempio a causa della correzione del tempo del server), cioè currentTimeMillis()non è adatto per misurare gli intervalli di tempo affatto. Vedi questa risposta per maggiori informazioni.

Invece di utilizzare nanoTime()direttamente per le misurazioni del tempo di esecuzione del codice, è preferibile utilizzare strutture di benchmarking e profilatori specializzati, ad esempio JMH e async-profiler in modalità di profilatura dell'orologio da parete .



6

Linux corregge le discrepanze tra le CPU, ma Windows no. Suggerisco di supporre che System.nanoTime () sia preciso solo a circa 1 micro-secondo. Un modo semplice per ottenere un tempo più lungo è chiamare foo () 1000 o più volte e dividere il tempo per 1000.


2
Potresti fornire un riferimento (comportamento su Linux e Windows)?
jfs,

Sfortunatamente il metodo proposto sarà generalmente molto impreciso perché ogni evento che cade nello slot di aggiornamento dell'orologio da parete +/- 100ms restituirà spesso zero per le operazioni del secondo secondo. La somma di 9 operazioni ciascuna con durata pari a zero è, beh, zero, divisa per nove è ... zero. Al contrario, l'utilizzo di System.nanoTime () fornirà una durata degli eventi relativamente accurata (diversa da zero), che poi sommata e divisa per il numero di eventi fornirà una media altamente precisa.
Darrell Teague,

@DarrellTeague sommando 1000 eventi e aggiungendoli è uguale all'ora di fine a fine.
Peter Lawrey,

@DarrellTeague System.nanoTime () è preciso a 1 micro-secondo o migliore (non 100.000 microsecondi) sulla maggior parte dei sistemi. La media di molte operazioni è rilevante solo quando si scende a pochi micro-secondi e solo su determinati sistemi.
Peter Lawrey,

1
Ci scusiamo perché c'era un po 'di confusione sul linguaggio usato negli eventi di "sintesi". Sì, se il tempo è contrassegnato all'inizio di diciamo 1000 operazioni al secondo, vengono eseguiti e quindi il tempo viene nuovamente contrassegnato alla fine e diviso - funzionerebbe per un sistema in sviluppo per ottenere una buona approssimazione della durata per un dato evento.
Darrell Teague,

5

Assolutamente non inutile. Gli appassionati di timing sottolineano correttamente il problema multi-core, ma nelle applicazioni con parole reali è spesso radicalmente migliore di currentTimeMillis ().

Quando si calcolano le posizioni grafiche nel frame si aggiorna nanoTime () porta a MOLTO movimento più fluido nel mio programma.

E collaudo solo su macchine multi-core.


5

Ho visto un tempo trascorso negativo segnalato dall'uso di System.nanoTime (). Per essere chiari, il codice in questione è:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

e la variabile 'naps trascorso' aveva un valore negativo. (Sono sicuro che anche la chiamata intermedia abbia impiegato meno di 293 anni, che è il punto di overflow per i nanos immagazzinati in long :)

Ciò si è verificato utilizzando un IBM v1.5 JRE 64 bit su hardware IBM P690 (multi-core) che esegue AIX. Ho visto questo errore verificarsi solo una volta, quindi sembra estremamente raro. Non conosco la causa - è un problema specifico dell'hardware, un difetto di JVM - Non lo so. Inoltre non conosco le implicazioni per l'accuratezza di nanoTime () in generale.

Per rispondere alla domanda originale, non credo che nanoTime sia inutile: fornisce una tempistica inferiore al millisecondo, ma esiste un rischio effettivo (non solo teorico) che sia impreciso di cui è necessario tener conto.


Purtroppo, sembra esserci qualche problema con il sistema operativo / hardware. La documentazione afferma che i valori fondamentali possono essere negativi ma (maggiore negativo meno negativo minore) dovrebbe comunque essere un valore positivo. In effetti il ​​presupposto è che nello stesso thread, la chiamata nanoTime () dovrebbe sempre restituire un valore positivo o negativo. Non ho mai visto questo su vari sistemi Unix e Windows per molti anni, ma sembra possibile soprattutto se l'hardware / sistema operativo sta dividendo questa operazione apparentemente atomica tra i processori.
Darrell Teague,

@BasilVandegriend non è un bug da nessuna parte. Come da documentazione, raramente il secondo System.nanoTime () nell'esempio può essere eseguito su una CPU diversa e i valori nanoTime calcolati su quella CPU potrebbero essere inferiori ai valori calcolati sulla prima CPU. Quindi, è possibile un valore di -ve per Nanos trascorso
infinito

2

Questo non sembra essere un problema su un Core 2 Duo con Windows XP e JRE 1.5.0_06.

In un test con tre thread non vedo System.nanoTime () andare indietro. I processori sono entrambi occupati e i thread vanno a dormire occasionalmente per provocare lo spostamento dei thread in giro.

[EDIT] Immagino che ciò avvenga solo su processori fisicamente separati, vale a dire che i contatori sono sincronizzati per più core sullo stesso die.


2
Probabilmente non succederà sempre, ma a causa del modo in cui il nanotime () viene implementato la possibilità è sempre lì.
pdeva,

Immagino che ciò avvenga solo su processori fisicamente separati, vale a dire che i contatori sono sincronizzati per più core sullo stesso dado.
Starblue,

Anche questo dipende dall'attuazione specifica, IIRC. Ma è qualcosa di cui il sistema operativo dovrebbe occuparsi.
Blaisorblade,

1
I contatori RDTSC su più core dello stesso processore x86 non sono necessariamente sincronizzati: alcuni sistemi moderni consentono l'esecuzione di core diversi a velocità diverse.
Jules,

2

No, non è ... Dipende solo dalla tua CPU, controlla il Timer eventi ad alta precisione per sapere come / perché le cose sono trattate diversamente in base alla CPU.

Fondamentalmente, leggi l'origine di Java e controlla cosa fa la tua versione con la funzione, e se funziona contro la CPU la eseguirai.

IBM suggerisce persino di utilizzarlo per il benchmarking delle prestazioni (un post del 2008, ma aggiornato).


Come ogni implementazione definita bahaviour, "caveat emptor!"
David Schmitt,

2

Sto collegando a ciò che essenzialmente è la stessa discussione in cui Peter Lawrey sta fornendo una buona risposta. Perché ottengo un tempo trascorso negativo utilizzando System.nanoTime ()?

Molte persone hanno affermato che in Java System.nanoTime () potrebbe restituire un tempo negativo. Mi scuso per aver ripetuto ciò che hanno già detto gli altri.

  1. nanoTime () non è un orologio ma un contatore di cicli della CPU.
  2. Il valore di ritorno è diviso per frequenza per assomigliare al tempo.
  3. La frequenza della CPU può variare.
  4. Quando il tuo thread è programmato su un'altra CPU, c'è la possibilità di ottenere nanoTime () che si traduce in una differenza negativa. Questo è logico. I contatori tra le CPU non sono sincronizzati.
  5. In molti casi, potresti ottenere risultati abbastanza fuorvianti, ma non potresti dirlo perché il delta non è negativo. Pensaci.
  6. (non confermato) Penso che potresti ottenere un risultato negativo anche sulla stessa CPU se le istruzioni vengono riordinate. Per evitarlo, dovresti invocare una barriera di memoria serializzando le tue istruzioni.

Sarebbe bello se System.nanoTime () restituisse coreID dove veniva eseguito.


1
Tutti i punti tranne 3. e 5. sono errati. 1. nanoTime () non è un contatore di cicli della CPU, è un tempo nano . 2. Il modo in cui viene prodotto il valore nanoTime è specifico della piattaforma. 4. No, la differenza non può essere negativa, secondo la specifica nanoTime (). Supponendo che OpenJDK non abbia bug su nanoTime () e al momento non sono noti bug irrisolti. 6. Le chiamate nanoTime non possono essere riordinate all'interno di un thread perché è un metodo nativo e JVM rispetta l'ordine del programma. JVM non riordina mai le chiamate del metodo nativo perché non sa cosa succede al loro interno e quindi non può dimostrare che tali riordini sarebbero sicuri.
leventov,

Per quanto riguarda 5. i risultati della differenza nanoTime () possono essere davvero fuorvianti, ma non per i motivi presentati in altri punti di questa domanda. Ma piuttosto per i motivi presentati qui: shipilev.net/blog/2014/nanotrusting-nanotime
leventov

Ironia della sorte, per quanto riguarda 6. c'era stato un bug in OpenJDK in particolare a causa di riordini: bugs.openjdk.java.net/browse/JDK-8184271 . In OpenJDK, nanoTime () è intrinseco e gli è stato permesso di riordinare, che era un bug.
leventov,

@leventov, quindi nanotime () è sicuro da usare? il che significa che non può restituire valori negativi ed è ~ accurato nel tempo. Non vedo il punto di esporre una funzione API piena di problemi. Questo post è una prova, iniziato nel 2009 e ancora commentato nel 2019. Per cose mission-critical, immagino che le persone facciano affidamento su schede di temporizzazione come Symmetricom
Vortex,

1
Il tuo numero 4 dice della differenza negativa , non dei valori: "c'è la possibilità di ottenere nanoTime () che si traduce in una differenza negativa".
leventov,

1

Java è multipiattaforma e nanoTime dipende dalla piattaforma. Se usi Java - quando non usi nanoTime. Ho trovato veri e propri bug in diverse implementazioni jvm con questa funzione.


0

La documentazione Java 5 consiglia inoltre di utilizzare questo metodo per lo stesso scopo.

Questo metodo può essere utilizzato solo per misurare il tempo trascorso e non è correlato ad alcuna altra nozione di tempo di sistema o di orologio da parete.

Doc. API Java 5


0

Inoltre, System.currentTimeMillies()cambia quando si cambia l'orologio del sistema, mentre System.nanoTime()non lo fa, quindi quest'ultimo è più sicuro per misurare le durate.


-3

nanoTimeè estremamente insicuro per i tempi. L'ho provato con i miei algoritmi di base per i test di primalità e mi ha dato risposte che erano letteralmente a un secondo di distanza per lo stesso input. Non usare quel metodo ridicolo. Ho bisogno di qualcosa che sia più preciso e preciso di ottenere millis di tempo, ma non così male nanoTime.


senza una fonte o una spiegazione migliore questo commento è inutile
ic3
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.