Come posso ottenere un tempo preciso, ad esempio in millisecondi in Objective-C?


99

C'è un modo semplice per ottenere un tempo molto preciso?

Devo calcolare alcuni ritardi tra le chiamate al metodo. Più specificamente, voglio calcolare la velocità di scorrimento in un UIScrollView.


ecco una domanda molto correlata che può anche aiutare a capire le risposte qui .. per favore dai un'occhiata!
abbood


Risposte:


127

NSDatee i timeIntervalSince*metodi restituiranno a NSTimeIntervalche è un doppio con una precisione inferiore al millisecondo. NSTimeIntervalè in pochi secondi, ma usa il doppio per darti una maggiore precisione.

Per calcolare la precisione temporale in millisecondi, puoi fare:

// Get a current time for where you want to start measuring from
NSDate *date = [NSDate date];

// do work...

// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
double timePassed_ms = [date timeIntervalSinceNow] * -1000.0;

Documentazione su timeIntervalSinceNow .

Esistono molti altri modi per calcolare questo intervallo utilizzando NSDatee consiglierei di consultare la documentazione della classe per la NSDatequale si trova in NSDate Class Reference .


3
In realtà questo è abbastanza preciso per il caso d'uso generale.
logancautrell

4
È sicuro utilizzare NSDates per calcolare il tempo trascorso? Mi sembra che l'ora di sistema possa andare avanti o indietro durante la sincronizzazione con fonti di tempo esterne, quindi non saresti in grado di fidarti del risultato. Vuoi davvero un orologio che aumenta in modo monotono, vero?
Kristopher Johnson,

1
Ho appena confrontato NSDatee mach_absolute_time()al livello di circa 30 ms. 27 contro 29, 36 contro 39, 43 contro 45. NSDateera più facile da usare per me ei risultati erano abbastanza simili da non preoccuparmene.
nevan king

7
Non è sicuro utilizzare NSDate per confrontare il tempo trascorso, poiché l'orologio di sistema può cambiare in qualsiasi momento (a causa di NTP, transizioni DST, secondi intercalari e molti altri motivi). Usa invece mach_absolute_time.
nevyn

@nevyn, hai appena salvato il mio test di velocità di Internet, ty! Accidenti, sono contento di aver letto i commenti prima di copiare il codice incollato eh
Albert Renshaw

41

mach_absolute_time() può essere utilizzato per ottenere misurazioni precise.

Vedi http://developer.apple.com/qa/qa2004/qa1398.html

È disponibile anche CACurrentMediaTime(), che è essenzialmente la stessa cosa ma con un'interfaccia più facile da usare.

(Nota: questa risposta è stata scritta nel 2009. Vedi la risposta di Pavel Alexeev per le clock_gettime()interfacce POSIX più semplici disponibili nelle versioni più recenti di macOS e iOS.)


1
In quel codice c'è una strana conversione - l'ultima riga del primo esempio è "return * (uint64_t *) & elapsedNano;" perché non solo "return (uint64_t) elapsedNano"?
Tyler

8
Core Animation (QuartzCore.framework) fornisce anche un metodo pratico,, CACurrentMediaTime()che converte mach_absolute_time()direttamente in un file double.
otto

1
@Tyler, elapsedNanoè di tipo Nanoseconds, che non è un tipo intero normale. È un alias di UnsignedWide, che è una struttura con due campi interi a 32 bit. Puoi usare al UnsignedWideToUInt64()posto del cast se preferisci.
Ken Thomases

CoreServices è solo una libreria per Mac. C'è un equivalente in iOS?
mm24

1
@ mm24 Su iOS, usa CACurrentMediaTime (), che si trova in Core Animation.
Kristopher Johnson

28

Si prega di non utilizzare NSDate, CFAbsoluteTimeGetCurrento gettimeofdayper misurare il tempo trascorso. Questi dipendono tutti dall'orologio di sistema, che può cambiare in qualsiasi momento a causa di molti motivi diversi, come la sincronizzazione dell'ora di rete (NTP) che aggiorna l'orologio (accade spesso per adattarsi alla deriva), le regolazioni dell'ora legale, i secondi intercalari e così via.

