Используя таймеры рассудительно

При использовании таймеров можно сократить использование питания приложения при помощи большего количества энергосберегающего APIs в их месте или, если необходимо использовать таймеры путем использования их эффективно.

Как работают таймеры

Таймер позволяет Вам планировать задержанное или периодическое действие. Таймер ожидает, пока определенный интервал не протек и затем стреляет, выполняя определенное действие, такое как отправка сообщения к его целевому объекту. Когда CPU и многочисленные другие системы пробуждены от их маломощных, состояний ожидания, пробуждение системы от состояния ожидания подвергается затратам энергии. Если таймер заставляет систему просыпаться, это несет те расходы.

Некоторые приложения используют таймеры для опроса относительно изменений состояния вместо того, чтобы использовать более эффективные службы для получения уведомлений о событии. Некоторые приложения используют таймеры в качестве инструментов синхронизации вместо того, чтобы использовать более эффективные инструменты, такие как семафоры и другие блокировки. Некоторые таймеры выполняются без подходящих тайм-аутов, и другие таймеры продолжают стрелять, когда они больше не необходимы. Если существует много вызванных на таймер пробуждений, энергетическое влияние высоко, как показано на рисунке 2-1.

Рисунок 2-1  энергетический прибор Влияния в XCode, показывающем влияние чрезмерных пробуждений от неактивного

Таймеры в OS X

OS X имеет много интерфейсов программирования для задержки процессов в течение установленных периодов времени. Любой метод или функция, которой Вы передаете относительный или абсолютный срок, являются таймером API. Например:

Некоторые приложения используют таймеры излишне. При использовании таймеров в приложении посмотрите, могли ли бы Вы обойтись без них. Например, некоторые приложения используют таймеры для опроса относительно изменений состояния, когда они должны реагировать на события вместо этого. Другие приложения используют таймеры в качестве инструментов синхронизации, когда они должны использовать семафоры или другие блокировки, чтобы быть самыми эффективными.

Реагируйте на события вместо того, чтобы опросить с таймерами

Некоторые таймеры использования приложений для опроса относительно нажатий клавиш, расположений мыши, изменяются на содержание файла, доступность сети и другие изменения состояния. Таймеры препятствуют тому, чтобы CPU шел в или остался в состоянии ожидания, использующем заряд батареи.

Вместо того, чтобы опросить для получения уведомлений о событии используйте более эффективные службы.

Например, вместо того, чтобы использовать таймер в качестве в Перечислении 2-1 для получения уведомлений о событии используйте источник отгрузки (показанный в Перечислении 2-2).

Перечисление 2-1  , Не рекомендуемое: Используя неэффективный энергией таймер для опроса относительно изменений в файле

NSTimer *myTimer = [[NSTimer alloc] initWithFireDate:date
                    interval:0.5  /*** Not Recommended ***/
                    target:self
                    selector:@selector(checkForFile:)
                    userInfo:nil
                    repeats:YES]; /*** Not Recommended ***/
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];

Перечисление 2-2  Используя энергосберегающий источник отгрузки для получения файла изменяет уведомления

const uint64_t flags = DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE;
dispatch_source_t my_source =
         dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, flags, queue);
dispatch_source_set_event_handler(my_source, ^{
         [self checkForFile];
});
dispatch_resume(my_source);

Для ответа на события вместо того, чтобы опросить с таймерами используйте уведомления о событии для многих предоставленных системой служб (Таблица 2-1).

Обратите внимание на то, что подобная таблица появляется в, Избегают Работы, Требующей Опроса, потому что те же методы могут использоваться для опроса что касается таймеров.

Таблица 2-1  Получая уведомления о событии для системных служб

Событие, которое будет уведомлено о

Подход для следования

Описанный в

Обновления к файлам

Сконфигурируйте источник отгрузки

Источники отгрузки в руководстве по программированию параллелизма

Обновления к файлам или каталогам в масштабе всей системы

Создайте поток событий с Событиями Файловой системы API

Руководство по программированию событий файловой системы

Межпроцессные сообщения

Используйте службы XPC

Создание служб XPC в руководстве по программированию демонов и служб

Используйте NSDistributedNotificationCenter класс

Ссылка класса NSDistributedNotificationCenter

Используйте notify_register, notify_post, и другие функции уведомления

Дарвинское уведомление ссылка API

Изменения в диске или объеме

Регистр для дисковых арбитражных уведомлений

Дисковое арбитражное руководство по программированию

Доступность заменяемых в горячем режиме устройств

Создайте объект уведомления Набора I/O

Нахождение и доступ к устройствам в доступе к аппаратным средствам из приложений

