您是否注意到dispatch_after在iOS设备上运行速度太慢〜10%?

Dun*_*n C 21 objective-c grand-central-dispatch ios objective-c-blocks

最近我一直在使用dispatch_after而不是performSelector:withObject:afterDelay,当我想在延迟后触发一些代码.代码更清晰,它可以访问封闭的范围,我可以将代码放入内联而不是写一个抛弃方法等等.

我的代码可能如下所示:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  delay * NSEC_PER_SEC),
  dispatch_get_main_queue(),
  ^{
    //Delayed-execution code goes here.
  }
);
Run Code Online (Sandbox Code Playgroud)

但是,我最近发现这段代码的执行时间似乎比请求的速度慢了大约10%.如果我要求延迟10秒,我的块将在11秒后执行.这是在iOS设备上.时间似乎在模拟器上非常接近.

我用来测试它的代码非常简单:

NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  delay * NSEC_PER_SEC),
  dispatch_get_main_queue(),
  ^{
    NSTimeInterval actualDelay = [NSDate timeIntervalSinceReferenceDate] - startTime;
    NSLog(@"Requested delay = %.3f. Atual delay = %.3f", delay, actualDelay);
    //Delayed-execution code goes here.
  }
);
Run Code Online (Sandbox Code Playgroud)

我已经在从iOS 4S到iPad Air的设备上进行了测试,而且额外的延迟非常一致.我还没有在像iPhone 4或iPad 2这样的旧设备上进行测试,尽管我很快就会这样做.

我可能期望在延迟中有20-50毫秒的"斜率",但是一致的10% - 11%的超调是奇怪的.

我在我的代码中添加了一个"软糖因子"来调整额外的延迟,但我发现它令人惊讶:

#define  delay_fudge 0.912557 //Value calculated based on averages from testing.


NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  delay * delay_fudge *  NSEC_PER_SEC),
  dispatch_get_main_queue(),
  ^{
    NSTimeInterval actualDelay = [NSDate timeIntervalSinceReferenceDate] - startTime;
    NSLog(@"Requested delay = %.3f. Actual delay = %.3f", delay, actualDelay);
    //Delayed-execution code goes here.
  }
);
Run Code Online (Sandbox Code Playgroud)

我应该做更多的分析,看看是否存在固定的延迟增加加上延迟因子或直线百分比延迟,或者可能是误差的一些非线性比例,但是现在一个简单的乘数似乎做得很好.

Cou*_*per 22

您可能听说过Timer Coalescing和App Nap--这有助于降低功耗.

你在这里观察到的是将系统事件延迟到某个"余地值"的效果,以便能够在一个时间点"定时器合并" 一起执行它们.这将增加CPU在功耗降低模式下的持续时间.

对于调度lib,有一个标志可用于提高"余地值"的准确性,这也最终影响计时器的准确性(见下文).我不认为让定时器不必要准确是个好主意 - 例如移动设备.

我的怀疑是,这dispatch_after将使用具有特定余地值集的调度计时器,这是实现定义的.

您可以使用dispatch lib实现非常准确的计时器dispatch_source_set_timer(),您还可以在其中指定"余地值".

也可以看看: dispatch/source.h

/*!
 * @typedef dispatch_source_timer_flags_t
 * Type of dispatch_source_timer flags
 *
 * @constant DISPATCH_TIMER_STRICT
 * Specifies that the system should make a best effort to strictly observe the
 * leeway value specified for the timer via dispatch_source_set_timer(), even
 * if that value is smaller than the default leeway value that would be applied
 * to the timer otherwise. A minimal amount of leeway will be applied to the
 * timer even if this flag is specified.
 *
 * CAUTION: Use of this flag may override power-saving techniques employed by
 * the system and cause higher power consumption, so it must be used with care
 * and only when absolutely necessary.
 */

#define DISPATCH_TIMER_STRICT 0x1

...

 * Any fire of the timer may be delayed by the system in order to improve power
 * consumption and system performance. The upper limit to the allowable delay
 * may be configured with the 'leeway' argument, the lower limit is under the
 * control of the system.
 *
Run Code Online (Sandbox Code Playgroud)

  • 为了它的价值,在http://www.opensource.apple.com/source/libdispatch/libdispatch-339.1.9/src/queue.c上查看dispatch_after_f的实现确认了延迟的10%的默认余地. . (8认同)