EarlGrey 源码阅读(二)
计算程序运行的时间
GREYStopwatch *elementFinderStopwatch = [[GREYStopwatch alloc] init];
// 开始计时
[elementFinderStopwatch start];
NSArray *elements = [elementFinder elementsMatchedInProvider:entireRootHierarchyProvider];
// 结束计时
[elementFinderStopwatch stop];
在这里我们见到了谷歌是如何计算程序运行时间的 GREYStopwatch
。先看看其他常见手段
NSDate 计算运行时间
NSDate* date = [NSDate date];
//You code here...
NSTimeInterval deltaTime = [[NSDate date] timeIntervalSinceDate:date];
这种方法比较 Naive,精确到秒。
CFAbsoluteTimeGetCurrent
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
// do something
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
网上还流传着用 CFAbsoluteTimeGetCurrent
来计算运行时间的方法,看前缀以为是比较底层的函数会更精确些,网上确实有人说这个函数精确到毫秒。但是文档清楚地写着
Absolute time is measured in seconds relative to …
以秒为单位计时,精确到秒。同时用 Xcode 打印变量的时候(大概是 Xcode 自己的优化,识别出是 CFAbsoluteTime
类型的)直接打出了人类可读时间戳形式。只精确到秒。
(lldb) po time
2018-10-03 21:52:59 CST
CFAbsoluteTime
本质是 double,使用 printf 看变量实际值 560267579.507048
。这小数点后面六位及时准确那也应该是到微秒而不是毫秒。所以网上流传的精确到毫秒是不对的。
mach_absolute_time
#include <assert.h>
#include <CoreServices/CoreServices.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <unistd.h>
uint64_t GetPIDTimeInNanoseconds(void)
{
uint64_t start;
uint64_t end;
uint64_t elapsed;
uint64_t elapsedNano;
static mach_timebase_info_data_t sTimebaseInfo;
// Start the clock.
start = mach_absolute_time();
// Call getpid. This will produce inaccurate results because
// we're only making a single system call. For more accurate
// results you should call getpid multiple times and average
// the results.
(void) getpid();
// Stop the clock.
end = mach_absolute_time();
// Calculate the duration.
elapsed = end - start;
// Convert to nanoseconds.
// If this is the first time we've run, get the timebase.
// We can use denom == 0 to indicate that sTimebaseInfo is
// uninitialised because it makes no sense to have a zero
// denominator is a fraction.
if ( sTimebaseInfo.denom == 0 ) {
(void) mach_timebase_info(&sTimebaseInfo);
}
// Do the maths. We hope that the multiplication doesn't
// overflow; the price you pay for working in fixed point.
elapsedNano = elapsed * sTimebaseInfo.numer / sTimebaseInfo.denom;
return elapsedNano;
}
这是一段 Apple 官方提供的如何使用 mach_absolute_time
获取纳秒级别时间的例子。其中 mach_absolute_time
获取到的是设备上次重启以来时钟滴答跳动的次数,但需要使用系统提供的参数进行转换成人类可读的形式。如果期间设备休眠比如关闭屏幕,这段时间是不计入其中的。
GREYStopwatch
便是用这个 api 来实现计算运行时间的。EarlGrey 提供了一段转换代码
/**
* Obtain the difference in seconds between two provided times (each in terms of the Mach absolute
* time unit).
*
* @param startTime The lesser of the two times to be measured between in terms of the Mach
* absolute time unit.
* @param endTime The greater of the two times being measured between in terms of the Mach
* absolute time unit.
*
* @return an NSTimeInterval with the interval in seconds between @c startTime and @c endTime.
*/
static inline NSTimeInterval grey_timeIntervalBetween(uint64_t startTime, uint64_t endTime) {
static mach_timebase_info_data_t info;
if (info.denom == 0) {
(void) mach_timebase_info(&info);
}
NSTimeInterval intervalInSeconds =
(double)((endTime - startTime) * info.numer) / (info.denom * NSEC_PER_SEC);
return intervalInSeconds;
}
这样计算滴答的次数还是有一些不精确的,不能精确到时钟滴答到当前进程多少下。假设滴答 100 次,那可能只有其中 50 次滴答在当前进程上,50 次中的 15 次滴答在当前线程上。如果说一段代码其实只需要 15 个时钟周期就能完成,由于进程、线程的调度算法导致需要多运行 85 次才能把任务完成。多测量几次会发现每次额外的滴答数都是不确定的,额外的部分就是误差。针对这样的问题就需要取平均了。
更甚,分别计算用户态花了多长时间,内核态的系统调用花了多长时间。
这部分展望在 计算 App 用户态和内核态占用的 CPU 时间 得到了解决。
总结
NSDate
精确到 sCFAbsoluteTimeGetCurrent
精确到 smach_absolute_time
精确到 ns (尽管如此,还是不能计算出程序所需的时钟周期数)
下篇写写 EarlGrey 里高精度 Timer。