Wie kann ich in Objective-C eine genaue Zeit ermitteln, beispielsweise in Millisekunden?

99

Gibt es eine einfache Möglichkeit, eine Zeit sehr genau zu bestimmen?

Ich muss einige Verzögerungen zwischen Methodenaufrufen berechnen. Insbesondere möchte ich die Geschwindigkeit des Bildlaufs in einer UIScrollView berechnen.

Vielen Dank
quelle
Hier ist eine sehr verwandte Frage, die auch helfen kann, die Antworten hier zu verstehen. Bitte werfen Sie einen Blick darauf!
Abbood

Antworten:

127

NSDateund die timeIntervalSince*Methoden geben NSTimeIntervalein Double mit einer Genauigkeit von weniger als einer Millisekunde zurück. NSTimeIntervalist in Sekunden, aber es verwendet das Doppel, um Ihnen mehr Präzision zu geben.

Um die Millisekunden-Zeitgenauigkeit zu berechnen, haben Sie folgende Möglichkeiten:

// 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;

Dokumentation zu timeIntervalSinceNow .

Es gibt viele andere Möglichkeiten , um dieses Intervall zu berechnen verwenden NSDate, und ich würde in der Klasse Dokumentation empfehlen , sich für NSDatewelche gefunden wird NSDate Klassenreferenz .

Jeff Thompson
quelle
3
Tatsächlich ist dies für den allgemeinen Anwendungsfall genau genug.
Logancautrell
4
Ist es sicher, NSDates zur Berechnung der verstrichenen Zeit zu verwenden? Mir scheint, dass die Systemzeit bei der Synchronisierung mit externen Zeitquellen vorwärts oder rückwärts gehen kann, sodass Sie dem Ergebnis nicht vertrauen können. Sie wollen wirklich eine monoton ansteigende Uhr, nicht wahr?
Kristopher Johnson
1
Ich habe gerade verglichen NSDateund mach_absolute_time()auf ungefähr 30ms Niveau. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDatewar für mich einfacher zu verwenden und die Ergebnisse waren ähnlich genug, um sich nicht darum zu kümmern.
Nevan King
7
Es ist nicht sicher, NSDate zum Vergleichen der verstrichenen Zeit zu verwenden, da sich die Systemuhr jederzeit ändern kann (aufgrund von NTP, DST-Übergängen, Schaltsekunden und vielen anderen Gründen). Verwenden Sie stattdessen mach_absolute_time.
Nevyn
@nevyn du hast gerade meinen Internet-Geschwindigkeitstest gespeichert, ty! Verdammt, ich bin froh, dass ich die Kommentare gelesen habe, bevor ich den Code
Albert Renshaw
41

mach_absolute_time() kann verwendet werden, um genaue Messungen zu erhalten.

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

Ebenfalls erhältlich ist CACurrentMediaTime(), was im Wesentlichen dasselbe ist, jedoch mit einer benutzerfreundlicheren Oberfläche.

Kristopher Johnson
quelle
1
In diesem Code befindet sich eine seltsame Konvertierung - die letzte Zeile des ersten Beispiels lautet "return * (uint64_t *) & elapsedNano;" warum nicht einfach "return (uint64_t) elapsedNano"?
Tyler
8
Die Kernanimation (QuartzCore.framework) bietet auch eine praktische Methode, CACurrentMediaTime()die mach_absolute_time()direkt in eine konvertiert werden kann double.
Otto
1
@Tyler elapsedNanoist vom Typ Nanoseconds, der kein einfacher ganzzahliger Typ ist. Es ist ein Alias ​​von UnsignedWide, bei dem es sich um eine Struktur mit zwei 32-Bit-Ganzzahlfeldern handelt. Sie können UnsignedWideToUInt64()anstelle der Besetzung verwenden, wenn Sie es vorziehen.
Ken Thomases
CoreServices ist nur eine Mac-Bibliothek. Gibt es ein Äquivalent in iOS?
mm24
1
@ mm24 Verwenden Sie unter iOS CACurrentMediaTime (), das sich in Core Animation befindet.
Kristopher Johnson
28

Bitte verwenden Sie nicht NSDate, CFAbsoluteTimeGetCurrentoder die gettimeofdayverstrichene Zeit zu messen. Diese hängen alle auf dem Systemtakt, die ändern können jeder aufgrund vielen verschiedenen Gründen Zeit, wie zum Beispiel Netzwerk - Zeitsynchronisation (NTP) die Aktualisierung der Uhr (oft geschieht , um die Drift zu justieren), DST Anpassungen, Schaltsekunden, und so weiter.

