FreeRTOS (время переключения) v №2

В моей предыдущей попытке написать статью, я чуть чуть неверно проводил тестирование задержки переключения задач. Разобравшись со своими ошибками хочу повторить тестирование.
Разберем несколько случаев. В общем случае используем МК stm32f100c8. Рабочая частота 24МГц. configTICK_RATE_HZ=1000, таким образом слайс времени равен 1мс.
1) Используем две задачи. Исполняются с одинаковым приоритетом. Код:
void vTaskLED3(void *pvParameters) {
	GPIO_TypeDef *GPIO=GPIOA;
	uint32 mask=0x0000000F;
	uint32 cfg=0x00000003;
	mask<<=5*4;
	mask=~mask;
	GPIO->CRL &=mask; //erase configure and mode bits;
	mask=cfg<<5*4;
	GPIO->CRL |=mask; //set configure and mode bits

	for(;;){
		GPIO->BSRR |=1<<5;
		GPIO->BRR |=1<<5;
		GPIO->BSRR |=1<<5;
		GPIO->BRR |=1<<5;
		taskYIELD();
	}
}

void vTaskLED4(void *pvParameters) {
	GPIO_TypeDef *GPIO=GPIOA;
	uint32 mask=0x0000000F;
	uint32 cfg=0x00000003;
	mask<<=6*4;
	mask=~mask;
	GPIO->CRL &=mask; //erase configure and mode bits;
	mask=cfg<<6*4;
	GPIO->CRL |=mask; //set configure and mode bits

	for(;;){
		GPIO->BSRR |=1<<6;
		GPIO->BRR |=1<<6;
		GPIO->BSRR |=1<<6;
		GPIO->BRR |=1<<6;
		taskYIELD();
	}
}




Получаем в среднем время между последним фронтом задачи №1 и первым фронтом задачи №2 около 5,42мкс. При этом отмечаем, что мы использовали принудительную смену контекста и то, что время исполнения команд в задачах равно 0,375мкс. Таким образом можно считать что время переключения контекста около 5мкс.
В данном моменте пару раз натыкался на грабли. Дело в том, что это время зависит от:
а) частоты МК. Тот же код, но МК работает на частоте в 30МГц:

Получаем время переключения около 4мкс.
б) настроек оптимизации компилятора. Тот же код, МК на частоте 24МГц, но вместо -O1 выставил -O3.

Получаем время переключения около 4,83мкс. То есть по сравнению с оптимизацией -O1 получаем выигрыш в 4%.

То есть в общем случае, все что нам поднимает производительность, то и уменьшает время переключения.

2) Используем две задачи. Исполняются с одинаковым приоритетом. Код:
void vTaskLED3(void *pvParameters) {
	GPIO_TypeDef *GPIO=GPIOA;
	uint32 mask=0x0000000F;
	uint32 cfg=0x00000003;
	mask<<=5*4;
	mask=~mask;
	GPIO->CRL &=mask; //erase configure and mode bits;
	mask=cfg<<5*4;
	GPIO->CRL |=mask; //set configure and mode bits

	for(;;){
		GPIO->BSRR |=1<<5;
		GPIO->BRR |=1<<5;
		GPIO->BSRR |=1<<5;
		GPIO->BRR |=1<<5;
	}
}

void vTaskLED4(void *pvParameters) {
	GPIO_TypeDef *GPIO=GPIOA;
	uint32 mask=0x0000000F;
	uint32 cfg=0x00000003;
	mask<<=6*4;
	mask=~mask;
	GPIO->CRL &=mask; //erase configure and mode bits;
	mask=cfg<<6*4;
	GPIO->CRL |=mask; //set configure and mode bits

	for(;;){
		GPIO->BSRR |=1<<6;
		GPIO->BRR |=1<<6;
		GPIO->BSRR |=1<<6;
		GPIO->BRR |=1<<6;
		taskYIELD();
	}
}

Отличия лишь в том, что первая задача не вызывает никаких функций FreeRTOS и у нас не используются сторонние прерывания. То есть задача исполняется до тех пор пока ее принудительно не прерывается сам RTOS по таймеру, когда истекает слайс времени. В нашем случае слайс по настройкам должен быть 1мс.


Отлично видно, что в случае прерывания по таймеру время переключения составляет около 8,34мкс. Кстати слайс времени действительно около 1мс. Интересно да? 8,3мкс против 5мкс.

