Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Покритикуйте реализацию простейшего шедулера на C++
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
sigmaN
Всем привет.
Вот тут на днях допилил свой Tiny таск шедулер. Основным требованием при реализации был минимальный оверхед во всем. Однако есть возможность конфигурировать его под разные задачи, опять же без оверхеда.

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

В архиве проект Atmel Studio 7.0 с минимальной демонстрацией, можно погонять в симуляторе) Нажмите для просмотра прикрепленного файла
Для тех у кого нет Atmel Studio вот ссылка на исходник шедулера http://pastebin.com/7YVSAFBn

Читайте комментарии в шапке файла. Там все подробности использования и настройки.
Herz
Цитата(sigmaN @ Feb 25 2017, 20:20) *
В общем выкладываю с целью получения обратной связи.

Неконсистентно. Тогда уже следует ожидать "фидбэка".
sigmaN
Не поверите, написал фидбэка и исправил, вспомнив про вашу любовь к великому и могучему. Но фичи исправить не подумал.
Ожидаю комьюнити фидбэка )))
zltigo
Цитата(sigmaN @ Feb 25 2017, 20:20) *
Основным требованием при реализации был минимальный оверхед во всем.

Очень не похоже, что основное требование выполнено, например, крутить свой счетчик для каждой из задач идея сама по себе редко когда удачная. Ну и я понимаю, что оптимизация великая сила, и наверняка сделает все возможное, но писать такое:
Код
timers[i] = (timers[i]>0)? (timers[i]-1):(timers[i]);

это как бы уже перебор.
Эдди
[offtop]
Скедулер, если что.
[/offtop]
Herz
Цитата(Эдди @ Feb 26 2017, 09:08) *
[offtop]
Скедулер, если что.
[/offtop]

Вот-вот. Мы сейчас вступим в дискуссию о том, как писать правильно асе эти англо-термины русскими буквами...
sigmaN
Цитата
крутить свой счетчик для каждой из задач идея сама по себе редко когда удачная.
Я вот сейчас немного подумал и.... всё равно не придумал как можно сделать оптимальнее. Как мне кажется во всех вариантах будет еще один байт.

Цитата
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 и в момент публицации этого вопроса на форуме у меня была полная голова фич, оверхеда и шедулеров ))))
zltigo
Цитата(sigmaN @ Feb 26 2017, 12:35) *
Я вот сейчас немного подумал и.... всё равно не придумал как можно сделать оптимальнее. Как мне кажется во всех вариантах будет еще один байт.

Про "еще один байт" не понял. Если у Вас цель какие-то байты экономить, а не такты, то так и напишите.
Для, например, 256 задач (реальный случай во времена i8080 2MHz) крутить 256 счетчиков - весь пар свисток уйдет. Да и не меньших количествах уже не подарок.
Такие тривиальные вещи вообще незачем делать "универсальными", если стоит задача сэкономить все и вся. Правильнее писать по месту, что для подобных вырожденных случаев ничуть не сложнее.
Цитата
Раньше было
if( timers[i] > 0 )
--timers[i];
Это было на целых 2 байта длиннее! При прочих равных настройках оптимизации и т.д.

Не верю! Если, вдруг, не одно и тоже, то первым шагом борьбы за что либо, следует выкинуть используемый компилятор.
А самое читабельное:
Код
if( timers[i] )
    timers[i]--;
sigmaN
Цитата
Про "еще один байт" не понял. Если у Вас цель какие-то байты экономить, а не такты, то так и напишите.
Ну вообще в данный момент да, основа это байты. Ибо 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"
zltigo
Цитата(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
sigmaN
Ладно, Бог с ними с компиляторами. Вы алгоритмом проворачивания 256 задач не поделитесь?
zltigo
Цитата(sigmaN @ Feb 26 2017, 14:47) *
Предполагаю, что надо крутить один счетчик в прерывании

В микроконтролерах непременно есть счетчики, которые вообще крутить не надо. Аппаратные. То есть отпадает целый кусок кода, который вызыватся каждый тик.
Цитата
Просто так получается да, такты прерывания мы экономим, но имеем кучу сравнений....

Куча сравнений подается оптимизации и, главное, это уже происходит не каждый тик таймера, а по завершении очередной задачи. Причем по завершении есть вероятность, что вообще времени девать некуда, ибо ни одна из задач запуска не ждет. Кроме всего этого просто так взять первую попавшуюся задачу и запустить по причине того, что счетчик до 0 досчитал, скорее всего бессмысленно - надо таки с приоритетами разбираться, а это опять "страшные" сравнения.
Цитата(sigmaN @ Feb 26 2017, 19:41) *
Вы алгоритмом проворачивания 256 задач не поделитесь?

Там была очень специфичная система под 256 одинаковых задач. Но счетчики в прерывании не крутились. Причины выше.
sigmaN
Ну да, не крутить счетчики в прерывании это хорошая идея biggrin.gif
Переделал RunScheduled() на:
Код
if( static_cast<TimerT>((TimeService::GetTickCount() - timers[I])) >= Task::Period )
            {
                Task::Run();
                timers[I] = TimeService::GetTickCount();
            }

Т.е. в массиве timers[] теперь хранится время последнего запуска задачи.
На тестовом примере и по тактам и по байтам получилось очень даже хорошо! 08.gif

Правда, на AVR при частоте 8МГц мне аппаратный счетчик не удалось заставить работать с миллисекундной частотой и если нужны многомиллисекундные задержки то в каждой задаче дополнительный делитель делать придется.

Но даже когда я сделал миллисекундное прерывание в котором крутится софтовый счетчик
Код
static volatile uint8_t G_TimerTicks;
static uint8_t GetTickCount()
    {         
        return G_TimerTicks;
    };
ISR(TIM0_COMPA_vect)
{
    G_TimerTicks++;    
}
результат всё равно превзошел предыдущий и по байтам(на 4байта) и тем более по тактам!

Я в общем так сосредоточился на шаблонах и рекурсии, что слона в упор не заметил )))))))
sigmaN
Делюсь окончательным вариантом с учетом замечаний.

