Temporizzazione in microsecondi in JavaScript


100

Esistono funzioni di temporizzazione in JavaScript con risoluzione al microsecondo?

Conosco timer.js per Chrome e spero che ci sarà una soluzione per altri browser amichevoli, come Firefox, Safari, Opera, Epiphany, Konqueror, ecc. Non sono interessato a supportare alcun IE, ma risposte che includono IE prego.

(Data la scarsa precisione del tempo in millisecondi in JS, non sto trattenendo il respiro su questo!)

Aggiornamento: timer.js annuncia una risoluzione in microsecondi, ma moltiplica semplicemente la lettura del millisecondo per 1.000. Verificato mediante test e ispezione del codice. Deluso. : [


2
Cosa stai cercando di fare in un browser che richiede una precisione al microsecondo? In generale, le garanzie di prestazione del comportamento dei browser non sono così precise.
Yuliy

4
Non succederà. Non puoi fidarti affatto della precisione del micro secondo anche se esistesse. L'unico caso d'uso solido che posso immaginare sono i client nativi in ​​Chrome, ma poi non ti interessa l'API JS. Mi piace anche trattare "Epiphany" come un browser di prima classe e ignorare IE.
Raynos

6
"Ottenere" il tempo in javascript richiede un po 'di tempo, così come restituirlo e la latenza aumenta se ci si trova su una pagina web che sta ridisegnando o gestendo eventi. Non conterei nemmeno sulla precisione di 10 millisecondi più vicina.
kennebec

1
Tipo, ad esempio, la visualizzazione di popup ad altissima velocità? Fondamentalmente, il problema è che dare a parti esterne un accesso eccessivo alle macchine degli utenti semplicemente per il fatto che una persona visita un sito Web è un problema serio.
Pointy

1
Non è più "vulnerabile" di setInterval (popup, 0), che è abbastanza veloce perché il problema sia sostanzialmente equivalente. Anche la precisione millisecondo dovrebbe essere rimossa? kennebec: il tuo commento ha senso, grazie.
mwcz

Risposte:


134

Come accennato nella risposta di Mark Rejhon, nei browser moderni è disponibile un'API che espone allo script dati di temporizzazione con risoluzione inferiore al millisecondo: il timer ad alta risoluzione W3C , aka window.performance.now().

now()è migliore del tradizionale Date.getTime()in due modi importanti:

  1. now()è un doppio con risoluzione inferiore al millisecondo che rappresenta il numero di millisecondi dall'inizio della navigazione della pagina. Restituisce il numero di microsecondi nel frazionario (ad esempio un valore di 1000.123 è 1 secondo e 123 microsecondi).

  2. now()è monotonicamente crescente. Questo è importante in quanto Date.getTime()può eventualmente saltare in avanti o addirittura indietro nelle chiamate successive. In particolare, se viene aggiornata l'ora di sistema del sistema operativo (ad es. Sincronizzazione dell'orologio atomico), Date.getTime()viene aggiornata anche. now()è garantito che aumenti sempre in modo monotono, quindi non è influenzato dall'ora di sistema del sistema operativo - sarà sempre l'ora dell'orologio da parete (supponendo che l'orologio da parete non sia atomico ...).

now()può essere utilizzato in quasi tutti i luoghi che new Date.getTime(), + new Datee Date.now()sono. L'eccezione è che Datee now()volte non si mescolano, come Dateè basato su unix-epoca (il numero di millisecondi dal 1970), mentre now()è il numero di millisecondi dal momento che la navigazione pagina iniziata (quindi sarà molto più piccola di Date).

now()è supportato in Chrome stabile, Firefox 15+ e IE10. Sono disponibili anche diversi polyfill .


1
i polyfill useranno molto probabilmente Date.now (), quindi questa è ancora l'opzione migliore considerando IE9 e sono milioni di utenti, perché mescolare la libreria di terze parti allora
Vitaliy Terziev

4
Il mio orologio da parete è atomico.
programmatore

4
new Date.getTime()non è una cosa. new Date().getTime()è.
Il Qodesmith