Dies bedeutet, dass Sie beim Messen Ihrer Download- oder Upload-Geschwindigkeit plötzliche Spitzen oder Abnahmen in Ihren Zahlen erhalten, die nicht mit dem korrelieren, was tatsächlich passiert ist. Ihre Leistungstests haben seltsame falsche Ausreißer. und Ihre manuellen Timer werden nach falscher Dauer ausgelöst. Die Zeit könnte sogar rückwärts gehen, und Sie erhalten negative Deltas, und Sie können mit unendlicher Rekursion oder totem Code enden (ja, ich habe beides getan).

Verwenden Sie mach_absolute_time. Es misst echte Sekunden seit dem Booten des Kernels. Es nimmt monoton zu (wird niemals rückwärts gehen) und wird von den Einstellungen für Datum und Uhrzeit nicht beeinflusst. Da es ein Problem ist, damit zu arbeiten, finden Sie hier einen einfachen Wrapper, der Ihnen Folgendes bietet 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
Nevyn
quelle
2
Vielen Dank. Was ist mit CACurrentMediaTime()QuartzCore?
Cœur
1
Hinweis: Man kann auch @import Darwin;anstelle von#include <mach/mach_time.h>
Cœur
@ Cœur CACurrentMediaTime sollte eigentlich genau meinem Code entsprechen. Guter Fund! (Header sagt "Dies ist das Ergebnis des Aufrufs von mach_absolute_time () und der Konvertierung der Einheiten in Sekunden")
Nevyn
"Kann sich jederzeit aus vielen verschiedenen Gründen ändern, z. B. durch Aktualisierung der Uhr durch Network Time Sync (NTP) (häufig Anpassung an Drift), DST-Anpassungen, Schaltsekunden usw." - Welche anderen Gründe könnte das sein? Ich beobachte Rückwärtssprünge von ungefähr 50 ms, während ich keine Internetverbindung habe. Also, anscheinend sollte keiner dieser Gründe zutreffen ...
Falko
@Falko nicht sicher, aber wenn ich raten müsste, würde ich wetten, dass das Betriebssystem selbst eine Driftkompensation durchführt, wenn es feststellt, dass verschiedene Hardware-Uhren nicht synchron sind?
Nevyn
14

CFAbsoluteTimeGetCurrent()gibt die absolute Zeit als doubleWert zurück, aber ich weiß nicht, wie genau sie ist - sie wird möglicherweise nur alle Dutzend Millisekunden aktualisiert, oder sie wird möglicherweise alle Mikrosekunden aktualisiert, ich weiß nicht.

Adam Rosenfield
quelle
2
Es handelt sich jedoch um einen Gleitkommawert mit doppelter Genauigkeit, der eine Genauigkeit von weniger als einer Millisekunde bietet. Ein Wert von 72,89674947369 Sekunden ist nicht ungewöhnlich ...
Jim Dovey
25
@ JimDovey: Ich muss sagen, ein Wert von 72.89674947369Sekunden wäre ziemlich ungewöhnlich, wenn man alle anderen Werte berücksichtigt, die er sein könnte. ;)
FreeAsInBeer
3
@ Jim: Haben Sie ein Zitat, das eine Genauigkeit von weniger als einer Millisekunde liefert (dies ist eine ehrliche Frage)? Ich hoffe, jeder hier versteht die Unterschiede zwischen Genauigkeit und Präzision .
Adam Rosenfield
5
CFAbsoluteTimeGetCurrent () ruft gettimeofday () unter OS X und GetSystemTimeAsFileTime () unter Windows auf. Hier ist der Quellcode .
Jim Dovey
3
Oh, und gettimeofday () wird mithilfe des Mach-Nanosekunden-Timers über mach_absolute_time () implementiert . Hier ist die Quelle für die Common-Page-Implementierung von gettimeofday () auf Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey
10

Ich würde NICHT verwenden, mach_absolute_time()da es eine Kombination aus Kernel und Prozessor für eine absolute Zeit mit Ticks abfragt (wahrscheinlich eine Verfügbarkeit).

Was ich verwenden würde:

CFAbsoluteTimeGetCurrent();

Diese Funktion wurde optimiert, um den Unterschied zwischen iOS- und OSX-Software und -Hardware zu korrigieren.

Etwas Geekier

Der Quotient einer Differenz in mach_absolute_time()und AFAbsoluteTimeGetCurrent()liegt immer bei 24000011.154871

Hier ist ein Protokoll meiner App:

Bitte beachten Sie, dass Endergebnisses Es ist ein Unterschied in 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] -----------------------------------------------------
Nate Symer
quelle
Am Ende benutzte ich mach_absolute_time()ein mach_timebase_info_dataund tat es dann (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Um die Mach-Zeitbasis zu erhalten, können Sie tun(void)mach_timebase_info(&your_timebase);
Nate Symer
12
In den Dokumenten für CFAbsoluteTimeGetCurrent () heißt es: "Die Systemzeit kann sich aufgrund der Synchronisation mit externen Zeitreferenzen oder aufgrund einer expliziten Änderung der Uhr durch den Benutzer verringern." Ich verstehe nicht, warum jemand so etwas verwenden möchte, um die verstrichene Zeit zu messen, wenn es rückwärts gehen kann.
Kristopher Johnson
6
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Verwendung:

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

Ausgabe:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
gngrwzrd
quelle
NSDatehängt von der Systemuhr ab, die jederzeit geändert werden kann, was möglicherweise zu falschen oder sogar negativen Zeitintervallen führt. Verwenden Sie mach_absolute_timestattdessen, um die korrekte verstrichene Zeit zu erhalten.
Cœur
mach_absolute_timekann durch Neustarts des Geräts beeinträchtigt werden. Verwenden Sie stattdessen die Serverzeit.
Kelin
5

Außerdem NSNumbererfahren Sie hier, wie Sie ein mit der Unix-Epoche initialisiertes 64-Bit in Millisekunden berechnen , falls Sie es auf diese Weise in CoreData speichern möchten. Ich brauchte dies für meine App, die mit einem System interagiert, das Daten auf diese Weise speichert.

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

Funktionen basierend auf mach_absolute_timesind gut für kurze Messungen.
Bei langen Messungen ist es jedoch wichtig, dass sie nicht mehr ticken, während das Gerät schläft.

Es gibt eine Funktion, um Zeit seit dem Booten zu bekommen. Es hört im Schlaf nicht auf. Auch gettimeofdayist nicht monoton, aber in meinen Experimenten habe ich immer gesehen, dass sich die Startzeit ändert, wenn sich die Systemzeit ändert, also denke ich, dass es gut funktionieren sollte.

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
}

Und seit iOS 10 und macOS 10.12 können wir CLOCK_MONOTONIC verwenden:

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
    }
}

Etwas zusammenfassen:

  • Date.timeIntervalSinceReferenceDate - ändert sich, wenn sich die Systemzeit ändert, nicht monoton
  • CFAbsoluteTimeGetCurrent() - nicht monoton, kann rückwärts gehen
  • CACurrentMediaTime() - hört auf zu ticken, wenn das Gerät schläft
  • timeSinceBoot() - schläft nicht, ist aber möglicherweise nicht monoton
  • CLOCK_MONOTONIC - schläft nicht, monoton, wird seit iOS 10 unterstützt
Pavel Alexeev
quelle
1

Ich weiß, dass dies eine alte ist, aber selbst ich bin wieder daran vorbeigekommen, also dachte ich, ich würde hier meine eigene Option einreichen.

Am besten schau dir meinen Blog-Beitrag dazu an: Timing der Dinge in Objective-C: Eine Stoppuhr

Grundsätzlich habe ich eine Klasse geschrieben, die auf sehr einfache Weise aufhört zu schauen, aber gekapselt ist, sodass Sie nur Folgendes tun müssen:

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

Und am Ende haben Sie:

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

im Protokoll ...

Schauen Sie sich meinen Beitrag noch einmal an oder laden Sie ihn hier herunter: MMStopwatch.zip

Bladnman
quelle
Ihre Implementierung wird nicht empfohlen. NSDatehängt von der Systemuhr ab, die jederzeit geändert werden kann, was möglicherweise zu falschen oder sogar negativen Zeitintervallen führt. Verwenden Sie mach_absolute_timestattdessen, um die korrekte verstrichene Zeit zu erhalten.
Cœur
1

Sie können die aktuelle Zeit in Millisekunden seit dem 1. Januar 1970 mit einem NSDate abrufen:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}
Camilo Ortegón
quelle
Dies gibt Ihnen Zeit in Millisekunden, aber es ist immer noch mit Sekundengenauigkeit
dev
-1

Für diejenigen brauchen wir die Swift-Version der Antwort von @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

Ich hoffe das hilft dir.

Victor Sigler
quelle
2
NSDatehängt von der Systemuhr ab, die jederzeit geändert werden kann, was möglicherweise zu falschen oder sogar negativen Zeitintervallen führt. Verwenden Sie mach_absolute_timestattdessen, um die korrekte verstrichene Zeit zu erhalten.
Cœur