Сетевые события

Используйте службу уведомления нажатия Apple

Локальное и удаленное руководство по программированию уведомления

Используйте добрый день

Руководство по программированию открытия службы DNS и руководство по программированию NSNetServices и CFNetServices

Используйте сетевую достижимость и соединение APIs

Определяя достижимость и будучи соединенным в инструкциях по программированию конфигурации системы

События, сгенерированные мышью, клавиатурой и другими устройствами ввода данных

Используйте мониторы события

Слежение за развитием событий в руководстве по обработке событий какао

Рисунок 2-2 иллюстрирует, как ответ на события вместо того, чтобы опросить с таймером сохраняет время работы от батареи. (С 2013 WWDC выбирается это видео: Повышение Эффективности Питания с Дремотой Приложения.)

  Событие Using рисунка 2-2 контролирует вместо таймеров

Используйте семафоры или другие блокировки для синхронизации вместо таймеров

Вместо того, чтобы использовать таймеры, Grand Central Dispatch (GCD) предоставляет очередям отгрузки, семафорам отгрузки и другим функциям синхронизации.

Например, вместо того, чтобы использовать основанные на таймере инструменты синхронизации (в качестве в Перечислении 2-3), Ваше приложение должно использовать семафоры или другие блокировки (как в Перечислении 2-4).

Перечисление 2-3  , Не рекомендуемое: Используя неэффективный энергией таймер как инструмент синхронизации

BOOL workIsDone = NO;
/* thread one */
void doWork(void) {
     /* wait for network ... */
     workIsDone = YES;
}
/* thread two: completion handler */  /*** Not Recommended ***/
void waitForWorkToFinish(void) {
     while (!workIsDone) {
        usleep(100000); /* 100 ms */  /*** Not Recommended ***/
     }
    [WorkController workDidFinish];
}

Код в вышеупомянутом примере выполняет работу над одним потоком, в то время как на другом потоке, обработчик завершения использует таймер, чтобы периодически проверить, завершилась ли работа над первым потоком. Пока работа в первом потоке не завершается, usleep таймер во втором потоке постоянно будит систему только, чтобы решить, что не завершилась работа в первом потоке.

Вместо того, чтобы использовать основанный на таймере инструмент синхронизации, Ваше приложение должно использовать семафоры или другие блокировки. Код в Перечислении 2-4 выполняет синхронизацию намного более эффективно с последовательной очередью отгрузки.

Перечисление 2-4  Используя энергосберегающую очередь отгрузки для синхронизации потоков

my_queue = dispatch_queue_create(“com.myapp.myq”, DISPATCH_QUEUE_SERIAL);
/* thread one */
dispatch_sync(my_queue, ^{
      /* wait for network ... */
}); }
/* thread two */
void waitForWorkToFinish(void) {
      dispatch_sync(my_queue, ^{
          [WorkController workDidFinish];
}); }

Постоянно не будя систему, блок завершения в потоке два автоматически выполняется, как только закончен первый поток.

Для получения дополнительной информации об использовании очередей отгрузки семафоры отгрузки и другие функции синхронизации Центральной Отгрузки, видят Руководство по программированию Параллелизма и Ссылку Grand Central Dispatch (GCD). Также посмотрите Синхронизацию в Поточной обработке Руководства по программированию.

Если Вы нуждаетесь в таймере, используете его эффективно

Если Вы решаете, что Ваше приложение действительно требует таймера (игры, и другие интенсивные графикой приложения часто полагаются на таймеры для инициирования экрана или обновлений анимации), считайте следующие разделы, описывающие некоторые подходы для рисования наименьшего количества суммы питания.

Если необходимо использовать таймеры, следовать этим инструкциям:

Укажите подходящие тайм-ауты

Много функций включают параметр тайм-аута и такой возврат функций в конце каждого тайм-аута. Передача временного интервала к этим функциям заставляет их действовать в качестве таймеров со всей потребляемой мощностью таймеров.

Если Ваше приложение использует несоответствующее значение тайм-аута, то использование может соединить проблему. Например, в коде в Перечислении 2-5, значение тайм-аута 500 наносекунд передается dispatch_semaphore_wait функция. Пока семафор не сообщен, код не выполняет полезной работы в то время как dispatch_semaphore_wait постоянно испытывает таймаут.

Если Ваше приложение использует DISPATCH_TIME_FOREVER постоянный, то использование может блокировать функцию неопределенно, позволив функциональному резюме только при необходимости. Код в Перечислении 2-6 передает DISPATCH_TIME_FOREVER постоянный к dispatch_semaphore_wait. Функциональные блоки, пока это не получает семафор.