Сам планировщик:
http://pastebin.com/RqVLaFrJ

Проект Atmel Studio 7 для тестов и веселья Нажмите для просмотра прикрепленного файла

P.S.
GCC опять веселит. Если собрать проект с оптимизацией -O1 то он почему-то окажется на 4 байта меньше, чем собранный с оптимизацией по размеру -Os
AHTOXA
Цитата(sigmaN @ Mar 1 2017, 02:26) *
Делюсь окончательным вариантом с учетом замечаний.

А вот если бы это была не ссылка на pastebin, а ссылка на репозиторий на github, то вместо того, чтобы перечитывать заново весь исходник, можно было бы посмотреть diff...
Это так, намёкsm.gif
Make_Pic
Цитата(sigmaN @ Mar 1 2017, 01:26) *
Делюсь окончательным вариантом с учетом замечаний.

Сам планировщик:
http://pastebin.com/RqVLaFrJ

Проект Atmel Studio 7 для тестов и веселья Нажмите для просмотра прикрепленного файла

P.S.
GCC опять веселит. Если собрать проект с оптимизацией -O1 то он почему-то окажется на 4 байта меньше, чем собранный с оптимизацией по размеру -Os


А вообще зачем здесь плюсы? - Только поиграться с шаблонами?
uriy
sigmaN вы знаете о protothreads? Я не знаток С++ и мне сложно ориентироваться в коде. Какие у вас преимущетсва на фоне protothreads?
sigmaN
Цитата
А вот если бы это была не ссылка на pastebin, а ссылка на репозиторий на github,
Да, надо там зарегиться наконец )

Цитата
А вообще зачем здесь плюсы? - Только поиграться с шаблонами?
Поиграться с шаблонами - это раз. Получить хорошую реализацию, соответствующую всем принципам инкапсуляции - это да. Ну а задать вопрос "Зачем здесь плюсы?" можно к любому проекту. Ведь всё можно сделать и без плюсов ))

Цитата
Какие у вас преимущетсва на фоне protothreads?
Никаких. У меня дёргалка функций с заданной частотой. protothreads же нацелен именно на создание "потоков" и обеспечение примитивов синхронизации потоков( PT_WAIT_UNTIL() и его производные ). Кстати нужно будет подумать как реализовать всё это на плюсах, со сравнимой эффективностью но без фокусов с препроцессором, к которым пришлось прибегнуть авторам protothreads... Интересная задачка на будущее кстати. Пока не уверен, что удастся побить по тактам их макросный фокус со switch()
sigmaN
Add:
Даа, с protothreads не поспоришь. Максимум C++ обертку можно сделать чтоб чуть красиее было и всё. А внутри сложно поспорить по эффективности с одной переменной и switch'ем, причем очень удобно замаскированным и невидимым для пользователя ))
uriy
Пожалуй единственное что мне не нравится в protothreads, я не соображу как ее сделать ticlkess для low power приложений.
Недавно перенес планировщик от NRF51822 на STM8L когда используются пара виртуальных таймеров все отлично работает.
Когда начинаю их активно останавливать/запускать начинаются глюки.
arhiv6
Цитата
Максимум C++ обертку можно сделать чтоб чуть красивее было и всё.

уже было такое: https://github.com/benhoyt/protothreads-cpp
psL
Цитата(AHTOXA @ Mar 1 2017, 01:09) *
А вот если бы это была не ссылка на pastebin, а ссылка на репозиторий на github, то вместо того, чтобы перечитывать заново весь исходник, можно было бы посмотреть diff...
Это так, намёкsm.gif

кстати, репозиторий необязателен, т.к. для расшаривания небольших сниппетов есть https://gist.github.com/ с поддержкой контроля версий
jcxz
Цитата(uriy @ Mar 3 2017, 06:22) *
Пожалуй единственное что мне не нравится в protothreads, я не соображу как ее сделать ticlkess для low power приложений.

А в чём проблема? Заменяете периодические прерывания по системному таймеру на программирование таймера на прерывание в момент времени ближайшего таймерного события одной из задач.
sigmaN
Кстати в С++17 ожидается поддержка Coroutines. Т.е. мы будем иметь продвинутые С++ protothreads прям в ядре языка!
https://youtu.be/_fu0gx-xseY
https://youtu.be/8C8NnE1Dg4A
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2024 Invision Power Services, Inc.