3) Используем две задачи. Исполняются с одинаковым приоритетом. Код:
void vTaskLED3(void *pvParameters) {
	GPIO_TypeDef *GPIO=GPIOA;
	uint32 mask=0x0000000F;
	uint32 cfg=0x00000003;
	mask<<=5*4;
	mask=~mask;
	GPIO->CRL &=mask; //erase configure and mode bits;
	mask=cfg<<5*4;
	GPIO->CRL |=mask; //set configure and mode bits

	for(;;){
		GPIO->BSRR |=1<<5;
		GPIO->BRR |=1<<5;
		GPIO->BSRR |=1<<5;
		GPIO->BRR |=1<<5;
		vTaskDelay(1);
	}
}

void vTaskLED4(void *pvParameters) {
	GPIO_TypeDef *GPIO=GPIOA;
	uint32 mask=0x0000000F;
	uint32 cfg=0x00000003;
	mask<<=6*4;
	mask=~mask;
	GPIO->CRL &=mask; //erase configure and mode bits;
	mask=cfg<<6*4;
	GPIO->CRL |=mask; //set configure and mode bits

	for(;;){
		GPIO->BSRR |=1<<6;
		GPIO->BRR |=1<<6;
		GPIO->BSRR |=1<<6;
		GPIO->BRR |=1<<6;
		vTaskDelay(1);
	}
}

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

Вот тут получается еще интересней. Вызов функции приводит к тому, что задача переключается за целых 15,5мкс! Что ровно в 3 раза больше чем принудительное переключение и почти в 2 раза больше чем переключение по таймеру.

Выводы: Все, что ускоряет производительность МК(повышение частоты, оптимизация кода в компиляторе), то и приводит к уменьшению времени переключения между задачами. Для повышения скорости переключения задач стоит использовать, там где это разумно, функцию принудительного переключения контекста.
  • +5
  • 15 мая 2013, 18:26
  • NiC

Комментарии (22)

RSS свернуть / развернуть
Заходишь на сайт FreeRTOS. Читаешь документацию. Также смотришь на исходник планировщика и держишь под рукой доку на компилятор и на ядро МК. И после этого не страдаешь муйней. Время передачи управления между задачами или время переключения контекста зависит от множества факторов. Во freertos их просто дохренилион.
0
  • avatar
  • a9d
  • 15 мая 2013, 18:42