Перечисление 2-5  , Не рекомендуемое: Установка тайм-аута, на который не реагирует приложение

for (;;) {
    long rv;
    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_SEC);
                                            /*** Not Recommended ***/
    rv = dispatch_semaphore_wait(my_sema, timeout);
    if (havePendingWork) {
        [self doPendingWork];
  }
}

  Блокирование перечисления 2-6 для семафора

for (;;) {
     long rv;
     dispatch_time_t timeout = DISPATCH_TIME_FOREVER;
     rv = dispatch_semaphore_wait(my_sema, timeout);
     if (havePendingWork) {
         [self doPendingWork];
} }

В большинстве случаев блокирование неопределенно (как в Перечислении 2-6) более подходит, чем указание временной стоимости. Но если Ваше приложение действительно должно ожидать тайм-аута, укажите семафорное значение, представляющее значимое изменение состояния, такое как состояние ошибки или сетевой тайм-аут.

Лишите законной силы повторяющиеся таймеры, когда сделано

Если Вы используете повторяющийся таймер, лишаете законной силы или отменяете его, когда Вам больше не нужен он. Иначе, Ваш таймер будет продолжать запускать и использовать питание. Упущение остановить таймеры, вероятно, тратит впустую больше питания, чем что-либо еще и является одной из самых простых проблем фиксировать.

Код в Перечислении 2-7 использует повторение NSTimer таймер. Когда таймер больше не необходим, код вызывает invalidate метод, чтобы мешать таймеру стрелять снова, избегая ненужного использования питания.

Перечисление 2-7  , Лишающее законной силы таймер, когда это больше не необходимо

NSTimer *myTimer = [[NSTimer alloc] initWithFireDate:date
                                    interval:1.0
                                    target:self
                                    selector:@selector(timerFired:)
                                    userInfo:nil
                                    repeats:YES];
/* ... */
[myTimer invalidate];    /* Recommended */

Для повторяющегося таймера отгрузки используйте dispatch_source_cancel функционируйте для отмены таймера, когда он больше не будет необходим. Для повторяющегося таймера CFRunLoop используйте CFRunLoopTimerInvalidate функция.

Укажите допуск таймера для объединения таймера в масштабе всей системы

Укажите допуск для точности того, когда будут стрелять Ваши таймеры. Система будет использовать эту гибкость для смещения выполнения таймеров мелкими суммами времени — в их допусках — так, чтобы таймеры многократных приложений могли быть выполнены одновременно. Используя этот подход существенно увеличивает количество времени, что процессор тратит бездействие, в то время как пользователи не обнаруживают изменения в системной скорости отклика.

Можно использовать setTolerance: метод для указания допуска для таймера, как показано в Перечислении 2-8. Допуск 10 процентов установлен setTolerance:0.3 по сравнению с interval:3.0.

Перечисление 2-8  , Устанавливающее допуск для NSTimer таймеры

NSTimer *myTimer = [[NSTimer alloc] initWithFireDate:date interval:3.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
myTimer setTolerace:0.3];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];

Пример в Перечислении 2-9 показывает, как можно установить допуск 10 процентов с помощью последнего параметра dispatch_source_set_timer функция.

Перечисление 2-9  , Устанавливающее допуск для таймеров отгрузки

dispatch_source_t my_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(my_timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, NSEC_PER_SEC / 10);
dispatch_source_set_event_handler(my_timer, ^{
    [self timerFired];
});
dispatch_resume(my_timer);

Можно указать допуск 10 процентов интервала таймера для таймеров CFRunLoop с помощью CFRunLoopTimerSetTolerance функция, как показано в Перечислении 2-10. Допуск 10 процентов установлен вторым параметром CFRunLoopTimerSetTolerance, по сравнению с третьим параметром CFRunLoopTimerCreate.

Перечисление 2-10  , Устанавливающее допуск для таймеров CFRunLoop

CFRunLoopTimerRef myTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, fireDate, 2.0, 0, &timerFired,  NULL);
CFRunLoopTimerSetTolerance(myTimer, 0.2);
CFRunLoopAddTimer(CFRunLoopGetCurrent(), myTimer, kCFRunLoopDefaultMode);

После указания допуска для таймера это может стрелять в любое время между его запланированной датой огня и запланированной датой огня плюс допуск. Таймер не будет стрелять перед запланированной датой огня. Для повторения таймеров следующая дата огня всегда вычисляется с исходной даты огня, для предотвращения смещения.

Общее правило ползунка состоит в том, чтобы установить допуск по крайней мере в 10 процентов интервала для повторяющегося таймера, как в этих трех примерах выше. Даже мелкая сумма допуска оказывает значительное позитивное влияние на использование питания Вашего приложения.

