Có cách nào dễ dàng để có được thời gian rất chính xác không?
Tôi cần tính toán một số độ trễ giữa các lần gọi phương thức. Cụ thể hơn, tôi muốn tính toán tốc độ cuộn trong UIScrollView.
Có cách nào dễ dàng để có được thời gian rất chính xác không?
Tôi cần tính toán một số độ trễ giữa các lần gọi phương thức. Cụ thể hơn, tôi muốn tính toán tốc độ cuộn trong UIScrollView.
Câu trả lời:
NSDate
và các timeIntervalSince*
phương thức sẽ trả về a NSTimeInterval
là một nhân đôi với độ chính xác dưới mili giây. NSTimeInterval
tính bằng giây, nhưng nó sử dụng số kép để cung cấp cho bạn độ chính xác cao hơn.
Để tính toán độ chính xác thời gian mili giây, bạn có thể thực hiện:
// 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;
Tài liệu về thời gianIntervalSinceNow .
Có nhiều cách khác để tính toán khoảng thời gian này bằng cách sử dụng NSDate
, và tôi khuyên bạn nên xem tài liệu lớp học NSDate
được tìm thấy trong Tài liệu tham khảo lớp NSDate .
NSDate
và mach_absolute_time()
ở mức khoảng 30ms. Đối với tôi, 27 so với 29, 36 so với 39, 43 so với 45. NSDate
dễ sử dụng hơn và các kết quả cũng tương tự, đủ để không cần quan tâm.
mach_absolute_time()
có thể được sử dụng để có được các phép đo chính xác.
Xem http://developer.apple.com/qa/qa2004/qa1398.html
Cũng có sẵn CACurrentMediaTime()
, về cơ bản là giống nhau nhưng với giao diện dễ sử dụng hơn.
(Lưu ý: Câu trả lời này được viết vào năm 2009. Xem câu trả lời của Pavel Alexeev về các clock_gettime()
giao diện POSIX đơn giản hơn có sẵn trong các phiên bản macOS và iOS mới hơn.)
CACurrentMediaTime()
, chuyển đổi mach_absolute_time()
trực tiếp thành a double
.
elapsedNano
thuộc loại Nanoseconds
, không phải là kiểu số nguyên đơn giản. Đó là một bí danh của UnsignedWide
, là một cấu trúc có hai trường số nguyên 32 bit. Bạn có thể sử dụng UnsignedWideToUInt64()
thay vì diễn viên nếu muốn.
Xin đừng sử dụng NSDate
, CFAbsoluteTimeGetCurrent
hoặc gettimeofday
để đo thời gian trôi qua. Tất cả những điều này phụ thuộc vào đồng hồ hệ thống, có thể thay đổi bất kỳ lúc nào do nhiều lý do khác nhau, chẳng hạn như đồng bộ hóa thời gian mạng (NTP) cập nhật đồng hồ (thường xảy ra để điều chỉnh độ trôi), điều chỉnh DST, giây nhuận, v.v.
Điều này có nghĩa là nếu bạn đang đo tốc độ tải xuống hoặc tải lên của mình, bạn sẽ nhận được những con số đột ngột hoặc giảm xuống không tương quan với những gì thực sự đã xảy ra; kiểm tra hiệu suất của bạn sẽ có những ngoại lệ không chính xác kỳ lạ; và bộ hẹn giờ thủ công của bạn sẽ kích hoạt sau khoảng thời gian không chính xác. Thời gian thậm chí có thể quay ngược trở lại, và bạn kết thúc với các delta âm, và bạn có thể kết thúc với đệ quy vô hạn hoặc mã chết (vâng, tôi đã làm cả hai điều này).
Sử dụng mach_absolute_time
. Nó đo số giây thực kể từ khi hạt nhân được khởi động. Nó đang tăng một cách đơn điệu (sẽ không bao giờ đi ngược lại) và không bị ảnh hưởng bởi cài đặt ngày và giờ. Vì rất khó để làm việc, đây là một trình bao bọc đơn giản cung cấp cho bạn 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()
từ QuartzCore thì sao?
@import Darwin;
thay vì#include <mach/mach_time.h>
CFAbsoluteTimeGetCurrent()
trả về thời gian tuyệt đối dưới dạng một double
giá trị, nhưng tôi không biết độ chính xác của nó là gì - nó có thể chỉ cập nhật mỗi chục mili giây hoặc nó có thể cập nhật mỗi micro giây, tôi không biết.
72.89674947369
giây sẽ khá không phổ biến, nếu xét tất cả các giá trị khác nó có thể là. ;)
Tôi sẽ KHÔNG sử dụng mach_absolute_time()
vì nó truy vấn sự kết hợp của hạt nhân và bộ xử lý trong một thời gian tuyệt đối bằng cách sử dụng tick (có thể là thời gian hoạt động).
Những gì tôi sẽ sử dụng:
CFAbsoluteTimeGetCurrent();
Chức năng này được tối ưu hóa để khắc phục sự khác biệt trong phần mềm và phần cứng iOS và OSX.
Một cái gì đó Geekier
Thương số của sự khác biệt trong mach_absolute_time()
và AFAbsoluteTimeGetCurrent()
luôn vào khoảng 24000011.154871
Đây là nhật ký ứng dụng của tôi:
Xin lưu ý rằng thời gian kết quả cuối cùng là sự khác biệt trong 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()
với một mach_timebase_info_data
và sau đó đã làm (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));
. Để có được cơ sở thời gian tốt, bạn có thể làm(void)mach_timebase_info(&your_timebase);
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)
Sử dụng:
CTTimeStart();
...
CTTimeEnd(@"that was a long time:");
Đầu ra:
2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
NSDate
phụ thuộc vào đồng hồ hệ thống mà có thể bị thay đổi bất kỳ lúc nào, có khả năng dẫn đến sai hoặc thậm chí khoảng thời gian âm. Sử dụng mach_absolute_time
thay thế để có được thời gian đã trôi qua chính xác.
mach_absolute_time
có thể bị ảnh hưởng bởi thiết bị khởi động lại. Sử dụng thời gian máy chủ thay thế.
Ngoài ra, đây là cách tính toán 64-bit được NSNumber
khởi tạo với kỷ nguyên Unix trong mili giây, trong trường hợp đó là cách bạn muốn lưu trữ nó trong CoreData. Tôi cần điều này cho ứng dụng của mình tương tác với hệ thống lưu trữ ngày tháng theo cách này.
+ (NSNumber*) longUnixEpoch {
return [NSNumber numberWithLongLong:[[NSDate date] timeIntervalSince1970] * 1000];
}
Các chức năng dựa trên mach_absolute_time
là tốt cho các phép đo ngắn.
Nhưng đối với các phép đo dài, lưu ý quan trọng là chúng ngừng hoạt động khi thiết bị ở chế độ ngủ.
Có một chức năng để lấy thời gian kể từ khi khởi động. Nó không dừng lại trong khi ngủ. Ngoài ra, gettimeofday
không phải là đơn điệu, nhưng trong các thí nghiệm của tôi, tôi luôn thấy rằng thời gian khởi động thay đổi khi thời gian hệ thống thay đổi, vì vậy tôi nghĩ nó sẽ hoạt động tốt.
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
}
Và kể từ iOS 10 và macOS 10.12, chúng tôi có thể sử dụng 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
}
}
Tóm lại:
Date.timeIntervalSinceReferenceDate
- thay đổi khi thời gian hệ thống thay đổi, không đơn điệuCFAbsoluteTimeGetCurrent()
- không đơn điệu, có thể đi lùiCACurrentMediaTime()
- ngừng đánh dấu khi thiết bị ở chế độ ngủtimeSinceBoot()
- không ngủ, nhưng có thể không đơn điệuCLOCK_MONOTONIC
- không ngủ, đơn âm, được hỗ trợ kể từ iOS 10Tôi biết đây là một cái cũ nhưng ngay cả tôi cũng thấy mình lang thang qua nó một lần nữa, vì vậy tôi nghĩ tôi sẽ gửi tùy chọn của riêng mình ở đây.
Đặt cược tốt nhất là hãy xem bài đăng trên blog của tôi về điều này: Định giờ mọi thứ trong Objective-C: Đồng hồ bấm giờ
Về cơ bản, tôi đã viết một lớp dừng xem theo cách rất cơ bản nhưng được gói gọn lại để bạn chỉ cần thực hiện những việc sau:
[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];
Và bạn kết thúc với:
MyApp[4090:15203] -> Stopwatch: [My Timer] runtime: [0.029]
trong nhật ký ...
Một lần nữa, hãy xem bài đăng của tôi để biết thêm một chút hoặc tải xuống tại đây: MMStopwatch.zip
NSDate
phụ thuộc vào đồng hồ hệ thống mà có thể bị thay đổi bất kỳ lúc nào, có khả năng dẫn đến sai hoặc thậm chí khoảng thời gian âm. Sử dụng mach_absolute_time
thay thế để có được thời gian đã trôi qua chính xác.
Bạn có thể tính thời gian hiện tại tính bằng mili giây kể từ ngày 1 tháng 1 năm 1970 bằng cách sử dụng NSDate:
- (double)currentTimeInMilliseconds {
NSDate *date = [NSDate date];
return [date timeIntervalSince1970]*1000;
}
Đối với những người chúng tôi cần phiên bản Swift của câu trả lời của @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
Tôi hy vọng điều này sẽ giúp bạn.
NSDate
phụ thuộc vào đồng hồ hệ thống mà có thể bị thay đổi bất kỳ lúc nào, có khả năng dẫn đến sai hoặc thậm chí khoảng thời gian âm. Sử dụng mach_absolute_time
thay thế để có được thời gian đã trôi qua chính xác.