Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Have you noticed that dispatch_after runs ~10% too slow on iOS devices?

Tags:

Lately I've been using dispatch_after instead of performSelector:withObject:afterDelay when I want to trigger some code after a delay. The code is cleaner, it has access to the enclosing scope, I can put the code in-line instead of writing a throw-away method, etc, etc.

My code might look like this:

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

However, I recently discovered that the time-to-excution from this code seems to run pretty consistently about 10% slower than requested. If I ask for a delay of 10 seconds, my block gets executed about 11 seconds later. This is on an iOS device. The times seem to match quite closely on the simulator.

The code I'm using to test this is pretty simple:

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.   } ); 

I've tested on devices from an iOS 4S to an iPad Air and the extra delay is pretty consistent. I haven't yet tested on an older device like an iPhone 4 or an iPad 2, although I will do that soon.

I might expect 20-50 ms of "slop" in the delay, but a consistent 10% - 11% overshoot is odd.

I've added a "fudge factor" to my code that adjusts for the extra delay, but I find it surprising:

#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.   } ); 

I should probably do more analysis and see if there is a fixed increase in delay plus a delay factor or a straight percent delay, or perhaps some non-linear scale to the error, but for now a simple multiplier seems to do pretty well.

like image 364
Duncan C Avatar asked Jan 21 '14 18:01

Duncan C


1 Answers

You may have heard about Timer Coalescing and App Nap - which helps to reduce power consumption.

What you are observing here is the effect of delaying system events up to a certain "leeway value" in order to be able to execute them all together at one point in time, "Timer Coalescing". That will increase the duration the CPU can dwell on its power reduced mode.

For dispatch lib, there is a flag which can be used to increase the accuracy of the "leeway value", which also affects eventually the accuracy of a timer (see below). I don't think its a good idea to make timers unnecessary accurate - for mobile devices for example.

My suspicion is, that dispatch_after will use a dispatch timer with a certain leeway value set, which is implementation defined.

You can implement quite accurate timers with dispatch lib, using dispatch_source_set_timer(), where you can also specify the "leeway value".

See also: 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.  * 
like image 198
CouchDeveloper Avatar answered Dec 28 '22 00:12

CouchDeveloper