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.
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.
Antworten:
NSDate
und die timeIntervalSince*
Methoden geben NSTimeInterval
ein Double mit einer Genauigkeit von weniger als einer Millisekunde zurück. NSTimeInterval
ist 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 NSDate
welche gefunden wird NSDate Klassenreferenz .
NSDate
und mach_absolute_time()
auf ungefähr 30ms Niveau. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDate
war für mich einfacher zu verwenden und die Ergebnisse waren ähnlich genug, um sich nicht darum zu kümmern.
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.
CACurrentMediaTime()
die mach_absolute_time()
direkt in eine konvertiert werden kann double
.
elapsedNano
ist 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.
Bitte verwenden Sie nicht NSDate
, CFAbsoluteTimeGetCurrent
oder die gettimeofday
verstrichene 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
CACurrentMediaTime()
QuartzCore?
@import Darwin;
anstelle von#include <mach/mach_time.h>
CFAbsoluteTimeGetCurrent()
gibt die absolute Zeit als double
Wert 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.
72.89674947369
Sekunden wäre ziemlich ungewöhnlich, wenn man alle anderen Werte berücksichtigt, die er sein könnte. ;)
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] -----------------------------------------------------
mach_absolute_time()
ein mach_timebase_info_data
und 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);
#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
NSDate
hä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_time
stattdessen, um die korrekte verstrichene Zeit zu erhalten.
mach_absolute_time
kann durch Neustarts des Geräts beeinträchtigt werden. Verwenden Sie stattdessen die Serverzeit.
Außerdem NSNumber
erfahren 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];
}
Funktionen basierend auf mach_absolute_time
sind 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 gettimeofday
ist 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(¤tTime, &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 monotonCFAbsoluteTimeGetCurrent()
- nicht monoton, kann rückwärts gehenCACurrentMediaTime()
- hört auf zu ticken, wenn das Gerät schläfttimeSinceBoot()
- schläft nicht, ist aber möglicherweise nicht monotonCLOCK_MONOTONIC
- schläft nicht, monoton, wird seit iOS 10 unterstütztIch 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
NSDate
hä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_time
stattdessen, um die korrekte verstrichene Zeit zu erhalten.
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;
}
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.
NSDate
hä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_time
stattdessen, um die korrekte verstrichene Zeit zu erhalten.