Bagaimana saya bisa mendapatkan waktu yang tepat, misalnya dalam milidetik di Objective-C?

99

Adakah cara mudah untuk mendapatkan waktu dengan sangat tepat?

Saya perlu menghitung beberapa penundaan antara panggilan metode. Lebih khusus lagi, saya ingin menghitung kecepatan scrolling di UIScrollView.

Terima kasih
sumber
di sini adalah pertanyaan yang sangat terkait yang juga dapat membantu memahami jawabannya di sini .. silakan lihat!
abbood

Jawaban:

127

NSDatedan timeIntervalSince*metode akan mengembalikan NSTimeIntervalnilai ganda dengan akurasi sub-milidetik. NSTimeIntervaldalam hitungan detik, tapi menggunakan ganda untuk memberi Anda presisi yang lebih baik.

Untuk menghitung akurasi waktu milidetik, Anda dapat melakukan:

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

Dokumentasi tentang timeIntervalSinceNow .

Ada banyak cara lain untuk menghitung interval ini menggunakan NSDate, dan saya akan merekomendasikan melihat dokumentasi kelas NSDateyang ditemukan di Referensi Kelas NSDate .

Jeff Thompson
sumber
3
Sebenarnya ini cukup tepat untuk kasus penggunaan umum.
logancautrell
4
Apakah aman menggunakan NSDates untuk menghitung waktu yang telah berlalu? Menurut saya waktu sistem dapat maju atau mundur saat menyinkronkan ke sumber waktu eksternal, jadi Anda tidak akan dapat mempercayai hasilnya. Anda benar-benar menginginkan jam yang meningkat secara monoton, bukan?
Kristopher Johnson
1
Saya baru saja membandingkan NSDatedan mach_absolute_time()pada level sekitar 30ms. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDatelebih mudah digunakan bagi saya dan hasilnya cukup mirip untuk tidak peduli.
raja nevan
7
Tidak aman menggunakan NSDate untuk membandingkan waktu yang telah berlalu, karena jam sistem dapat berubah kapan saja (karena NTP, transisi DST, detik kabisat, dan banyak alasan lainnya). Gunakan mach_absolute_time sebagai gantinya.
nevyn
@nevyn Anda baru saja menyimpan tes kecepatan internet saya, ty! Sial, saya senang saya membaca komentar sebelum menyalin kode tempel heh
Albert Renshaw
41

mach_absolute_time() dapat digunakan untuk mendapatkan pengukuran yang tepat.

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

Juga tersedia CACurrentMediaTime(), yang pada dasarnya sama tetapi dengan antarmuka yang lebih mudah digunakan.

(Catatan: Jawaban ini ditulis pada tahun 2009. Lihat jawaban Pavel Alexeev untuk clock_gettime()antarmuka POSIX yang lebih sederhana yang tersedia di versi macOS dan iOS yang lebih baru.)

Kristopher Johnson
sumber
1
Dalam kode itu terdapat konversi yang aneh - baris terakhir dari contoh pertama adalah "return * (uint64_t *) & elapsedNano;" mengapa tidak hanya "return (uint64_t) elapsedNano"?
Tyler
8
Core Animation (QuartzCore.framework) juga menyediakan metode praktis CACurrentMediaTime(), yang mach_absolute_time()langsung diubah menjadi file double.
otto
1
@ Tyler, elapsedNanoadalah tipe Nanoseconds, yang bukan tipe integer biasa. Ini adalah alias dari UnsignedWide, yang merupakan struct dengan dua bidang integer 32-bit. Anda dapat menggunakan UnsignedWideToUInt64()sebagai pengganti cast jika mau.
Ken Thomases
CoreServices hanyalah pustaka Mac. Apakah ada yang setara di iOS?
mm24
1
@ mm24 Di iOS, gunakan CACurrentMediaTime (), yang ada di Core Animation.
Kristopher Johnson
28

Jangan gunakan NSDate, CFAbsoluteTimeGetCurrentatau gettimeofdayuntuk mengukur waktu berlalu. Ini semua bergantung pada jam sistem, yang dapat berubah kapan saja karena berbagai alasan, seperti sinkronisasi waktu jaringan (NTP) memperbarui jam (sering terjadi untuk menyesuaikan penyimpangan), penyesuaian DST, detik kabisat, dan sebagainya.

