Avez-vous remarqué que dispatch_after tourne ~ 10% trop lentement sur les appareils iOS?

Dernièrement j'ai utilisé dispatch_after au lieu de performSelector: withObject: afterDelay quand je veux triggersr du code après un timeout. Le code est plus propre, il a access à la scope englobante, je peux mettre le code en ligne au lieu d'écrire une méthode jetable, etc.

Mon code pourrait ressembler à ceci:

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

Cependant, j'ai récemment découvert que le timeout d'exécution de ce code semble fonctionner de façon assez constante, environ 10% plus lent que demandé. Si je request un timeout de 10 secondes, mon bloc est exécuté environ 11 secondes plus tard. C'est sur un appareil iOS. Les time semblent correspondre assez étroitement au simulateur.

Le code que j'utilise pour tester ceci est assez 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. } ); 

J'ai testé sur des appareils à partir d'un iOS 4S à un iPad Air et le timeout supplémentaire est assez cohérent. Je n'ai pas encore testé sur un appareil plus ancien comme un iPhone 4 ou un iPad 2, même si je le ferai bientôt.

Je pourrais m'attendre à 20-50 ms de "slop" dans le delay, mais un overshoot cohérent de 10% – 11% est impair.

J'ai ajouté un "facteur de fudge" à mon code qui s'ajuste au timeout supplémentaire, mais je trouve cela surprenant:

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

Je devrais probablement faire plus d'parsings et voir s'il y a une augmentation fixe du timeout plus un facteur de retard ou un pourcentage de retard, ou peut-être une échelle non linéaire à l'erreur, mais pour l'instant un simple multiplicateur semble plutôt bien fonctionner.

    Vous avez peut-être entendu parler de Timer Coalescing et App Nap – qui aide à réduire la consommation d'énergie.

    Ce que vous observez ici est l'effet de retarder les events du système jusqu'à une certaine "valeur de dérive" afin de pouvoir les exécuter tous set à un moment donné, "Timer Coalescing". Cela augmentera la durée pendant laquelle la CPU peut restr en mode de réduction de puissance.

    Pour le dispatch lib, il y a un drapeau qui peut être utilisé pour augmenter la précision de la "valeur de dérive", ce qui affecte aussi éventuellement la précision d'une timer (voir ci-dessous). Je ne pense pas que ce soit une bonne idée de rendre les minuteurs inutiles précis – pour les appareils mobiles par exemple.

    Mon soupçon est, que dispatch_after utilisera un timer d'expédition avec un certain set de valeurs de marge, qui est défini par l'implémentation.

    Vous pouvez implémenter des minuteurs assez précis avec dispatch lib, en utilisant dispatch_source_set_timer() , où vous pouvez également spécifier la "valeur de marge".

    Voir aussi: 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 ssortingctly 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. *