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


Таймеры в OS X
OS X имеет много интерфейсов программирования для задержки процессов в течение установленных периодов времени. Любой метод или функция, которой Вы передаете относительный или абсолютный срок, являются таймером API. Например:
Высокоуровневый таймер APIs включает источники таймера отгрузки,
CFRunLoopTimerCreateи другие функции CFRunLoopTimer,NSTimerкласс,performSelector:withObject:afterDelay:метод,CVDisplayLinkStartи другие функции CVDisplayLink, иNSProgressIndicatorкласс.Низкоуровневый таймер APIs включает функции
sleep,usleep,nanosleep,pthread_cond_timedwait,select,poll,kevent,dispatch_after, иdespatch_semaphore_wait.
Некоторые приложения используют таймеры излишне. При использовании таймеров в приложении посмотрите, могли ли бы Вы обойтись без них. Например, некоторые приложения используют таймеры для опроса относительно изменений состояния, когда они должны реагировать на события вместо этого. Другие приложения используют таймеры в качестве инструментов синхронизации, когда они должны использовать семафоры или другие блокировки, чтобы быть самыми эффективными.
Реагируйте на события вместо того, чтобы опросить с таймерами
Некоторые таймеры использования приложений для опроса относительно нажатий клавиш, расположений мыши, изменяются на содержание файла, доступность сети и другие изменения состояния. Таймеры препятствуют тому, чтобы 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).
Обратите внимание на то, что подобная таблица появляется в, Избегают Работы, Требующей Опроса, потому что те же методы могут использоваться для опроса что касается таймеров.
Событие, которое будет уведомлено о | Подход для следования | Описанный в |
|---|---|---|
Обновления к файлам | Сконфигурируйте источник отгрузки | Источники отгрузки в руководстве по программированию параллелизма |
Обновления к файлам или каталогам в масштабе всей системы | Создайте поток событий с Событиями Файловой системы API | |
Межпроцессные сообщения | Используйте службы XPC | Создание служб XPC в руководстве по программированию демонов и служб |
Используйте | ||
Используйте | ||
Изменения в диске или объеме | Регистр для дисковых арбитражных уведомлений | |
Доступность заменяемых в горячем режиме устройств | Создайте объект уведомления Набора I/O | Нахождение и доступ к устройствам в доступе к аппаратным средствам из приложений |
Сетевые события | Используйте службу уведомления нажатия Apple | Локальное и удаленное руководство по программированию уведомления |
Используйте добрый день | Руководство по программированию открытия службы DNS и руководство по программированию NSNetServices и CFNetServices | |
Используйте сетевую достижимость и соединение APIs | Определяя достижимость и будучи соединенным в инструкциях по программированию конфигурации системы | |
События, сгенерированные мышью, клавиатурой и другими устройствами ввода данных | Используйте мониторы события | Слежение за развитием событий в руководстве по обработке событий какао |
Рисунок 2-2 иллюстрирует, как ответ на события вместо того, чтобы опросить с таймером сохраняет время работы от батареи. (С 2013 WWDC выбирается это видео: Повышение Эффективности Питания с Дремотой Приложения.)
Используйте семафоры или другие блокировки для синхронизации вместо таймеров
Вместо того, чтобы использовать таймеры, 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.

Если Ваше приложение имеет больше чем одно пробуждение в секунду, когда это должно быть неактивно, заняться расследованиями почему с 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 выбирается это видео: энергетические Методы наиболее успешной практики.)
timerfires инструмент