Ini berarti bahwa jika Anda mengukur kecepatan unduh atau unggah, Anda akan mendapatkan lonjakan atau penurunan tiba-tiba pada angka Anda yang tidak berkorelasi dengan apa yang sebenarnya terjadi; tes kinerja Anda akan memiliki pencilan aneh yang salah; dan timer manual Anda akan terpicu setelah durasi yang salah. Waktu bahkan mungkin mundur, dan Anda berakhir dengan delta negatif, dan Anda bisa berakhir dengan rekursi tak terbatas atau kode mati (ya, saya telah melakukan keduanya).

Gunakan mach_absolute_time. Ini mengukur detik nyata sejak kernel di-boot. Ini meningkat secara monoton (tidak akan pernah mundur), dan tidak terpengaruh oleh pengaturan tanggal dan waktu. Karena merepotkan untuk dikerjakan, berikut bungkus sederhana yang memberi Anda 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
sumber
2
Terima kasih. Bagaimana dengan CACurrentMediaTime()QuartzCore?
Cœur
1
Catatan: satu juga dapat menggunakan @import Darwin;bukannya#include <mach/mach_time.h>
Cœur
@ Cœur CACurrentMediaTime seharusnya sama persis dengan kode saya. Temuan bagus! (Header mengatakan "Ini adalah hasil dari pemanggilan mach_absolute_time () dan mengubah unit menjadi detik")
nevyn
"dapat berubah kapan saja karena berbagai alasan, seperti sinkronisasi waktu jaringan (NTP) memperbarui jam (sering terjadi untuk menyesuaikan penyimpangan), penyesuaian DST, detik kabisat, dan sebagainya." - Alasan apa lagi? Saya mengamati lompatan mundur sekitar 50 md saat tidak memiliki koneksi internet. Jadi, tampaknya, tidak satupun dari alasan ini yang berlaku ...
Falko
@Falko tidak yakin, tetapi jika saya harus menebak, saya berani bertaruh OS melakukan kompensasi drift sendiri jika melihat jam perangkat keras yang berbeda tidak sinkron?
nevyn
14

CFAbsoluteTimeGetCurrent()mengembalikan waktu absolut sebagai sebuah doublenilai, tetapi saya tidak tahu apa ketepatannya - mungkin hanya memperbarui setiap lusin milidetik, atau mungkin memperbarui setiap mikrodetik, saya tidak tahu.

Adam Rosenfield
sumber
2
Ini adalah nilai floating-point presisi ganda, dan memang memberikan akurasi sub-milidetik. Nilai 72,89674947369 detik tidak jarang ...
Jim Dovey
25
@JimDovey: Saya harus mengatakan nilai 72.89674947369detik akan sangat tidak umum, mengingat semua nilai lain yang mungkin terjadi. ;)
FreeAsInBeer
3
@ Jim: Apakah Anda memiliki kutipan yang memberikan akurasi sub-milidetik (ini adalah pertanyaan yang jujur)? Saya berharap semua orang di sini memahami perbedaan antara akurasi dan presisi .
Adam Rosenfield
5
CFAbsoluteTimeGetCurrent () memanggil gettimeofday () di OS X dan GetSystemTimeAsFileTime () di Windows. Berikut kode sumbernya .
Jim Dovey
3
Oh, dan gettimeofday () diimplementasikan menggunakan timer Mach nanodetik melalui mach_absolute_time () ; berikut adalah sumber implementasi halaman umum gettimeofday () di Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey
10

Saya TIDAK akan menggunakan mach_absolute_time()karena menanyakan kombinasi kernel dan prosesor untuk waktu absolut menggunakan kutu (mungkin uptime).

Apa yang akan saya gunakan:

CFAbsoluteTimeGetCurrent();

Fungsi ini dioptimalkan untuk memperbaiki perbedaan pada perangkat lunak dan perangkat keras iOS dan OSX.

Sesuatu yang lebih Geekier

Hasil bagi dari selisih mach_absolute_time()dan AFAbsoluteTimeGetCurrent()selalu sekitar 24000011.154871

Ini adalah log aplikasi saya:

