Какая частота для FreeRTOS оптимальна?
Когда я начал работать со FreeRTOS, я уперся в следующую магическую строчку в FreeRTOSConfig.h: #define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) Это частота переключения между задачами. Почему 1000? А почему не 10000 или 100? А если у меня STM32F4, настроенный на 168 MHz? Я могу выбрать другую, большую частоту? Ответы были какие-то туманные. Самый адекватный: «если будет слишком часто переключаться, то планировщик задач будет потреблять слишком много временного ресурса». Слишком много — это сколько? Решил разобраться. Под катом — ответ на вопрос «какая частота подходит для данной ситуации?».
UPD: существенное исправление: добавил количество потоков, изменились результаты!
Постановка задачиИтак, есть вопрос — какая частота переключения наиболее оптимальна для этой программы в этом железе для этого заказа? Наверное та, когда планировщик задач («менеджер») потребляет минимум времени. В реальной жизни надо задаться этим минимумом — сколько я могу себе позволить. В каких единицах? В миллисекундах? Хорошая идея, подумал я. Пошел выкапывать доставать цифровой осциллограф по офису и продумывать программу, которая бы во время планировщика задач держала на ноге «0», потом «1». Бредя среди завалов хлама полезного оборудования, я думал далее — допустим, этом будет 1 мксек. Это много или мало? Смотря сколько времени остается задаче. Значит, надо будет мерять два периода — работы программы и работы планировщика. Уже почти докопавшись достав осциллограф, я вдруг понял — меня не интересует абсолютное время работы того и другого, мне нужно относительное время работы планировщика. Значит, мне достаточно понять соотношение времени работы планировщика ко времени выполнения задачи. В относительных единицах. Следовательно, осциллограф мне не обязателен — я могу измерить это время в режиме отладки. Сбросив пару свалившихся на голову клавиатур закрыв ящик с осциллографом, я пошел писать программу.
Структура решенияКак мы будем решать такую задачу?
Я исхожу из того, что у меня STM32F4, который 32х-битный контроллер, настроенный на 168MHz. Также я исхожу из того, что у меня STM32F4Discovery, который микроконтроллер, программатор и встроенный отладчик в одном лице устройстве. Еще я не забываю об Eclipse и OpenOCD, что мне позволяет написать программу и отладить ее в режиме реального времени.
Я решил передать планировщику задач некую функцию Adder, которая будет периодически инкрементировать 32хбитную переменную counter, повесить перехватчик вызова планировщика задач (функция vApplicationTickHook) и в нем смотреть ее значение. «Смотреть» — это запустить плату в режиме отладки и поставить точку остановки в нужное мне место.
По ходу отладки оказалось, что значение counter от вызова к вызову несколько плавает. Поэтому я решил ее усреднять — допустим, сделать CallsAmount переключений задач, потом разделить счетчик counter на количество вызовов CallsAmount. Если количество вызовов будет достаточно большим, то я буду делать целочисленное деление и результат будет точным. Проверил — результат достаточно быстро сходится к одному значению.
В итоге получился такой код:
Что это значит? Это значит, что единица измерения у меня — операция «инкремент». Т. е. я считаю сколько инкрементов выполняется задачей между переключениями задач. Я использую 32х-битный контроллер, в нем эта операция выполняется за один такт. Следовательно, я получаю количество выполненных операций. Это не миллисекунды (хотя их можно перевести в оные). Это плохо, что не миллисекунды? Мне все равно, так как меня интересует соотношение затраченного времени на одно и на другое.
Чудесно, я получил для данной частоты среднее количество инкрементов. Что мне с этим делать дальше?
Во FreeRTOS производится настройка таймера, соответствующая значению константы configTICK_RATE_HZ. Допустим, наша частота f1 равна 1000Hz. За время T1 между вызовами таймера выполняется задача (за время j1) и выполняется код планировщика задач (за время m).
Теперь, допустим, configTICK_RATE_HZ равна 2346, что соответствует частоте f2 = 2346Hz. Время между переключениями T2 станет меньше, задача j2 будет выполняться дольше, а вот планировщик задач m будет выполняться то же самое время!
Если говорить о нашей единице измерения «инкремент», то мы четко вычисляем j1 и j2 — значение переменной average. При таком подходе мы не знаем T1 и T2, но мы знаем их соотношение (k = T1 / T2). Наша задача — вычислить m. Потом мы можем для fi получить соотношение m / ji. Вот и все!
Математика тут следующая:
РезультатыUPD: по совету я добавил второй поток, цифры все изменились!
Я проделал измерения для нескольких частот. Далее я привожу результаты в частоте (Hz) и в значениях счетчика: 10000 — 1085, 5000 — 2204, 3333 — 3163, 2500 — 4442, 2000 — 5561, 1000 — 11155. Как и следовало ожидать, налицо гиперболическая зависимость.
Далее делаем несложный расчет для любой пары значений. Возьмем два крайних: f1 = 10000 и f2 = 1000, j1 = 1085 и j2 = 11155. Для этих частот соотношение будет k = T1 / T2 = f2 / f1 = 0.1. Тогда m = (0.1 * 11155 — 1085) / (1 — 0.1) = 34.
Итак, я могу утверждать для моего случая (контроллер + программа + настройки компилятора), что планировщик задач выполняется за 34 операций инкремента.
Это много или мало?
Давайте проведем следующий расчет: для частоты fi разделим m на длительность выполнения задачи ji и выразим результат di в процентах: di = m / ji. Результат: 10000Hz — 3.1%, 5000Hz — 1.5%, 3333Hz — 1.1%, 2500Hz — 0.8%, 2000Hz — 0.6%, 1000Hz — 0.3%. Можно считать, что di — это падение производительности для частоты fi.
Из этих цифр можно получить еще один интересный результат: сколько времени (в реальных мико-, милли- или просто секундах) будет выполняться планировщик, а сколько — программа. Для этого для начала посмотрим сколько времени будет работать планировщик: 1000 мсек * di. Получим такой результат: 10000Hz — 31.1msec, 5000Hz — 15.3msec, 3333Hz — 10.7msec, 2500Hz — 7.6msec, 2000Hz — 6.1msec, 1000Hz — 3.0msec. Соответственно, наоборот мы получим время выполнения задачи: 1000 мсек * (1 — di). Получаем следующее: 10000Hz — 969msec, 5000Hz — 985msec, 3333Hz — 989msec, 2500Hz — 992msec, 2000Hz — 994msec, 1000Hz — 997msec.
Такой вот результат. Теперь у меня есть инструментарий для измерения времени выполнения планировщика в текущей программе. Разумеется, планировщик по-разному будет себя вести в случае таймеров, очередей и прочего (сопрограмма — вообще особый случай). Сколько мне выбирать? В каждом конкретном случае ответ будет разным. Насколько я вижу, в простейшей программе я получил для 10KHz переключения на 31msec работы планировщика за секунду, или падение производительности на 1%. Если это управление работой ядерного реактора или самолетом, то стоит выбирать меньшую частоту переключений (а лучше — вообще другой RTOS, например, SafeRTOS). Если же это цвето- и аудиомузыка в кафешке, то это более чем славно.
P. S.Хочу поучаствовать в конкурсе, поэтому добавляю:
-
, , ,
- +4
- 28 июля 2014, 16:47
Есть один момент. В вашем тестовом коде только один поток (задача)
И планировщик работает в «холостом» режиме. Вызвался, убедился, что делать ему ничего не нужно и завершился (ну еще там из прерывания где-то vApplicationTickHook позвался). Нет самого главного – нет переключения контекста. А это, ИМХО, самый ресурсоемкий фрагмент работы планировщика.