Contents
  1. 1. 计算程序运行的时间
    1. 1.1. NSDate 计算运行时间
    2. 1.2. CFAbsoluteTimeGetCurrent
    3. 1.3. mach_absolute_time
  2. 2. 总结

计算程序运行的时间

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 精确到 s

  • CFAbsoluteTimeGetCurrent 精确到 s

  • mach_absolute_time 精确到 ns (尽管如此,还是不能计算出程序所需的时钟周期数)

下篇写写 EarlGrey 里高精度 Timer。

Contents
  1. 1. 计算程序运行的时间
    1. 1.1. NSDate 计算运行时间
    2. 1.2. CFAbsoluteTimeGetCurrent
    3. 1.3. mach_absolute_time
  2. 2. 总结