Ciò significa che se stai misurando la tua velocità di download o upload, otterrai picchi o cali improvvisi nei tuoi numeri che non sono correlati a ciò che è realmente accaduto; i tuoi test delle prestazioni avranno strani valori anomali errati; e i timer manuali si attiveranno dopo durate errate. Il tempo potrebbe anche tornare indietro e si finisce con delta negativi e si può finire con la ricorsione infinita o il codice morto (sì, ho fatto entrambi).

Usa mach_absolute_time. Misura i secondi reali da quando il kernel è stato avviato. Aumenta in modo monotono (non tornerà mai indietro) e non è influenzato dalle impostazioni di data e ora. Dal momento che è un problema con cui lavorare, ecco un semplice involucro che ti dà NSTimeInterval:

// LBClock.h
@interface LBClock : NSObject
+ (instancetype)sharedClock;
// since device boot or something. Monotonically increasing, unaffected by date and time settings
- (NSTimeInterval)absoluteTime;

- (NSTimeInterval)machAbsoluteToTimeInterval:(uint64_t)machAbsolute;
@end

// LBClock.m
#include <mach/mach.h>
#include <mach/mach_time.h>

@implementation LBClock
{
    mach_timebase_info_data_t _clock_timebase;
}

+ (instancetype)sharedClock
{
    static LBClock *g;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        g = [LBClock new];
    });
    return g;
}

- (id)init
{
    if(!(self = [super init]))
        return nil;
    mach_timebase_info(&_clock_timebase);
    return self;
}

- (NSTimeInterval)machAbsoluteToTimeInterval:(uint64_t)machAbsolute
{
    uint64_t nanos = (machAbsolute * _clock_timebase.numer) / _clock_timebase.denom;

    return nanos/1.0e9;
}

- (NSTimeInterval)absoluteTime
{
    uint64_t machtime = mach_absolute_time();
    return [self machAbsoluteToTimeInterval:machtime];
}
@end

2
Grazie. Che mi dici CACurrentMediaTime()di QuartzCore?
Cœur

1
Nota: si può anche usare al @import Darwin;posto di#include <mach/mach_time.h>
Cœur

@ Cœur CACurrentMediaTime dovrebbe effettivamente essere esattamente equivalente al mio codice. Buona scoperta! (L'intestazione dice "Questo è il risultato della chiamata a mach_absolute_time () e della conversione delle unità in secondi")
nevyn

"può cambiare in qualsiasi momento a causa di molti motivi diversi, come la sincronizzazione dell'ora di rete (NTP) che aggiorna l'orologio (accade spesso per adattarsi alla deriva), le regolazioni dell'ora legale, i secondi intercalari e così via." - Quali altri motivi potrebbero essere? Sto osservando salti all'indietro di circa 50 ms senza connessione a Internet. Quindi, a quanto pare, nessuno di questi motivi dovrebbe essere applicato ...
Falko

@Falko non ne sono sicuro, ma se dovessi indovinare, scommetterei che il sistema operativo esegue la compensazione della deriva da solo se nota che diversi clock hardware non sono sincronizzati?
nevyn

14

CFAbsoluteTimeGetCurrent()restituisce il tempo assoluto come doublevalore, ma non so quale sia la sua precisione: potrebbe aggiornare solo ogni dozzina di millisecondi o potrebbe aggiornare ogni microsecondo, non lo so.


2
Tuttavia, è un valore in virgola mobile a doppia precisione e fornisce una precisione inferiore al millisecondo. Un valore di 72,89674947369 secondi non è raro ...
Jim Dovey

25
@ JimDovey: direi che un valore di 72.89674947369secondi sarebbe piuttosto raro, considerando tutti gli altri valori che potrebbe essere. ;)
FreeAsInBeer

3
@ Jim: hai una citazione che fornisce una precisione inferiore al millisecondo (questa è una domanda onesta)? Spero che tutti qui capiscano le differenze tra accuratezza e precisione .
Adam Rosenfield,

5
CFAbsoluteTimeGetCurrent () chiama gettimeofday () su OS X e GetSystemTimeAsFileTime () su Windows. Ecco il codice sorgente .
Jim Dovey,