Нет не дохрелион. Покажите мне еще хотя бы пару вариантов почему время переключения может изменяться. При этом учитываем, что речь идет о времени переключения контекста, а не о времени через которое задача Х будет запущена, после окончания задачи У. Это чуть разные вещи правда?
-1
Количество приоритетов, тип планировщика, количество задач, тип ядра ОС(для кортексов есть два вида с небольшими различиями), версия и настройки компилятора, прерывания, наличие программных таймеров и т.п.
0
Количество приоритетов? Ок а как это влияет на «время смены контента»? Может быть из-за наличия разных очередей ядро дольше на наносекунды думает? Количество задач? Хотите выложу лог с 4 задачами? Время теже самые 5мкс.
Про компилятор вроде бы мной было написано, правда?
Мы говорим про время переключения контекста или про время включения задачи, после выключения предыдущей?
0
Кстати слямзил с хабра:
Моменты времени, когда происходят переключения задач с использованием шедулера, бывают в точках окончания каждого тика времени (для этого используется прерывание по таймеру), а также в момент вызова функций API FreeRTOS, и при наступлении специальных событий (окончание аппаратного прерывания, окончание времени блокировки задачи, наступление момента обновления очереди или семафора).
0
Количество приоритетов? Ок а как это влияет на «время смены контента»? Может быть из-за наличия разных очередей ядро дольше на наносекунды думает?
Именно так и может происходить. Если приоритеты реализуются отдельными списками (конкретно фриртос не скажу какая реализация), то происходит последовательный перебор этих списков — от высокоприоритетного к низкоприорететному — с целью найти первую задачу, готовую к выполнению.
0
КАТ!
0
>Все, что ускоряет производительность МК(повышение частоты, оптимизация кода в компиляторе), то и приводит к уменьшению времени переключения между задачами.
«Странный» вывод. Если мы оптимизируем скорость выполнения кода, то код начинает выполняться быстрее. КЭП, это ты автор статьи?
Про 15мкс против 5. Принудительное переключение контекста, просто переключает контекст. Это 5мкс.
Переключение по слайсу, это прерывание, + переключение + возврат из прерывания. Это несколько дороже чем просто переключиться, получаем 8мкс. Ваш КЭП.
При вызове функции ОС, появляется дополнительная обработка тела функции. Это видимо еще 7мкс.
Простите. В чем необходимость темы? На мой взгляд, все выводы очевидны и без опытов.
З.Ы. Как «обучалка» хорошо. Для «опытных», новой информации ноль.
Все исключительно IMHO.
+1
Несколько подправлю по поводу 15мкс в 2-х задачах с делеем.
Вызов vTaskDelay(1) приводит к «усыплению задачи на 1мс». По специфике функций задержек/сна — вам гарантируется, что время ваш поток будет приостановлен в выполнении как минимум на заданное время, максимум же зависит от многих других параметров (загрузка системы, более приоритетные задачи и т.д.). В итоге получается некоторый промежуток времени, когда обе задачи спят — нет ни одной активной. Вероятно в это время прочессор отправляется в сон, либо же происходит переключение на «пустую» задачу (idle task). Вот вам и 2 переключения: добровольная отдача управления 5мкс + idleloop/wfi N мкс + вытеснение высокоприоритетной задачей 8 мкс = 15мкс.
Таким образом при использовании vTaskDelay(1) в идеале у вас должно быть время между фронтами одной задачи 1мс. И для меня более удивительно, что это не так (-1.5мкс мелочь конечно, может немного сократили время сна в расчете на накладные расходы, которые оказались меньше, или кто их знает). Между фронтами 2-х задач время переключения не обязано быть временем одного переключения. Возможно имеет смысл в одной из задач добавить перед циклом задержку в ~100мкс для большей наглядности (а кстати, не в начальной ли инициализации то смещение 15мкс и возникло?).
0
Я конечно могу попробовать поймать начальную инициализацию. Но зачем? Все честно, это выполнение задач в циклах.
0
Я к тому, что вывод
Вот тут получается еще интересней. Вызов функции приводит к тому, что задача переключается за целых 15,5мкс! Что ровно в 3 раза больше чем принудительное переключение и почти в 2 раза больше чем переключение по таймеру.
Не совсем корректный. Само переключение по идеи происходит за то же время, что и добровольная передача управления, просто в момент переключения ещё не кому отдавать управление. По этому и идет ожидание. Аналогичное явление вы наблюдали и в прошлой статье, только там разница была больше.
0
Решил проверить, вашу идею. МК на 24МГц. Код:
void vTaskLED3(void *pvParameters) {
	GPIO_TypeDef *GPIO=GPIOA;
	uint32 mask=0x0000000F;
	uint32 cfg=0x00000003;
	mask<<=5*4;
	mask=~mask;
	GPIO->CRL &=mask; //erase configure and mode bits;
	mask=cfg<<5*4;
	GPIO->CRL |=mask; //set configure and mode bits

	for(;;){
		GPIO->BSRR |=1<<5;
		GPIO->BRR |=1<<5;
		GPIO->BSRR |=1<<5;
		GPIO->BRR |=1<<5;
		vTaskDelay(1);
	}
}

void vTaskLED4(void *pvParameters) {
	GPIO_TypeDef *GPIO=GPIOA;
	uint32 mask=0x0000000F;
	uint32 cfg=0x00000003;
	mask<<=6*4;
	mask=~mask;
	GPIO->CRL &=mask; //erase configure and mode bits;
	mask=cfg<<6*4;
	GPIO->CRL |=mask; //set configure and mode bits

	for(;;){
		GPIO->BSRR |=1<<6;
		GPIO->BRR |=1<<6;
		GPIO->BSRR |=1<<6;
		GPIO->BRR |=1<<6;
		taskYIELD();
	}
}


То есть первый поток вызывает функцию и спит около 1мс. Второй отдает управление сразу.


Отсюда видно, что время переключения после вызова функции все таки около 15,5мкс. Так как вторая задача готова выполнятся всегда.
Кстати словил интересный эффект:

Вот кто бы его объяснил.
0
Гм. Не ожидал, что yeld остановит готовый поток. Как же тогда класическое vTaskDelay(0) юзать?
А вот «интересный эффект» для меня более понятен. Не понятно почему только прекратился.
0
А вот «интересный эффект» для меня более понятен.