Harap dicatat bahwa waktu hasil akhir perbedaan 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] -----------------------------------------------------
Nate Symer
sumber
Saya akhirnya menggunakan mach_absolute_time()dengan mach_timebase_info_datadan kemudian melakukannya (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Untuk mendapatkan basis waktu mach, Anda dapat melakukannya(void)mach_timebase_info(&your_timebase);
Nate Symer
12
Menurut dokumen untuk CFAbsoluteTimeGetCurrent (), "Waktu sistem dapat berkurang karena sinkronisasi dengan referensi waktu eksternal atau karena perubahan jam yang eksplisit oleh pengguna." Saya tidak mengerti mengapa ada orang yang ingin menggunakan sesuatu seperti ini untuk mengukur waktu yang telah berlalu, jika bisa mundur.
Kristopher Johnson
6
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Pemakaian:

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

Keluaran:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
gngrwzrd.dll
sumber
NSDatebergantung pada jam sistem yang dapat diubah kapan saja, berpotensi menghasilkan interval waktu yang salah atau bahkan negatif. Gunakan mach_absolute_timesebagai gantinya untuk mendapatkan waktu berlalu yang benar.
Cœur
mach_absolute_timedapat terpengaruh oleh reboot perangkat. Gunakan waktu server sebagai gantinya.
kelin
5

Selain itu, berikut adalah cara menghitung 64-bit yang NSNumberdiinisialisasi dengan epoch Unix dalam milidetik, jika Anda ingin menyimpannya di CoreData. Saya membutuhkan ini untuk aplikasi saya yang berinteraksi dengan sistem yang menyimpan tanggal dengan cara ini.

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

Fungsi berdasarkan mach_absolute_timebagus untuk pengukuran singkat.
Namun untuk pengukuran yang lama, peringatan penting adalah bahwa mereka berhenti berdetak saat perangkat sedang tidur.

Ada fungsi untuk mendapatkan waktu sejak boot. Itu tidak berhenti saat tidur. Juga, gettimeofdayini tidak monotonik, tetapi dalam percobaan saya, saya selalu melihat bahwa waktu boot berubah ketika waktu sistem berubah, jadi saya pikir itu akan bekerja dengan baik.

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
}

Dan karena iOS 10 dan macOS 10.12 kita dapat menggunakan 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
    }
}

Singkatnya:

  • Date.timeIntervalSinceReferenceDate - berubah ketika waktu sistem berubah, tidak monotonik
  • CFAbsoluteTimeGetCurrent() - tidak monotonik, mungkin mundur
  • CACurrentMediaTime() - berhenti berdetak saat perangkat tidur
  • timeSinceBoot() - tidak tidur, tapi mungkin tidak monotonik
  • CLOCK_MONOTONIC - tidak tidur, monoton, didukung sejak iOS 10
Pavel Alexeev
sumber
1

Saya tahu ini adalah yang lama tetapi bahkan saya mendapati diri saya melewatinya lagi, jadi saya pikir saya akan mengirimkan pilihan saya sendiri di sini.

Taruhan terbaik adalah memeriksa posting blog saya tentang ini : Pengaturan waktu di Objective-C: Sebuah stopwatch

Pada dasarnya, saya menulis kelas yang berhenti menonton dengan cara yang sangat dasar tetapi dikemas sehingga Anda hanya perlu melakukan hal berikut:

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

Dan Anda berakhir dengan:

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

di log ...

Sekali lagi, periksa posting saya untuk lebih banyak atau unduh di sini: MMStopwatch.zip

bladnman
sumber
Penerapan Anda tidak disarankan. NSDatebergantung pada jam sistem yang dapat diubah kapan saja, berpotensi menghasilkan interval waktu yang salah atau bahkan negatif. Gunakan mach_absolute_timesebagai gantinya untuk mendapatkan waktu berlalu yang benar.
Cœur
1

Anda bisa mendapatkan waktu saat ini dalam milidetik sejak 1 Januari 1970 menggunakan NSDate:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}
Camilo Ortegón
sumber
Ini memberi Anda waktu dalam milidetik tetapi tetap pada presisi detik
dev
-1

Bagi mereka, kami membutuhkan versi Swift dari jawaban @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

Saya harap ini membantu Anda.

Victor Sigler
sumber
2
NSDatetergantung pada jam sistem yang dapat diubah kapan saja, berpotensi menghasilkan interval waktu yang salah atau bahkan negatif. Menggunakanmach_absolute_time sebagai gantinya untuk mendapatkan waktu berlalu yang benar.
Cœur