Mi è piaciuta molto questa risposta. Ho eseguito alcuni test e ho trovato un esempio che puoi inserire nella tua console per vedere che questo avrà ancora forti collisioni quando lo usi. (nota che stavo ottenendo il 10% di collisioni su una buona macchina anche facendo qualcosa di costoso come una console.logsu ogni corsa) Difficile da capire ma copia tutto il codice evidenziato qui:last=-11; same=0; runs=100; for(let i=0;i<runs;i++) { let now = performance.now(); console.log('.'); if (now === last) { same++; } last = now; } console.log(same, 'were the same');
bladnman

2
Rivisitando il mio commento dell'anno 2012 . performance.now () è ora leggermente confuso di nuovo da Meltdown / Spectre workaround. Alcuni browser hanno seriamente degradato performance.now () per motivi di sicurezza. Penso che la mia tecnica abbia probabilmente riacquistato una certa rilevanza per una quantità enorme di molti casi d'uso di benchmarking legittimi, soggetti a limitazioni del timer-fuzz. Detto questo, alcuni browser ora hanno alcune funzionalità / estensioni di profilazione delle prestazioni degli sviluppatori che non esistevano nel 2012.
Mark Rejhon

20

Ora c'è un nuovo metodo per misurare i microsecondi in javascript: http://gent.ilcore.com/2012/06/better-timer-for-javascript.html

Tuttavia, in passato, ho trovato un metodo grezzo per ottenere una precisione di 0,1 millisecondi in JavaScript da un timer di millisecondi. Impossibile? No. Continua a leggere:

Sto eseguendo alcuni esperimenti ad alta precisione che richiedono accuratezze del timer auto-controllate e ho scoperto di essere in grado di ottenere in modo affidabile una precisione di 0,1 millisecondi con alcuni browser su determinati sistemi.

Ho scoperto che nei moderni browser web con accelerazione GPU su sistemi veloci (ad es. I7 quad core, dove diversi core sono inattivi, solo la finestra del browser) - ora posso fidarmi che i timer siano precisi al millisecondo. In effetti, è diventato così preciso su un sistema i7 inattivo che sono stato in grado di ottenere in modo affidabile lo stesso identico millisecondo, in oltre 1.000 tentativi. Solo quando provo a fare cose come caricare una pagina web aggiuntiva o altro, la precisione del millisecondo si riduce (e sono in grado di rilevare con successo la mia precisione degradata eseguendo un controllo temporale prima e dopo, per vedere se il mio tempo di elaborazione si è improvvisamente allungato a 1 o più millisecondi - questo mi aiuta a invalidare i risultati che probabilmente sono stati influenzati troppo negativamente dalle fluttuazioni della CPU).