А какие у Вас идеи? Я предрасполагаю, что задержка в 17 мкс — это обработчик прерывания от таймера без переключения задачи. Но как-то это много, не сосем согласуется с другими результатами, при условии, что переключения контекста в данном случае нет.

А касательно «vTaskDelay(0) vs taskYIELD()» — есть комментарии от разработчиков.
+1
Ой нет, прошу прощения. сделал ошибочные выводы, т.к. картинка обрезалась. Ничего yeld не стопорит, всё как положено тикает.
Тогда эффект действительно интересный.
Я предрасполагаю, что задержка в 17 мкс — это обработчик прерывания от таймера без переключения задачи. Но как-то это много, не сосем согласуется с другими результатами, при условии, что переключения контекста в данном случае нет.
Почему это нет? По «реализации» вполне может быть, что по джиттеру и происходит переключение на первую готовую задачу, то есть на себя же. Надо смотреть код, есть ли отдельная обработка ситуации когда переключение на себя, а то как раз на этой функции я и остановил поиски :)
За ссылку на делай-ноль спасибо. То, что и ожидалось.
0
Ой, я забыл про кат. Сори, народ я недавно на ресурсе.

Дорогой JeckDigger , для вас все выше перечисленное является очевидным, как два пальца об асфальт. Как разведение плоской антенны на печатной плате под частоту в 2ГГц, как создание гремучей ртути на кухне.
Но для меня, это был интересный эксперимент, в ходе которого я узнал для себя что-то новое. В тему фриртоса я начал вникать совсем недавно. И хочу отметить что свой эксперимент я выложил в своем ПЕРСОНАЛЬНОМ блоге. Кто-ты выкладывает тут хуйню, а кто-то свои «нубские» эксперименты. Но только блин избранные тут должны выкладывать интересные статьи.

PS Дорогие читатели спасибо за «обоснованные» минуса.
PPS В теме которая меня заставила вникать в переключение контекста например о тех выводах что я сделал не было ни слова, там было просто указано, для хз каких условий дескать время задержки на стм 3мкс. А почему 3, а не 10? ХЗ. А вот меня подвигло на опус который вы прочитали(мб) выше.
+1
  • avatar
  • NiC
  • 15 мая 2013, 21:10
Я принципиально не ставлю минуса, ни где. :) А на данный момент, здесь даже не имею такой возможности. Думаю, имеет смысл добавить в теги «обучение» или что-то такое. И в начале дописать, что это для начинающих. Профи просто не станут читать… И минусов убавиться…
0
Блин, перепутал ники. Терпеть ненавижу древовидные комментарии.
Добавил тег.
0
для хз каких условий дескать время задержки на стм 3мкс. А почему 3, а не 10? ХЗ
Кстати, 3 мкс там было на частоте 72 МГц. А у вас тоже на 24-х.
0
А можно тоже нубский вопрос? А зачем вся эта погоня за микросекундами в контексте ртос?
Я так понимаю, что диспетчер вызывается раз в 1 мс, то есть нам желательно уложить каждую задачу в приделы этого времени. А сколько займет переключение между ними для интерфейса пользователя вообще пофиг. А для скоростных и не очень интерфейсов ( юарты, и2с, шим, дисплеи всякие и тп.) в контроллере есть аппаратные модули.
Не конечно, мега круто запустить на тиньке БПФ, но зачем?
0
Ну погоня исходная была из-за переключения в ~150мкс, а это 15% от времени работы системы — не мало, не правда ли? Далее выяснилось, что переключение идет всё-таки ~10мкс — 1% терпимо/нормально. Ну а выяснение 15мкс/8мкс/5мкс просто исследования для себя на предмет понимания принципов работы. Ну и для более оптимального использования ресурсов.
Не забывайте же, что кроме хардварных интерфейсов, есть ещё выводы/биты занятости, которые читать надо софтварно. И чем быстрее вы выясните что работать еще рано, и чем быстрее отдадите управление — тем лучше для системы в целом.
гнаться слишком не стоит (мало ли что измениться в следующей версии ядра), но понимание лишним не будет.
0
Спасибо, я просто первую статью пропустил и про 150 мкс не знал. Хотя попади сам в подобную ситуацию, остановился бы на 15-20 мкс и дальше копать бы не стал. Так что автору можно поставить плюс за старательность.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.