3
Oh, e gettimeofday () è implementato usando il timer Mach in nanosecondi tramite mach_absolute_time () ; ecco la fonte per l'implementazione della pagina comune di gettimeofday () su Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey,

10

NON mach_absolute_time()lo userei perché interroga una combinazione del kernel e del processore per un tempo assoluto usando i tick (probabilmente un uptime).

Cosa userei:

CFAbsoluteTimeGetCurrent();

Questa funzione è ottimizzata per correggere la differenza nel software e hardware iOS e OSX.

Qualcosa di più geek

Il quoziente di una differenza in mach_absolute_time()ed AFAbsoluteTimeGetCurrent()è sempre intorno a 24000011.154871

Ecco un registro della mia app:

Si prega di notare che il tempo risultato finale è una differenza di CFAbsoluteTimeGetCurrent()s'

 2012-03-19 21:46:35.609 Rest Counter[3776:707] First Time: 353900795.609040
 2012-03-19 21:46:36.360 Rest Counter[3776:707] Second Time: 353900796.360177
 2012-03-19 21:46:36.361 Rest Counter[3776:707] Final Result Time (difference): 0.751137
 2012-03-19 21:46:36.363 Rest Counter[3776:707] Mach absolute time: 18027372
 2012-03-19 21:46:36.365 Rest Counter[3776:707] Mach absolute time/final time: 24000113.153295
 2012-03-19 21:46:36.367 Rest Counter[3776:707] -----------------------------------------------------
 2012-03-19 21:46:43.074 Rest Counter[3776:707] First Time: 353900803.074637
 2012-03-19 21:46:43.170 Rest Counter[3776:707] Second Time: 353900803.170256
 2012-03-19 21:46:43.172 Rest Counter[3776:707] Final Result Time (difference): 0.095619
 2012-03-19 21:46:43.173 Rest Counter[3776:707] Mach absolute time: 2294833
 2012-03-19 21:46:43.175 Rest Counter[3776:707] Mach absolute time/final time: 23999753.727777
 2012-03-19 21:46:43.177 Rest Counter[3776:707] -----------------------------------------------------
 2012-03-19 21:46:46.499 Rest Counter[3776:707] First Time: 353900806.499199
 2012-03-19 21:46:55.017 Rest Counter[3776:707] Second Time: 353900815.016985
 2012-03-19 21:46:55.018 Rest Counter[3776:707] Final Result Time (difference): 8.517786
 2012-03-19 21:46:55.020 Rest Counter[3776:707] Mach absolute time: 204426836
 2012-03-19 21:46:55.022 Rest Counter[3776:707] Mach absolute time/final time: 23999996.639500
 2012-03-19 21:46:55.024 Rest Counter[3776:707] -----------------------------------------------------

Ho finito per usare mach_absolute_time()con a mach_timebase_info_datae poi l'ho fatto (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Per ottenere la base dei tempi di mach, puoi fare(void)mach_timebase_info(&your_timebase);
Nate Symer,

12
Secondo la documentazione per CFAbsoluteTimeGetCurrent (), "L'ora di sistema potrebbe diminuire a causa della sincronizzazione con riferimenti temporali esterni oa causa di una modifica esplicita dell'orologio da parte dell'utente". Non capisco perché qualcuno vorrebbe usare qualcosa di simile per misurare il tempo trascorso, se può tornare indietro.
Kristopher Johnson,

6
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Utilizzo:

CTTimeStart();
...
CTTimeEnd(@"that was a long time:");

Produzione:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023

NSDatedipende dall'orologio di sistema che può essere modificato in qualsiasi momento, risultando potenzialmente in intervalli di tempo errati o addirittura negativi. Utilizzare mach_absolute_timeinvece per ottenere il tempo trascorso corretto.
Cœur

mach_absolute_timepuò essere influenzato dai riavvii del dispositivo. Usa invece l'ora del server.
kelin

5

