|
Покритикуйте реализацию простейшего шедулера на C++, Разработано для 8ми битных контроллеров AVR |
|
|
|
Feb 25 2017, 18:20
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Всем привет. Вот тут на днях допилил свой Tiny таск шедулер. Основным требованием при реализации был минимальный оверхед во всем. Однако есть возможность конфигурировать его под разные задачи, опять же без оверхеда. Хотелось бы услышать конструктивную критику, возможно предложение каких-то фич новых... В общем выкладываю с целью получения обратной связи. В архиве проект Atmel Studio 7.0 с минимальной демонстрацией, можно погонять в симуляторе)
TinyScheduler_ver5_Loki_.zip ( 175.14 килобайт )
Кол-во скачиваний: 53Для тех у кого нет Atmel Studio вот ссылка на исходник шедулера http://pastebin.com/7YVSAFBnЧитайте комментарии в шапке файла. Там все подробности использования и настройки.
--------------------
The truth is out there...
|
|
|
|
|
Feb 26 2017, 05:24
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(sigmaN @ Feb 25 2017, 20:20)  Основным требованием при реализации был минимальный оверхед во всем. Очень не похоже, что основное требование выполнено, например, крутить свой счетчик для каждой из задач идея сама по себе редко когда удачная. Ну и я понимаю, что оптимизация великая сила, и наверняка сделает все возможное, но писать такое: Код timers[i] = (timers[i]>0)? (timers[i]-1):(timers[i]); это как бы уже перебор.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Feb 26 2017, 10:35
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Цитата крутить свой счетчик для каждой из задач идея сама по себе редко когда удачная. Я вот сейчас немного подумал и.... всё равно не придумал как можно сделать оптимальнее. Как мне кажется во всех вариантах будет еще один байт. Цитата timers[i] = (timers[i]>0)? (timers[i]-1):(timers[i]); это как бы уже перебор. Ну у меня 0 в счетчике это признак запуска задачи, так что проверять на 0 приходится. Раньше было Код for(uint8_t i=0; i < TaskListLen; i++) { if( timers[i] > 0 ) --timers[i]; } Это было на целых 2 байта длиннее! При прочих равных настройках оптимизации и т.д. Я, так сказать, подчинился основной цели и выбрал сэкономить 2 байта флэша путем ухудшения читабельности кода. P.S. Я ночью писал двольно длинную шапку коментов на английском, слушал выступления Скотта Майерса на CPPCon и в момент публицации этого вопроса на форуме у меня была полная голова фич, оверхеда и шедулеров ))))
--------------------
The truth is out there...
|
|
|
|
|
Feb 26 2017, 11:09
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(sigmaN @ Feb 26 2017, 12:35)  Я вот сейчас немного подумал и.... всё равно не придумал как можно сделать оптимальнее. Как мне кажется во всех вариантах будет еще один байт. Про "еще один байт" не понял. Если у Вас цель какие-то байты экономить, а не такты, то так и напишите. Для, например, 256 задач (реальный случай во времена i8080 2MHz) крутить 256 счетчиков - весь пар свисток уйдет. Да и не меньших количествах уже не подарок. Такие тривиальные вещи вообще незачем делать "универсальными", если стоит задача сэкономить все и вся. Правильнее писать по месту, что для подобных вырожденных случаев ничуть не сложнее. Цитата Раньше было if( timers[i] > 0 ) --timers[i]; Это было на целых 2 байта длиннее! При прочих равных настройках оптимизации и т.д. Не верю! Если, вдруг, не одно и тоже, то первым шагом борьбы за что либо, следует выкинуть используемый компилятор. А самое читабельное: Код if( timers[i] ) timers[i]--;
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Feb 26 2017, 12:47
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Цитата Про "еще один байт" не понял. Если у Вас цель какие-то байты экономить, а не такты, то так и напишите. Ну вообще в данный момент да, основа это байты. Ибо AVR Tiny, задачи две - три...ну пять в худший день. Цитата Для, например, 256 задач (реальный случай во времена i8080 2MHz) крутить 256 счетчиков - весь пар свисток уйдет. Полностью согласен. Надеюсь поделитесь алгоритмом для такого случая? Интересно. Предполагаю, что надо крутить один счетчик в прерывании, а в задаче хранить переменную со значением счётчика при котором надо срабатывать. Потом в RunScheduled() брать текущее значение счетчика, проходиться по задачам и сравнивать. После выполнения задачи переменную увеличивать вперед на период. Я правильно мыслю или есть более оптимальный вариант? Просто так получается да, такты прерывания мы экономим, но имеем кучу сравнений(и сравнений не с константами) в RunScheduled(). Цитата Такие тривиальные вещи вообще незачем делать "универсальными" Согласен, но я тут просто добрался до шаблонов в С++ и конечно же страдаю "переиспользованием". Но всё равно полезно ) Цитата Не верю! Если, вдруг, не одно и тоже, то первым шагом борьбы за что либо, следует выкинуть используемый компилятор. Код for(uint8_t i=0; i < TaskListLen; i++) timers[i] = (timers[i]>0)? (timers[i]-1):(timers[i]); Program Memory Usage : 160 bytes 15,6 % Full Data Memory Usage : 2 bytes 3,1 % Full { 00000019 PUSH R1 Push register on stack 0000001A PUSH R0 Push register on stack 0000001B IN R0,0x3F In from I/O location 0000001C PUSH R0 Push register on stack 0000001D CLR R1 Clear Register 0000001E PUSH R24 Push register on stack RELOAD_TCNT0(); 0000001F LDI R24,0x82 Load immediate 00000020 OUT 0x32,R24 Out to I/O location --- D:\Poligon\Scheduler\TinyScheduler\TinyScheduler\Debug/.././TinyScheduler.hpp timers[i] = (timers[i]>0)? (timers[i]-1):(timers[i]); 00000021 LDS R24,0x0060 Load direct from data space 00000023 CPSE R24,R1 Compare, skip if equal 00000024 SUBI R24,0x01 Subtract immediate 00000025 STS 0x0060,R24 Store direct to data space 00000027 LDS R24,0x0061 Load direct from data space 00000029 CPSE R24,R1 Compare, skip if equal 0000002A SUBI R24,0x01 Subtract immediate 0000002B STS 0x0061,R24 Store direct to data space --- D:\Poligon\Scheduler\TinyScheduler\TinyScheduler\Debug/.././main.cpp ------- } 0000002D POP R24 Pop register from stack 0000002E POP R0 Pop register from stack 0000002F OUT 0x3F,R0 Out to I/O location 00000030 POP R0 Pop register from stack 00000031 POP R1 Pop register from stack 00000032 RETI Interrupt return Предложенный вами вариант: Код for(uint8_t i=0; i < TaskListLen; i++) if( timers[i] ) timers[i]--; Program Memory Usage : 164 bytes 16,0 % Full Data Memory Usage : 2 bytes 3,1 % Full { 00000019 PUSH R1 Push register on stack 0000001A PUSH R0 Push register on stack 0000001B IN R0,0x3F In from I/O location 0000001C PUSH R0 Push register on stack 0000001D CLR R1 Clear Register --- D:\Poligon\Scheduler\TinyScheduler\TinyScheduler\Debug/.././main.cpp ------- 0000001E PUSH R24 Push register on stack RELOAD_TCNT0(); 0000001F LDI R24,0x82 Load immediate 00000020 OUT 0x32,R24 Out to I/O location --- D:\Poligon\Scheduler\TinyScheduler\TinyScheduler\Debug/.././TinyScheduler.hpp if( timers[i] ) 00000021 LDS R24,0x0060 Load direct from data space 00000023 TST R24 Test for Zero or Minus 00000024 BREQ PC+0x04 Branch if equal timers[i]--; 00000025 SUBI R24,0x01 Subtract immediate 00000026 STS 0x0060,R24 Store direct to data space if( timers[i] ) 00000028 LDS R24,0x0061 Load direct from data space 0000002A TST R24 Test for Zero or Minus 0000002B BREQ PC+0x04 Branch if equal timers[i]--; 0000002C SUBI R24,0x01 Subtract immediate 0000002D STS 0x0061,R24 Store direct to data space --- D:\Poligon\Scheduler\TinyScheduler\TinyScheduler\Debug/.././main.cpp ------- } 0000002F POP R24 Pop register from stack 00000030 POP R0 Pop register from stack 00000031 OUT 0x3F,R0 Out to I/O location 00000032 POP R0 Pop register from stack 00000033 POP R1 Pop register from stack 00000034 RETI Interrupt return получилось даже 4 байта, а не 2. Компилятор: Цитата Invoking: AVR8/GNU C Compiler : 4.9.2 "C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin\avr-g++.exe" -funsigned-char -funsigned-bitfields -DDEBUG -I"C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.1.102\include" -I"../include" -Os -ffunction-sections -fdata-sections -fpack-struct -fshort-enums -g2 -Wall -mmcu=attiny13a -B "C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.1.102\gcc\dev\attiny13a" -c -fno-threadsafe-statics -fno-exceptions -fno-rtti -MD -MP -MF "main.d" -MT"main.d" -MT"main.o" -o "main.o" ".././main.cpp"
--------------------
The truth is out there...
|
|
|
|
|
Feb 26 2017, 17:24
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(sigmaN @ Feb 26 2017, 14:47)  Код timers[i] = (timers[i]>0)? (timers[i]-1):(timers[i]); 00000021 LDS R24,0x0060 Load direct from data space 00000023 CPSE R24,R1 Compare, skip if equal 00000024 SUBI R24,0x01 Subtract immediate 00000025 STS 0x0060,R24 Store direct to data space 00000027 LDS R24,0x0061 Load direct from data space 00000029 CPSE R24,R1 Compare, skip if equal 0000002A SUBI R24,0x01 Subtract immediate 0000002B STS 0x0061,R24 Store direct to data space Древний IAR компилятор, какой уж был по руками, уложился на одну комаду меньше, если Вы хотели объем минимизировать. Код if( timers[i] ) timers[i]--; \ 00000000 .... LDI R30, timers \ 00000002 0FE0 ADD R30, R16 \ 00000004 8100 LD R16, Z \ 00000006 2300 TST R16 \ 00000008 F011 BREQ ??TimerISR_0 \ 0000000A 950A DEC R16 \ 0000000C 8300 ST Z, R16
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Feb 26 2017, 20:10
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(sigmaN @ Feb 26 2017, 14:47)  Предполагаю, что надо крутить один счетчик в прерывании В микроконтролерах непременно есть счетчики, которые вообще крутить не надо. Аппаратные. То есть отпадает целый кусок кода, который вызыватся каждый тик. Цитата Просто так получается да, такты прерывания мы экономим, но имеем кучу сравнений.... Куча сравнений подается оптимизации и, главное, это уже происходит не каждый тик таймера, а по завершении очередной задачи. Причем по завершении есть вероятность, что вообще времени девать некуда, ибо ни одна из задач запуска не ждет. Кроме всего этого просто так взять первую попавшуюся задачу и запустить по причине того, что счетчик до 0 досчитал, скорее всего бессмысленно - надо таки с приоритетами разбираться, а это опять "страшные" сравнения. Цитата(sigmaN @ Feb 26 2017, 19:41)  Вы алгоритмом проворачивания 256 задач не поделитесь? Там была очень специфичная система под 256 одинаковых задач. Но счетчики в прерывании не крутились. Причины выше.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Feb 26 2017, 23:51
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Ну да, не крутить счетчики в прерывании это хорошая идея Переделал RunScheduled() на: Код if( static_cast<TimerT>((TimeService::GetTickCount() - timers[I])) >= Task::Period ) { Task::Run(); timers[I] = TimeService::GetTickCount(); } Т.е. в массиве timers[] теперь хранится время последнего запуска задачи. На тестовом примере и по тактам и по байтам получилось очень даже хорошо! Правда, на AVR при частоте 8МГц мне аппаратный счетчик не удалось заставить работать с миллисекундной частотой и если нужны многомиллисекундные задержки то в каждой задаче дополнительный делитель делать придется. Но даже когда я сделал миллисекундное прерывание в котором крутится софтовый счетчик Код static volatile uint8_t G_TimerTicks; static uint8_t GetTickCount() { return G_TimerTicks; }; ISR(TIM0_COMPA_vect) { G_TimerTicks++; } результат всё равно превзошел предыдущий и по байтам(на 4байта) и тем более по тактам! Я в общем так сосредоточился на шаблонах и рекурсии, что слона в упор не заметил )))))))
--------------------
The truth is out there...
|
|
|
|
|
Feb 28 2017, 21:26
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Делюсь окончательным вариантом с учетом замечаний. Сам планировщик: http://pastebin.com/RqVLaFrJПроект Atmel Studio 7 для тестов и веселья
TinyScheduler_ver6_ElecronixEDIT_.zip ( 174.88 килобайт )
Кол-во скачиваний: 40P.S. GCC опять веселит. Если собрать проект с оптимизацией -O1 то он почему-то окажется на 4 байта меньше, чем собранный с оптимизацией по размеру -Os
--------------------
The truth is out there...
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|