Обратите внимание на то, что Вы не можете ожидать, что таймер будет стрелять в точную наносекунду, которую Вы запрашиваете, даже без допуска. Система прилагает все усилия для размещения потребностей, но не может гарантировать точные времена увольнения. Система может применить мелкую сумму допуска даже к таймерам, которым не дают указанную сумму, но этот допуск обычно недостаточен, чтобы позволить многократным таймерам стрелять во время единственного пробуждения.

Распознайте и решите проблемы таймера

При разработке и тестировании приложения, у Вас есть два инструмента, которые могут помочь Вам определить, повреждают ли таймеры в Вашем приложении энергетическую производительность: энергетический прибор Влияния (в XCode) и энергетическая область (в Мониторе Действия).

Энергетический прибор Влияния. Когда Вы создаете и выполняете свое приложение в XCode, можно проверить взрывы таймера с энергетическим прибором Влияния. Откройтесь навигатор отладки (выберите View> Navigators> Show Debug Navigator). Выберите энергетический прибор Влияния. Область "Wakes and CPU” сообщает, как часто таймер стрелял в прошлую секунду. Очень высокое число пробуждений приводит к очень высокоэнергетическому влиянию, как показано на рисунке 2-1.

Монитор действия. При тестировании можно проверить взрывы таймера путем запуска приложения с выполнением приложения Монитора Действия. Нажмите энергетическую кнопку в Мониторе Действия. Выберите View> Column> Idle Wake Ups и просмотрите состояние своего приложения. Число в столбце Idle Wake Ups сообщает, сколько раз в секунду таймер запустил, усредненный по демонстрационному интервалу, как проиллюстрировано на рисунке 2-3.

  Монитор Действия рисунка 2-3, показывающий энергетическое влияние чрезмерных пробуждений от неактивного

Если Ваше приложение имеет больше чем одно пробуждение в секунду, когда это должно быть неактивно, заняться расследованиями почему с timerfires инструмент командной строки. Предоставьте инструмент ID процесса, который Вы исследуете и передаете -s флаг для включения отслеживаний стека. Инструмент регистрирует все места, где приложение проснулось из-за таймера.

В выводе Terminal, показанном в Перечислении 2-11, MyApp имеет Базовую Основу на 2 Гц (CF) таймер это выполняет вызванную подпрограмму timerFired в его делегате приложения. Центральная Отгрузка (dispatch) таймер также выполняет вызванную подпрограмму updateWidgets. MyApp также вызывает usleep таймер, как показано в отслеживании стека. С этой информацией разработчик MyApp может исследовать те вызовы, чтобы видеть, ответственны ли они за чрезмерные пробуждения.

Перечисление 2-11  Отлаживая чрезмерный таймер, стреляющий с timerfires инструмент

$ sudo timerfires -p <pid> -s
TIME(ms)   PID  PROCESS  TYPE      TIMER ROUTINE
     435  1603  MyApp    CF        MyApp`-[AppDelegate timerFired:]
     933  1603  MyApp    CF        MyApp`-[AppDelegate timerFired:]
    1055  1603  MyApp    dispatch  MyApp`-[AppModel updateWidgets:]
    1435  1603  MyApp    CF        MyApp`-[AppDelegate timerFired:]
    1935  1603  MyApp    CF        MyApp`-[AppDelegate timerFired:]
    2055  1603  MyApp    dispatch  MyApp`-[AppModel updateWidgets:]
    2435  1603  MyApp    CF        MyApp`-[AppDelegate timerFired:]
    3004  1603  MyApp    sleep
              libsystem_kernel.dylib`__semwait_signal+0xa
              libsystem_c.dylib`usleep+0x36
              MyApp`-[AppModel pollForChange]+0x1a
              libdispatch.dylib`_dispatch_call_block_and_release+0xc

В видеодемонстрации, показанной на рисунке 2-4, аналитик использует энергетический прибор Влияния и timerfires инструмент, чтобы обнаружить и диагностировать две проблемы с таймерами. В одном случае приложение использует таймер для опроса относительно изменений в файле, и в другом случае, таймер никогда не лишается законной силы после того, как это больше не необходимо.

Эти проблемы решены путем замены таймера опроса с источником отгрузки и путем добавления вызова для лишения законной силы другого таймера. С теми изменениями приложение входит в абсолютно состояние ожидания, когда оно не используется. (С 2013 WWDC выбирается это видео: энергетические Методы наиболее успешной практики.)

Рисунок 2-4  Используя энергетический прибор Влияния и timerfires инструмент