Inoltre, ecco come calcolare un 64 bit NSNumberinizializzato con l'epoca Unix in millisecondi, nel caso in cui sia così che si desidera memorizzarlo in CoreData. Ne avevo bisogno per la mia app che interagisce con un sistema che memorizza le date in questo modo.

  + (NSNumber*) longUnixEpoch {
      return [NSNumber numberWithLongLong:[[NSDate date] timeIntervalSince1970] * 1000];
  }

3

Le funzioni basate su mach_absolute_timesono utili per misurazioni brevi.
Ma per misurazioni lunghe, un avvertimento importante è che smettono di ticchettare mentre il dispositivo è addormentato.

C'è una funzione per ottenere tempo dall'avvio. Non si ferma durante il sonno. Inoltre, gettimeofdaynon è monotono, ma nei miei esperimenti ho sempre visto che l'ora di avvio cambia quando cambia l'ora di sistema, quindi penso che dovrebbe funzionare bene.

func timeSinceBoot() -> TimeInterval
{
    var bootTime = timeval()
    var currentTime = timeval()
    var timeZone = timezone()

    let mib = UnsafeMutablePointer<Int32>.allocate(capacity: 2)
    mib[0] = CTL_KERN
    mib[1] = KERN_BOOTTIME
    var size = MemoryLayout.size(ofValue: bootTime)

    var timeSinceBoot = 0.0

    gettimeofday(&currentTime, &timeZone)

    if sysctl(mib, 2, &bootTime, &size, nil, 0) != -1 && bootTime.tv_sec != 0 {
        timeSinceBoot = Double(currentTime.tv_sec - bootTime.tv_sec)
        timeSinceBoot += Double(currentTime.tv_usec - bootTime.tv_usec) / 1000000.0
    }
    return timeSinceBoot
}

E da iOS 10 e macOS 10.12 possiamo usare CLOCK_MONOTONIC:

if #available(OSX 10.12, *) {
    var uptime = timespec()
    if clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) == 0 {
        return Double(uptime.tv_sec) + Double(uptime.tv_nsec) / 1000000000.0
    }
}

Riassumendo:

  • Date.timeIntervalSinceReferenceDate - cambia quando cambia l'ora del sistema, non monotono
  • CFAbsoluteTimeGetCurrent() - non monotono, può tornare indietro
  • CACurrentMediaTime() - smette di ticchettare quando il dispositivo è addormentato
  • timeSinceBoot() - non dorme, ma potrebbe non essere monotono
  • CLOCK_MONOTONIC - non dorme, monotono, supportato da iOS 10

1

So che questo è vecchio ma anche io mi sono ritrovato a vagabondare di nuovo, quindi ho pensato di presentare la mia opzione qui.

La cosa migliore è dare un'occhiata al mio post sul blog su questo: Cronometrare le cose in Objective-C: un cronometro

Fondamentalmente, ho scritto una lezione che smette di guardare in un modo molto semplice ma è incapsulata in modo che tu debba fare solo quanto segue:

[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];

E ti ritroverai con:

MyApp[4090:15203]  -> Stopwatch: [My Timer] runtime: [0.029]

nel registro ...

Ancora una volta, controlla il mio post per ulteriori informazioni o scaricalo qui: MMStopwatch.zip


La tua implementazione non è consigliata. NSDatedipende dall'orologio di sistema che può essere modificato in qualsiasi momento, risultando potenzialmente in intervalli di tempo errati o addirittura negativi. Utilizzare mach_absolute_timeinvece per ottenere il tempo trascorso corretto.
Cœur

1

Puoi ottenere l'ora corrente in millisecondi dal 1 ° gennaio 1970 utilizzando NSDate:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}

Questo ti dà il tempo in millisecondi ma è ancora con precisione al secondo
dev

-1

Per quelli abbiamo bisogno della versione Swift della risposta di @Jeff Thompson:

// Get a current time for where you want to start measuring from
var date = NSDate()

// do work...

// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
var timePassed_ms: Double = date.timeIntervalSinceNow * -1000.0

Spero che questo ti sia d'aiuto.


2
NSDatedipende dall'orologio di sistema che può essere modificato in qualsiasi momento, risultando potenzialmente in intervalli di tempo errati o addirittura negativi. Utilizzare mach_absolute_timeinvece per ottenere il tempo trascorso corretto.
Cœur
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.