Czy istnieje łatwy sposób na bardzo dokładne określenie czasu?
Muszę obliczyć pewne opóźnienia między wywołaniami metod. Dokładniej, chcę obliczyć szybkość przewijania w UIScrollView.
Czy istnieje łatwy sposób na bardzo dokładne określenie czasu?
Muszę obliczyć pewne opóźnienia między wywołaniami metod. Dokładniej, chcę obliczyć szybkość przewijania w UIScrollView.
Odpowiedzi:
NSDate
a timeIntervalSince*
metody zwrócą NSTimeInterval
wartość podwójną z dokładnością poniżej milisekundy. NSTimeInterval
zajmuje kilka sekund, ale używa podwójnej wartości, aby zapewnić większą precyzję.
Aby obliczyć dokładność milisekundową, możesz wykonać:
// 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;
Dokumentacja na czasIntervalSinceNow .
Istnieje wiele innych sposobów obliczania tego przedziału za pomocą NSDate
i polecam przejrzenie dokumentacji klas, dla NSDate
której można znaleźć w NSDate Class Reference .
NSDate
i mach_absolute_time()
na poziomie około 30 ms. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDate
było dla mnie łatwiejsze w użyciu, a wyniki były na tyle podobne, że nie przejmowałem się.
mach_absolute_time()
można wykorzystać do uzyskania precyzyjnych pomiarów.
Zobacz http://developer.apple.com/qa/qa2004/qa1398.html
Dostępny jest również CACurrentMediaTime()
, który jest zasadniczo tym samym, ale z łatwiejszym w użyciu interfejsem.
(Uwaga: ta odpowiedź została napisana w 2009 roku. Zobacz odpowiedź Pawła Aleksiejewa na temat prostszych clock_gettime()
interfejsów POSIX dostępnych w nowszych wersjach macOS i iOS).
CACurrentMediaTime()
, która konwertuje mach_absolute_time()
bezpośrednio na plik double
.
elapsedNano
jest typu Nanoseconds
, który nie jest zwykłą liczbą całkowitą. Jest to alias programu UnsignedWide
, który jest strukturą z dwoma 32-bitowymi polami całkowitymi. UnsignedWideToUInt64()
Jeśli wolisz, możesz użyć zamiast obsady.
Proszę nie używać NSDate
, CFAbsoluteTimeGetCurrent
lub gettimeofday
zmierzyć czas, który upłynął. Wszystko to zależy od zegara systemowego, który może się zmienić w dowolnym momencie z wielu różnych powodów, takich jak synchronizacja czasu sieciowego (NTP), aktualizacja zegara (często w celu dostosowania do dryfu), regulacja czasu letniego, sekundy przestępne i tak dalej.
Oznacza to, że jeśli mierzysz prędkość pobierania lub wysyłania, otrzymasz nagłe skoki lub spadki liczb, które nie są powiązane z tym, co faktycznie się wydarzyło; twoje testy wydajności będą miały dziwne, nieprawidłowe wartości odstające; a ręczne zegary uruchomią się po nieprawidłowym czasie trwania. Czas może nawet cofnąć się, a skończysz z ujemnymi deltami i możesz skończyć z nieskończoną rekurencją lub martwym kodem (tak, zrobiłem oba te).
Użyj mach_absolute_time
. Mierzy rzeczywiste sekundy od momentu uruchomienia jądra. Zwiększa się monotonicznie (nigdy nie cofa się) i nie ma na nie wpływu ustawienia daty i godziny. Ponieważ praca z nią jest trudna, oto proste opakowanie, które daje 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()
z QuartzCore?
@import Darwin;
zamiast#include <mach/mach_time.h>
CFAbsoluteTimeGetCurrent()
zwraca bezwzględny czas jako double
wartość, ale nie wiem, jaka jest jego dokładność - może aktualizować się tylko co kilkanaście milisekund lub może aktualizować co mikrosekundę, nie wiem.
72.89674947369
sekund byłaby dość rzadka, biorąc pod uwagę wszystkie inne wartości, które mogłyby to być. ;)
NIE użyłbym, mach_absolute_time()
ponieważ pyta o kombinację jądra i procesora przez absolutny czas za pomocą tików (prawdopodobnie uptime).
Czego bym użył:
CFAbsoluteTimeGetCurrent();
Ta funkcja jest zoptymalizowana pod kątem korygowania różnic w oprogramowaniu i sprzęcie iOS i OSX.
Coś bardziej geekowskiego
Iloraz różnicy mach_absolute_time()
i AFAbsoluteTimeGetCurrent()
jest zawsze wokół 24000011.154871
Oto dziennik mojej aplikacji:
Należy pamiętać, że końcowy wynik jest czas różnica w 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()
z a, mach_timebase_info_data
a potem zrobiłem (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));
. Aby uzyskać (void)mach_timebase_info(&your_timebase);
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)
Stosowanie:
CTTimeStart();
...
CTTimeEnd(@"that was a long time:");
Wynik:
2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
NSDate
zależy od zegara systemowego, który można zmienić w dowolnym momencie, potencjalnie powodując błędne lub nawet ujemne odstępy czasu. Użyj mach_absolute_time
zamiast tego, aby uzyskać prawidłowy czas, który upłynął.
mach_absolute_time
może mieć wpływ na ponowne uruchomienie urządzenia. Zamiast tego użyj czasu serwera.
Tutaj jest również sposób obliczania 64-bitowego NSNumber
zainicjowanego z epoką Uniksa w milisekundach, na wypadek, gdyby tak było w przypadku przechowywania w CoreData. Potrzebowałem tego dla mojej aplikacji, która współpracuje z systemem przechowującym daty w ten sposób.
+ (NSNumber*) longUnixEpoch {
return [NSNumber numberWithLongLong:[[NSDate date] timeIntervalSince1970] * 1000];
}
Oparte na mach_absolute_time
nich funkcje nadają się do krótkich pomiarów.
Ale w przypadku długich pomiarów ważnym zastrzeżeniem jest to, że przestają tykać, gdy urządzenie śpi.
Istnieje funkcja pozwalająca uzyskać czas od momentu uruchomienia. Nie zatrzymuje się podczas snu. Nie gettimeofday
jest też monotoniczny, ale w moich eksperymentach zawsze widziałem, że czas uruchamiania zmienia się wraz ze zmianą czasu systemowego, więc myślę, że powinien działać dobrze.
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
}
A od iOS 10 i macOS 10.12 możemy korzystać z 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
}
}
Podsumowując:
Date.timeIntervalSinceReferenceDate
- zmienia się wraz ze zmianą czasu systemowego, a nie monotonicznieCFAbsoluteTimeGetCurrent()
- nie monotonna, może cofać sięCACurrentMediaTime()
- przestaje tykać, gdy urządzenie śpitimeSinceBoot()
- nie śpi, ale może nie być monotonnyCLOCK_MONOTONIC
- nie śpi, monotonicznie, obsługiwane od iOS 10Wiem, że jest to stara, ale nawet ja ponownie przechodzę obok niej, więc pomyślałem, że przedstawię tutaj własną opcję.
Najlepiej sprawdzić mój wpis na blogu na ten temat: Czas rzeczy w Objective-C: Stoper
Zasadniczo napisałem zajęcia, które przestają oglądać w bardzo prosty sposób, ale są zamknięte, więc wystarczy wykonać następujące czynności:
[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];
Otrzymujesz:
MyApp[4090:15203] -> Stopwatch: [My Timer] runtime: [0.029]
w dzienniku ...
Ponownie, sprawdź mój post po trochę więcej lub pobierz go tutaj: MMStopwatch.zip
NSDate
zależy od zegara systemowego, który można zmienić w dowolnym momencie, potencjalnie powodując błędne lub nawet ujemne odstępy czasu. Użyj mach_absolute_time
zamiast tego, aby uzyskać prawidłowy czas, który upłynął.
Możesz uzyskać aktualny czas w milisekundach od 1 stycznia 1970 roku za pomocą NSDate:
- (double)currentTimeInMilliseconds {
NSDate *date = [NSDate date];
return [date timeIntervalSince1970]*1000;
}
Dla tych potrzebujemy szybkiej wersji odpowiedzi @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
Mam nadzieję, że to ci pomoże.
NSDate
zależy od zegara systemowego, który można zmienić w dowolnym momencie, potencjalnie powodując błędne lub nawet ujemne odstępy czasu. Użyj mach_absolute_time
zamiast tego, aby uzyskać prawidłowy czas, który upłynął.