È diventato così preciso in alcuni browser con accelerazione GPU su sistemi quad-core i7 (quando la finestra del browser è l'unica finestra), che ho scoperto che avrei desiderato poter accedere a un timer di precisione di 0,1 ms in JavaScript, poiché la precisione è finalmente ora su alcuni sistemi di navigazione di fascia alta per rendere utile tale precisione del timer per alcuni tipi di applicazioni di nicchia che richiedono alta precisione e dove le applicazioni sono in grado di autoverificarsi per deviazioni di precisione.

Ovviamente se stai eseguendo più passaggi, puoi semplicemente eseguire più passaggi (es. 10 passaggi) quindi dividere per 10 per ottenere una precisione di 0,1 millisecondi. Questo è un metodo comune per ottenere una migliore precisione: eseguire più passaggi e dividere il tempo totale per il numero di passaggi.

TUTTAVIA ... Se posso eseguire solo un singolo passaggio di benchmark di un test specifico a causa di una situazione insolitamente unica, ho scoperto che posso ottenere una precisione di 0,1 (e talvolta 0,01 ms) in questo modo:

Inizializzazione / Calibrazione:

  1. Eseguire un ciclo occupato per attendere che il timer passi al millisecondo successivo (allinea il timer all'inizio dell'intervallo di millisecondo successivo) Questo ciclo occupato dura meno di un millisecondo.
  2. Eseguire un altro ciclo occupato per incrementare un contatore mentre si attende che il timer aumenti. Il contatore ti dice quanti incrementi del contatore si sono verificati in un millisecondo. Questo ciclo occupato dura un millisecondo intero.
  3. Ripeti quanto sopra, finché i numeri non diventano ultra stabili (tempo di caricamento, compilatore JIT, ecc.). 4. NOTA: La stabilità del numero ti dà la tua precisione raggiungibile su un sistema inattivo. È possibile calcolare la varianza, se è necessario controllare automaticamente la precisione. Le variazioni sono maggiori su alcuni browser e minori su altri browser. Più grande su sistemi più veloci e più lento su sistemi più lenti. Anche la coerenza varia. Puoi dire quali browser sono più coerenti / accurati di altri. Sistemi più lenti e sistemi occupati porteranno a maggiori variazioni tra i passaggi di inizializzazione. In questo modo è possibile visualizzare un messaggio di avviso se il browser non fornisce una precisione sufficiente per consentire misurazioni di 0,1 ms o 0,01 ms. Lo skew del timer può essere un problema, ma alcuni timer interi di millisecondi su alcuni sistemi aumentano abbastanza accuratamente (esattamente sul punto), il che si tradurrà in valori di calibrazione molto coerenti di cui ti puoi fidare.
  4. Salva il valore del contatore finale (o la media degli ultimi passaggi di calibrazione)

Confronto tra un passaggio e una precisione inferiore al millisecondo:

  1. Eseguire un ciclo occupato per attendere che il timer passi al millisecondo successivo (allinea il timer all'inizio dell'intervallo di millisecondo successivo). Questo ciclo occupato dura meno di un millisecondo.
  2. Esegui l'attività che desideri per confrontare con precisione il tempo.
  3. Controlla il timer. Questo ti dà i millisecondi interi.
  4. Eseguire un ultimo ciclo occupato per incrementare un contatore mentre si attende che il timer aumenti. Questo ciclo occupato dura meno di un millisecondo.
  5. Dividere questo valore del contatore per il valore del contatore originale dell'inizializzazione.
  6. Ora hai la parte decimale dei millisecondi !!!!!!!!

ATTENZIONE: I loop occupati NON sono consigliati nei browser web, ma fortunatamente questi loop occupati vengono eseguiti per meno di 1 millisecondo ciascuno e vengono eseguiti solo poche volte.

Variabili come la compilazione JIT e le fluttuazioni della CPU aggiungono enormi imprecisioni, ma se esegui diversi passaggi di inizializzazione, avrai una ricompilazione dinamica completa e alla fine il contatore si assesta su qualcosa di molto preciso. Assicurati che tutti i loop occupati abbiano esattamente la stessa funzione per tutti i casi, in modo che le differenze nei loop occupati non portino a differenze. Assicurati che tutte le righe di codice vengano eseguite più volte prima di iniziare a fidarti dei risultati, per consentire ai compilatori JIT di essersi già stabilizzati a una ricompilazione dinamica completa (dynarec).

In effetti, ho assistito a una precisione che si avvicinava ai microsecondi su alcuni sistemi, ma non mi fiderei ancora. Ma la precisione di 0,1 millisecondi sembra funzionare in modo abbastanza affidabile, su un sistema quad-core inattivo in cui sono l'unica pagina del browser. Sono arrivato a un caso di test scientifico in cui potevo eseguire solo passaggi una tantum (a causa di variabili univoche che si verificano) e avevo bisogno di cronometrare con precisione ogni passaggio, piuttosto che fare la media di più passaggi ripetuti, quindi è per questo che l'ho fatto.

Ho eseguito diversi pre-passaggi e passaggi fittizi (anche per regolare il dynarec), per verificare l'affidabilità della precisione di 0,1 ms (è rimasto solido per diversi secondi), quindi ho tenuto le mani lontane dalla tastiera / mouse, mentre si verificava il benchmark, quindi ho fatto diversi post-pass per verificare l'affidabilità della precisione di 0,1 ms (è rimasto di nuovo solido). Ciò verifica anche che cose come modifiche dello stato di alimentazione o altre cose non si siano verificate tra il prima e il dopo, interferendo con i risultati. Ripeti il ​​pre-test e il post-test tra ogni singolo passaggio di benchmark. Su questo, ero praticamente certo che i risultati intermedi fossero accurati. Non vi è alcuna garanzia, ovviamente, ma dimostra che in alcuni casi è possibile una precisione <0.1ms in un browser web.

Questo metodo è utile solo in casi molto, molto di nicchia . Anche così, non sarà letteralmente garantito al 100% all'infinito, puoi ottenere un'accuratezza abbastanza affidabile e persino un'accuratezza scientifica se combinata con diversi livelli di verifiche interne ed esterne.


3
Era complicato eseguire il tempismo con una precisione superiore perché tutto ciò che avevamo era Date.now()o +new Date(). Ma ora abbiamo performance.now(). Sebbene sia chiaro che hai trovato alcuni modi interessanti per hackerare più funzionalità, questa risposta è essenzialmente obsoleta. Inoltre, non consigliare nulla relativo a loop occupati. Basta non farlo. Non ne abbiamo bisogno di più.
Steven Lu

1
La maggior parte dei browser ha ridotto la precisione dell'implementazione di performance.now () per mitigare temporaneamente l'attacco di temporizzazione della cache. Mi chiedo se questa risposta abbia ancora un significato nella ricerca sulla sicurezza.
Qi Fan

2
Rivisitando il mio commento. Wow, ho pubblicato quanto sopra nel 2012 molto prima di performance.now (). Ma ora è un po 'confuso di nuovo leggermente dalle soluzioni alternative di Meltdown / Spectre. Alcuni browser hanno seriamente degradato performance.now () per motivi di sicurezza. Penso che la tecnica di cui sopra abbia probabilmente riacquistato una certa rilevanza per una grande quantità di molti casi d'uso di benchmarking legittimi, soggetti a limitazioni del timer-fuzz.
Mark Rejhon

3

La risposta è "no", in generale. Se stai utilizzando JavaScript in un ambiente lato server (ovvero non in un browser), tutte le scommesse sono disattivate e puoi provare a fare tutto ciò che desideri.

modifica : questa risposta è vecchia; gli standard sono progrediti e sono disponibili nuove strutture come soluzioni al problema del tempo preciso. Anche così, va ricordato che al di fuori del dominio di un vero sistema operativo in tempo reale, il codice ordinario non privilegiato ha un controllo limitato sul proprio accesso alle risorse di elaborazione. Misurare le prestazioni non è (necessariamente) la stessa cosa che prevedere le prestazioni.


2

Ecco un esempio che mostra il mio timer ad alta risoluzione per node.js :

 function startTimer() {
   const time = process.hrtime();
   return time;
 }

 function endTimer(time) {
   function roundTo(decimalPlaces, numberToRound) {
     return +(Math.round(numberToRound + `e+${decimalPlaces}`)  + `e-${decimalPlaces}`);
   }
   const diff = process.hrtime(time);
   const NS_PER_SEC = 1e9;
   const result = (diff[0] * NS_PER_SEC + diff[1]); // Result in Nanoseconds
   const elapsed = result * 0.0000010;
   return roundTo(6, elapsed); // Result in milliseconds
 }

Utilizzo:

 const start = startTimer();

 console.log('test');

 console.log(`Time since start: ${endTimer(start)} ms`);

Normalmente, potresti essere in grado di utilizzare:

 console.time('Time since start');

 console.log('test');

 console.timeEnd('Time since start');

Se stai cronometrando sezioni di codice che implicano il loop, non puoi accedere al valore di console.timeEnd()per sommare i risultati del timer. Puoi, ma diventa sgradevole perché devi iniettare il valore della tua variabile iterante, comei , e impostare una condizione per rilevare se il ciclo è finito.

Ecco un esempio perché può essere utile:

 const num = 10;

 console.time(`Time til ${num}`);

 for (let i = 0; i < num; i++) {
   console.log('test');
   if ((i+1) === num) { console.timeEnd(`Time til ${num}`); }
   console.log('...additional steps');
 }

Cita: https://nodejs.org/api/process.html#process_process_hrtime_time

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.