Перейти к содержанию
    

Покритикуйте реализацию простейшего шедулера на C++

Всем привет.

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

 

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

 

В архиве проект Atmel Studio 7.0 с минимальной демонстрацией, можно погонять в симуляторе) TinyScheduler_ver5_Loki_.zip

Для тех у кого нет Atmel Studio вот ссылка на исходник шедулера http://pastebin.com/7YVSAFBn

 

Читайте комментарии в шапке файла. Там все подробности использования и настройки.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В общем выкладываю с целью получения обратной связи.

Неконсистентно. Тогда уже следует ожидать "фидбэка".

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Не поверите, написал фидбэка и исправил, вспомнив про вашу любовь к великому и могучему. Но фичи исправить не подумал.

Ожидаю комьюнити фидбэка )))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Основным требованием при реализации был минимальный оверхед во всем.

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

timers[i] = (timers[i]>0)? (timers[i]-1):(timers[i]);

это как бы уже перебор.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

[offtop]

Скедулер, если что.

[/offtop]

Вот-вот. Мы сейчас вступим в дискуссию о том, как писать правильно асе эти англо-термины русскими буквами...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

крутить свой счетчик для каждой из задач идея сама по себе редко когда удачная.
Я вот сейчас немного подумал и.... всё равно не придумал как можно сделать оптимальнее. Как мне кажется во всех вариантах будет еще один байт.

 

timers = (timers>0)? (timers-1):(timers);

это как бы уже перебор.

Ну у меня 0 в счетчике это признак запуска задачи, так что проверять на 0 приходится.

Раньше было

for(uint8_t i=0; i < TaskListLen; i++)
        {
            if( timers[i] > 0 )
              --timers[i];
        }

Это было на целых 2 байта длиннее! При прочих равных настройках оптимизации и т.д. Я, так сказать, подчинился основной цели и выбрал сэкономить 2 байта флэша путем ухудшения читабельности кода.

 

P.S.

Я ночью писал двольно длинную шапку коментов на английском, слушал выступления Скотта Майерса на CPPCon и в момент публицации этого вопроса на форуме у меня была полная голова фич, оверхеда и шедулеров ))))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я вот сейчас немного подумал и.... всё равно не придумал как можно сделать оптимальнее. Как мне кажется во всех вариантах будет еще один байт.

Про "еще один байт" не понял. Если у Вас цель какие-то байты экономить, а не такты, то так и напишите.

Для, например, 256 задач (реальный случай во времена i8080 2MHz) крутить 256 счетчиков - весь пар свисток уйдет. Да и не меньших количествах уже не подарок.

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

Раньше было

if( timers > 0 )

--timers;

Это было на целых 2 байта длиннее! При прочих равных настройках оптимизации и т.д.

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

А самое читабельное:

if( timers[i] )
    timers[i]--;

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Про "еще один байт" не понял. Если у Вас цель какие-то байты экономить, а не такты, то так и напишите.
Ну вообще в данный момент да, основа это байты. Ибо 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"

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

            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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ладно, Бог с ними с компиляторами. Вы алгоритмом проворачивания 256 задач не поделитесь?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Предполагаю, что надо крутить один счетчик в прерывании

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

Просто так получается да, такты прерывания мы экономим, но имеем кучу сравнений....

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

Вы алгоритмом проворачивания 256 задач не поделитесь?

Там была очень специфичная система под 256 одинаковых задач. Но счетчики в прерывании не крутились. Причины выше.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну да, не крутить счетчики в прерывании это хорошая идея :biggrin:

Переделал RunScheduled() на:

if( static_cast<TimerT>((TimeService::GetTickCount() - timers[I])) >= Task::Period )
            {
                Task::Run();
                timers[I] = TimeService::GetTickCount();
            }

Т.е. в массиве timers[] теперь хранится время последнего запуска задачи.

На тестовом примере и по тактам и по байтам получилось очень даже хорошо! :08:

 

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

 

Но даже когда я сделал миллисекундное прерывание в котором крутится софтовый счетчик

static volatile uint8_t G_TimerTicks;
static uint8_t GetTickCount()
    {         
        return G_TimerTicks; 
    };
ISR(TIM0_COMPA_vect)
{
    G_TimerTicks++;    
}

результат всё равно превзошел предыдущий и по байтам(на 4байта) и тем более по тактам!

 

Я в общем так сосредоточился на шаблонах и рекурсии, что слона в упор не заметил )))))))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Делюсь окончательным вариантом с учетом замечаний.

 

Сам планировщик:

http://pastebin.com/RqVLaFrJ

 

Проект Atmel Studio 7 для тестов и веселья TinyScheduler_ver6_ElecronixEDIT_.zip

 

P.S.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Делюсь окончательным вариантом с учетом замечаний.

А вот если бы это была не ссылка на pastebin, а ссылка на репозиторий на github, то вместо того, чтобы перечитывать заново весь исходник, можно было бы посмотреть diff...

Это так, намёк:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...