Чи є простий спосіб дуже точно отримати час?
Мені потрібно обчислити деякі затримки між викликами методів. Більш конкретно, я хочу обчислити швидкість прокрутки в UIScrollView.
Чи є простий спосіб дуже точно отримати час?
Мені потрібно обчислити деякі затримки між викликами методів. Більш конкретно, я хочу обчислити швидкість прокрутки в UIScrollView.
Відповіді:
NSDate
і timeIntervalSince*
методи повернуть a, NSTimeInterval
що є подвійним з точністю до мілісекунди. NSTimeInterval
знаходиться в секундах, але він використовує подвійний, щоб забезпечити вам більшу точність.
Для того, щоб обчислити точність мілісекундного часу, ви можете зробити:
// 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;
Документація про часIntervalSinceNow .
Існує багато інших способів розрахунку цього інтервалу за допомогою NSDate
, і я б рекомендував переглянути документацію класу, NSDate
яку ви знайдете в NSDate Class Reference .
NSDate
і mach_absolute_time()
приблизно на рівні 30 мс. 27 проти 29, 36 проти 39, 43 проти 45. NSDate
мені було легше використовувати, і результати були досить схожі, щоб не хвилювати.
mach_absolute_time()
може бути використаний для отримання точних вимірювань.
Дивіться http://developer.apple.com/qa/qa2004/qa1398.html
Також є в наявності CACurrentMediaTime()
, що по суті те саме, але з більш простим у використанні інтерфейсом.
(Примітка. Ця відповідь була написана у 2009 році. Див. Відповідь Павла Алексєєва щодо простіших clock_gettime()
інтерфейсів POSIX, доступних у новіших версіях macOS та iOS.)
CACurrentMediaTime()
, який перетворюється mach_absolute_time()
безпосередньо в a double
.
elapsedNano
має тип Nanoseconds
, який не є простим цілим числом. Це псевдонім UnsignedWide
, який представляє собою структуру з двома 32-бітовими цілими полями. UnsignedWideToUInt64()
Якщо ви хочете, ви можете використовувати замість акторів.
Будь ласка , не використовуйте NSDate
, CFAbsoluteTimeGetCurrent
або gettimeofday
для вимірювання витраченого часу. Все це залежить від системного годинника, який може змінюватися в будь-який час через безліч різних причин, таких як мережева синхронізація часу (NTP), оновлення годин (часто трапляється для налаштування дрейфу), налаштування DST, стрибкові секунди тощо.
Це означає, що якщо ви вимірюєте швидкість завантаження або завантаження, у вас з’являться раптові сплески або падіння ваших цифр, які не співвідносяться з тим, що відбулося насправді; ваші тести на ефективність матимуть дивні невірні виграші; і ваші ручні таймери спрацьовуватимуть після неправильної тривалості. Час може навіть повернутися назад, і ви закінчитеся з негативними дельтами, і ви можете закінчитися нескінченною рекурсією або мертвим кодом (так, я зробив це обоє).
Використовуйте mach_absolute_time
. Він вимірює реальні секунди з моменту завантаження ядра. Він монотонно збільшується (ніколи не піде назад) і не впливає на налаштування дати та часу. Оскільки з цим боляче працювати, ось простий пакунок, який дає вам 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;
замість цього можна використовувати#include <mach/mach_time.h>
CFAbsoluteTimeGetCurrent()
повертає абсолютний час як double
значення, але я не знаю, у чому його точність - він може оновлюватись лише кожні десяток мілісекунд, або він може оновлювати кожну мікросекунду, я не знаю.
72.89674947369
секунд було б досить рідкісним, враховуючи всі інші значення, якими воно могло бути. ;)
Я б НЕ використовував, mach_absolute_time()
тому що він запитує комбінацію ядра та процесора протягом абсолютного часу, використовуючи кліщі (ймовірно, тривалість роботи).
Що б я використав:
CFAbsoluteTimeGetCurrent();
Ця функція оптимізована для виправлення різниці програмного та апаратного забезпечення iOS та OSX.
Щось Geekier
Коефіцієнт різниці в mach_absolute_time()
і AFAbsoluteTimeGetCurrent()
завжди становить близько 24000011.154871
Ось журнал мого додатка:
Зверніть увагу, що остаточний час результату - це різниця у 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()
a mach_timebase_info_data
і потім зробив (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));
. Щоб отримати (void)mach_timebase_info(&your_timebase);
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)
Використання:
CTTimeStart();
...
CTTimeEnd(@"that was a long time:");
Вихід:
2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
NSDate
залежить від системних годин, які можуть бути змінені в будь-який час, що може призвести до неправильних або навіть негативних часових інтервалів. Використовуйте mach_absolute_time
натомість, щоб отримати правильний час.
mach_absolute_time
може вплинути перезавантаження пристрою. Замість цього використовуйте час сервера.
Крім того, ось як обчислити 64-розрядну NSNumber
ініціалізацію з епохою Unix в мілісекундах, на випадок, коли ви хочете зберегти її в CoreData. Мені це знадобилось для мого додатка, який взаємодіє із системою, яка зберігає дати таким чином.
+ (NSNumber*) longUnixEpoch {
return [NSNumber numberWithLongLong:[[NSDate date] timeIntervalSince1970] * 1000];
}
Функції на основі mach_absolute_time
корисні для коротких вимірювань.
Але для тривалих вимірювань важливим застереженням є те, що вони припиняють тикати, поки пристрій спить.
Існує функція отримати час після завантаження. Це не зупиняється під час сну. Крім того, gettimeofday
не є монотонним, але в своїх експериментах я завжди бачу, що час завантаження змінюється, коли змінюється системний час, тому я думаю, що він повинен працювати добре.
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
}
А оскільки iOS 10 та macOS 10.12 ми можемо використовувати 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
}
}
Підсумовуючи це:
Date.timeIntervalSinceReferenceDate
- зміни, коли змінюється системний час, а не монотонніCFAbsoluteTimeGetCurrent()
- не монотонне, може йти назадCACurrentMediaTime()
- припиняє тикати, коли пристрій спитьtimeSinceBoot()
- не спить, але може бути не монотоннимCLOCK_MONOTONIC
- не спить, монотонно, підтримується з iOS 10Я знаю, що це старе, але навіть я знову опинився, коли блукав повз нього, тому подумав, що подати тут свій варіант.
Найкраще перевірити мою публікацію в блозі на цю тему : Визначення часу в предметі-C: секундомір
В основному я написав клас, який перестає дивитися дуже просто, але інкапсульований так, що вам потрібно зробити лише наступне:
[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];
І закінчуєте:
MyApp[4090:15203] -> Stopwatch: [My Timer] runtime: [0.029]
в журналі ...
Ще раз, ознайомтеся з моєю публікацією ще трохи або завантажте її тут: MMStopwatch.zip
NSDate
залежить від системних годин, які можуть бути змінені в будь-який час, що може призвести до неправильних або навіть негативних часових інтервалів. Використовуйте mach_absolute_time
натомість, щоб отримати правильний час.
Ви можете отримати поточний час у мілісекундах з 1 січня 1970 року за допомогою NSDate:
- (double)currentTimeInMilliseconds {
NSDate *date = [NSDate date];
return [date timeIntervalSince1970]*1000;
}
Для тих, хто нам потрібна версія Swift, відповідь @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
Я сподіваюся, що це допоможе тобі.
NSDate
залежить від системних годин, які можуть бути змінені в будь-який час, що може призвести до неправильних або навіть негативних часових інтервалів. Використовуйте mach_absolute_time
натомість, щоб отримати правильний час.