Техническое примечание TN2169

Таймеры Высокой точности в iOS / OS X

Примечание покроет do's и dont's использования таймеров высокой точности на iOS и OS X.

Таймеры Высокой точности в iOS / OS X
Мне нужен таймер высокой точности?
Предложение для синхронизации с обновлениями дисплея
Как выполняют работу таймеров?
Как выполняют работу таймеров высокой точности?
Как я становлюсь помещенным в класс планирования реального времени?
Какую синхронизацию API я должен использовать?
Насколько точный таймеры высокой точности должны быть?
Каковы do's и dont's кода, работающего с таймерами высокой точности?
История версии документа

Таймеры Высокой точности в iOS / OS X

Примечание покроет do's и dont's использования таймеров высокой точности на iOS и OS X.

Мне нужен таймер высокой точности?

Не используйте таймер высокой точности, если Вам действительно не нужен он. Они используют, вычисляют циклы и батарею. Может только быть ограниченное количество высоких precsion таймеров, активных сразу. Таймер высокой точности является «первым в строке», и не каждый таймер может быть первым. Когда слишком многие пробуют, все таймеры теряют точность.

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

Предложение для синхронизации с обновлениями дисплея

Если Вы пишете код, который должен синхронизироваться с кадровым буфером или обновлениями дисплея, Apple уже выполнил большую часть тяжелой работы для Вас. Если Вы разрабатываете для iOS, посмотрите класс CADisplayLink, найденный в QuartzCore.framework. При предназначении для OS X посмотрите CVDisplayLink, также найденный в QuartzCore.framework.

iOS: Ссылка класса CADisplayLink

OS X: ссылка CVDisplayLink

Как выполняют работу таймеров?

Существует много API's в iOS и OS X, позволяющих ожидать установленного периода времени. Они могут быть Objective C или C, и они берут различные виды параметров, но они все заканчивают тем, что использовали тот же код в ядре. Каждый API таймера говорит ядру, что это должно ожидать до определенного времени, например 10 секунд с этого времени. Ядро отслеживает каждый поток, и когда запрос таймера входит, тот поток отмечен как, «я хотел бы работать через 10 секунд».

Ядро пытается быть максимально скромным с циклами CPU, поэтому если не будет никакой другой работы, чтобы сделать, то это поместит CPU для сна в течение 10 секунд, затем проснется и выполнит поток.

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

Как выполняют работу таймеров высокой точности?

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

Как я становлюсь помещенным в класс планирования реального времени?

Перечисление 1  следующий код переместит pthread в класс планирования реального времени

#include <mach/mach.h>
#include <mach/mach_time.h>
#include <pthread.h>
 
void move_pthread_to_realtime_scheduling_class(pthread_t pthread)
{
    mach_timebase_info_data_t timebase_info;
    mach_timebase_info(&timebase_info);
 
    const uint64_t NANOS_PER_MSEC = 1000000ULL;
    double clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC;
 
    thread_time_constraint_policy_data_t policy;
    policy.period      = 0;
    policy.computation = (uint32_t)(5 * clock2abs); // 5 ms of work
    policy.constraint  = (uint32_t)(10 * clock2abs);
    policy.preemptible = FALSE;
 
    int kr = thread_policy_set(pthread_mach_thread_np(pthread_self()),
                   THREAD_TIME_CONSTRAINT_POLICY,
                   (thread_policy_t)&policy,
                   THREAD_TIME_CONSTRAINT_POLICY_COUNT);
    if (kr != KERN_SUCCESS) {
        mach_error("thread_policy_set:", kr);
        exit(1);
    }
}

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

Используя поток Маха API для влияния на планирование

Какую синхронизацию API я должен использовать?

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

Перечисление 2  Этот пример кода демонстрирует использование mach_wait_until () для ожидания точно 10 секунд.

#include <mach/mach.h>
#include <mach/mach_time.h>
 
static const uint64_t NANOS_PER_USEC = 1000ULL;
static const uint64_t NANOS_PER_MILLISEC = 1000ULL * NANOS_PER_USEC;
static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MILLISEC;
 
static mach_timebase_info_data_t timebase_info;
 
static uint64_t abs_to_nanos(uint64_t abs) {
    return abs * timebase_info.numer  / timebase_info.denom;
}
 
static uint64_t nanos_to_abs(uint64_t nanos) {
    return nanos * timebase_info.denom / timebase_info.numer;
}
 
void example_mach_wait_until(int argc, const char * argv[])
{
    mach_timebase_info(&timebase_info);
    uint64_t time_to_wait = nanos_to_abs(10ULL * NANOS_PER_SEC);
    uint64_t now = mach_absolute_time();
    mach_wait_until(now + time_to_wait);
}

Насколько точный таймеры высокой точности должны быть?

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

Таймеры не точны через сон и цикл следа аппаратных средств.

Каковы do's и dont's кода, работающего с таймерами высокой точности?

Действительно используйте как можно меньше CPU во время Вашего цикла таймера. Помните, что Ваш поток теперь имеет специальные полномочия, и когда Вы работаете, никто больше не может. Действительно попытайтесь протянуть свои запросы таймера так, как Вы безопасно можете. Это позволяет ядру использовать меньше батареи путем сна чаще и дольше.

Не вращайте цикл! Это записывает CPU и батарею на очень высоких показателях, и, когда более новый более быстрые аппаратные средства выпущены, Вы запишете CPU и батарею еще быстрее!

Не создавайте большие количества оперативных потоков. Оперативное планирование не является волшебным, оно работает путем создания потока более высоким приоритетом, чем другие потоки в системе. Если все попытаются толпиться к передней стороне строки, то все оперативные потоки перестанут работать.



История версии документа


ДатаПримечания
05.03.2013

Новый документ, что примечание покроет do's и dont's использования таймеров высокой точности на iOS и OS X.