Jak mogę uzyskać dokładny czas, na przykład w milisekundach w Objective-C?

99

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.

Dzięki
źródło
tutaj jest bardzo pokrewne pytanie, które może również pomóc w zrozumieniu odpowiedzi tutaj ... proszę spojrzeć!
opactwo

Odpowiedzi:

127

NSDatea timeIntervalSince*metody zwrócą NSTimeIntervalwartość podwójną z dokładnością poniżej milisekundy. NSTimeIntervalzajmuje 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ą NSDatei polecam przejrzenie dokumentacji klas, dla NSDatektórej można znaleźć w NSDate Class Reference .

Jeff Thompson
źródło
3
W rzeczywistości jest to wystarczająco precyzyjne dla ogólnego przypadku użycia.
logancautrell
4
Czy używanie NSDates do obliczania upływającego czasu jest bezpieczne? Wydaje mi się, że czas systemowy może przesunąć się do przodu lub do tyłu podczas synchronizacji z zewnętrznymi źródłami czasu, więc nie byłbyś w stanie zaufać wynikowi. Naprawdę chcesz monotonnie rosnącego zegara, prawda?
Kristopher Johnson
1
Właśnie porównałem NSDatei mach_absolute_time()na poziomie około 30 ms. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDatebyło dla mnie łatwiejsze w użyciu, a wyniki były na tyle podobne, że nie przejmowałem się.
nevan king
7
Używanie NSDate do porównywania czasu, który upłynął, nie jest bezpieczne, ponieważ zegar systemowy może się zmienić w dowolnym momencie (z powodu przejść NTP, czasu letniego, sekund przestępnych i wielu innych powodów). Zamiast tego użyj mach_absolute_time.
Nevyn
@nevyn właśnie zapisałeś mój test szybkości internetu, ty! Cholera, cieszę się, że przeczytałem komentarze przed skopiowaniem kodu wklejania heh
Albert Renshaw
41

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).

Kristopher Johnson
źródło
1
W tym kodzie jest dziwna konwersja - ostatnia linia pierwszego przykładu to „return * (uint64_t *) & elapsedNano;” dlaczego nie po prostu „return (uint64_t) elapsedNano”?
Tyler
8
Core Animation (QuartzCore.framework) zapewnia również wygodną metodę CACurrentMediaTime(), która konwertuje mach_absolute_time()bezpośrednio na plik double.
otto
1
@Tyler elapsedNanojest 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.
Ken Thomases,
CoreServices to tylko biblioteka dla komputerów Mac. Czy istnieje odpowiednik w iOS?
mm24
1
@ mm24 Na iOS użyj CACurrentMediaTime (), który jest w Core Animation.
Kristopher Johnson,
28

Proszę nie używać NSDate, CFAbsoluteTimeGetCurrentlub gettimeofdayzmierzyć 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
nevyn
źródło
2
Dzięki. A co CACurrentMediaTime()z QuartzCore?
Cœur
1
Uwaga: można również użyć @import Darwin;zamiast#include <mach/mach_time.h>
Cœur
@ Cœur CACurrentMediaTime powinien być dokładnie odpowiednikiem mojego kodu. Dobre znalezisko! (Nagłówek mówi „To jest wynik wywołania mach_absolute_time () i zamiany jednostek na sekundy”)
nevyn
„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), zmiany czasu letniego, sekundy przestępne i tak dalej”. - Jakie mogą to być inne powody? Obserwuję skoki wstecz o około 50 ms bez połączenia z Internetem. Więc najwyraźniej żaden z tych powodów nie powinien mieć zastosowania ...
Falko
@Falko nie jestem pewien, ale gdybym musiał zgadywać, założyłbym się, że system operacyjny sam dokonuje kompensacji dryfu, jeśli zauważy, że różne zegary sprzętowe nie są zsynchronizowane?
Nevyn
14

CFAbsoluteTimeGetCurrent()zwraca bezwzględny czas jako doublewartość, 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.

Adam Rosenfield
źródło
2
Jest to jednak wartość zmiennoprzecinkowa podwójnej precyzji i zapewnia dokładność poniżej milisekundy. Wartość 72,89674947369 sekund nie jest niczym niezwykłym ...
Jim Dovey
25
@JimDovey: Muszę powiedzieć, że wartość 72.89674947369sekund byłaby dość rzadka, biorąc pod uwagę wszystkie inne wartości, które mogłyby to być. ;)
FreeAsInBeer
3
@Jim: Czy masz cytat, który zapewnia dokładność poniżej milisekundy (to jest uczciwe pytanie)? Mam nadzieję, że wszyscy tutaj rozumieją różnice między dokładnością a precyzją .
Adam Rosenfield
5
CFAbsoluteTimeGetCurrent () wywołuje gettimeofday () w systemie OS X i GetSystemTimeAsFileTime () w systemie Windows. Oto kod źródłowy .
Jim Dovey,
3
Aha, i funkcja gettimeofday () jest zaimplementowana przy użyciu zegara nanosekundowego Macha poprzez mach_absolute_time () ; tutaj jest źródło implementacji gettimeofday () ze wspólnej strony w Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey
10

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] -----------------------------------------------------
Nate Symer
źródło
Skończyło się na używaniu mach_absolute_time()z a, mach_timebase_info_dataa potem zrobiłem (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Aby uzyskać (void)mach_timebase_info(&your_timebase);
podstawę
12
Zgodnie z dokumentacją CFAbsoluteTimeGetCurrent (), „Czas systemowy może ulec skróceniu z powodu synchronizacji z zewnętrznymi odniesieniami czasu lub z powodu jawnej zmiany zegara przez użytkownika”. Nie rozumiem, dlaczego ktoś miałby chcieć użyć czegoś takiego do pomiaru upływającego czasu, jeśli można go cofnąć.
Kristopher Johnson
6
#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
gngrwzrd
źródło
NSDatezależ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_timezamiast tego, aby uzyskać prawidłowy czas, który upłynął.
Cœur
mach_absolute_timemoże mieć wpływ na ponowne uruchomienie urządzenia. Zamiast tego użyj czasu serwera.
kelin
5

Tutaj jest również sposób obliczania 64-bitowego NSNumberzainicjowanego 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];
  }
John Wright
źródło
3

Oparte na mach_absolute_timenich 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 gettimeofdayjest 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(&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
}

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 monotonicznie
  • CFAbsoluteTimeGetCurrent() - nie monotonna, może cofać się
  • CACurrentMediaTime() - przestaje tykać, gdy urządzenie śpi
  • timeSinceBoot() - nie śpi, ale może nie być monotonny
  • CLOCK_MONOTONIC - nie śpi, monotonicznie, obsługiwane od iOS 10
Pavel Alexeev
źródło
1

Wiem, ż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

bladnman
źródło
Twoja implementacja nie jest zalecana. NSDatezależ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_timezamiast tego, aby uzyskać prawidłowy czas, który upłynął.
Coœur
1

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;
}
Camilo Ortegón
źródło
Daje to czas w milisekundach, ale wciąż z dokładnością do sekund
dev
-1

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.

Victor Sigler
źródło
2
NSDatezależ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_timezamiast tego, aby uzyskać prawidłowy czas, który upłynął.
Cœur