Есть ли простой способ очень точно узнать время?
Мне нужно рассчитать некоторые задержки между вызовами методов. В частности, я хочу рассчитать скорость прокрутки в UIScrollView.
Есть ли простой способ очень точно узнать время?
Мне нужно рассчитать некоторые задержки между вызовами методов. В частности, я хочу рассчитать скорость прокрутки в UIScrollView.
Ответы:
NSDate
и timeIntervalSince*
методы вернут NSTimeInterval
двойной с точностью до миллисекунды. NSTimeInterval
измеряется в секундах, но для большей точности используется значение double.
Чтобы рассчитать точность времени в миллисекундах, вы можете:
// 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;
Документация по timeIntervalSinceNow .
Есть много других способов вычислить этот интервал с помощью 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()
непосредственно в файл double
.
elapsedNano
имеет тип Nanoseconds
, который не является простым целочисленным типом. Это псевдоним UnsignedWide
, который представляет собой структуру с двумя 32-битными целочисленными полями. Вы можете использовать UnsignedWideToUInt64()
вместо литья, если хотите.
Пожалуйста, не используйте NSDate
, CFAbsoluteTimeGetCurrent
или gettimeofday
для измерения прошедшего времени. Все это зависит от системных часов, которые могут измениться в любое время из-за множества различных причин, таких как синхронизация сетевого времени (NTP), обновляющая часы (часто происходит с корректировкой дрейфа), настройки летнего времени, дополнительные секунды и т. Д.
Это означает, что если вы измеряете скорость загрузки или выгрузки, вы получите внезапные всплески или падения ваших чисел, которые не коррелируют с тем, что произошло на самом деле; ваши тесты производительности будут иметь странные неправильные выбросы; и ваши ручные таймеры сработают после неправильной продолжительности. Время может даже пойти в обратном направлении, и вы получите отрицательные дельты, и вы можете получить бесконечную рекурсию или мертвый код (да, я сделал и то, и другое).
Используйте 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.
Что-то более интересное
Частное от разницы в mach_absolute_time()
и AFAbsoluteTimeGetCurrent()
всегда составляет около 24000011,154871.
Вот журнал моего приложения:
Пожалуйста , обратите внимание , что последний раз результата является разницей в CFAbsoluteTimeGetCurrent()
-х
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Я знаю, что это старый, но даже я обнаружил, что снова блуждаю мимо него, поэтому я решил представить здесь свой собственный вариант.
Лучше всего проверить мою запись в блоге об этом: Расчет времени в Objective-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
вместо этого, чтобы узнать истекшее время.