Здравствуйте, Евгений Музыченко, Вы писали:
M>>Зачем руками, когда есть PROTOTHREADS?
ЕМ>Оно ж совсем невнятное. Что толку от имитации последовательной работы, когда значения переменных не сохраняются? Такое годится для тех, кто в параллельной обработке вообще ничего не смыслит, чтоб могли тупо записать простенький последовательный алгоритм.
Храни как поля структуры. Нормас, я пользовался, жить вполне можно
ЕМ>Я б сделал объекты/структуры, хранящие состояния процессов, функции-обработчики событий, и главный диспетчер, обнаруживающий события и вызывающий обработчики.
Зачем? Есть куча встраиваемых ОС, зачем писать ещё одну?
Здравствуйте, Евгений Музыченко, Вы писали:
vsb>>не хватает аналога async/await в С. Чтобы я мог записать алгоритм в простом виде, а компилятор из этого сварганил машину состояний и оно бы работало параллельно с другими такими же машинами состояний.
ЕМ>Такое может неплохо работать на мощных процессорах, когда запас по быстродействию большой. На МК это рискует тормозить, причем непредсказуемо.
Автоматы нормально работают и на МК, и никаких особенных требований по быстродействию не требуют. Не вижу, почему бы автомат, сгенерированный компилятором, был бы сильно хуже
ЕМ>А есть реализации Rust под требуемые МК?
Хз, у меня коллега по эмбеду дрочил на раст, но вроде ничего не приносил. Если бы нашел, то уверен, что попробовал бы это нам продать. Но это было несколько лет назад
vsb>это блокирующий алгоритм. Если хочется его делать не в блокирующем режиме, то этот алгоритм переводится в машину состояний вроде
vsb>
vsb>void process_events() {
vsb> switch (state) {
vsb> case WAIT_FOR_TBE:
vsb> if (TBE != 0) state = TRANSMIT_BYTE;
vsb> break;
vsb> case TRANSMIT_BYTE:
vsb> TRANSMIT_DATA := transmit_data[i];
vsb> state = WAIT_FOR_RBNE;
vsb> break;
vsb> case WAIT_FOR_RBNE:
vsb> if (RBNE != 0) state = RECEIVE_BYTE;
vsb> break;
vsb> case RECEIVE_BYTE:
vsb> receive_data[i] := RECEIVE_DATA;
vsb> state = NEXT_ITERATION;
vsb> break;
vsb> case NEXT_ITERATION:
vsb> i++;
vsb> if (i < length) {
vsb> state = WAIT_FOR_TBE;
vsb> } else {
vsb> state = END;
vsb> }
vsb> break;
vsb> }
vsb>}
vsb>
vsb>Как видно, блокирующий алгоритм переделали в неблокирующий, теперь надо только достаточно часто вызывать process_events();
vsb>но переделка абсолютно механическая и по этому алгоритму работают многие языки, реализуя async/await под капотом именно в таком виде.
vsb>Т.е. можно было бы взять начальный код
vsb>
vsb>for (size_t i = 0; i < length; i++) {
vsb> while (TBE == 0) yield; // ждем пока не включится флаг TRANSMIT_BUFFER_EMPTY
vsb> TRANSMIT_DATA := transmit_data[i] // записываем первый байт
vsb> while (RBNE == 0) yield; // ждём пока не включится флаг RECEIVE_BUFFER_NOT_EMPTY
vsb> receive_data[i] := RECEIVE_DATA;
vsb>}
vsb>
vsb>И по yield разбить функцию на куски и каждый кусок оформить в виде куска в switch машины состояний. Ну тут нет вызовов других функций, в общем случае всё: конечно, может быть похитрей, но суть та же.
vsb>Понятно, что можно вкорячить туда какой-нибудь FreeRTOS и запускать такие алгоритмы, как полновесные процессы, но это кажется ненужным усложнением.
Не надо вкорячивать никаких ОС, это как есть PROTOTHREADS, там один файл и пяток макросов, состояния делаются автоматом на базе __COUNTER или как его, как раз то, что тебе нужно
Да, стека там нет, PROTOTHREADS хранит данные в структуре. Я на них делал здоровые автоматы строк по паре тысяч для разных роботов, никаких проблем с вызовом функций, только переменные не завести
ЕМ>Вообще, любой конечный автомат естественным образом ложится на объект (в предельном случае — структуру), где хранится состояние, и набор методов/функций вокруг него.
ЕМ>Если хотите, чтоб код оставался линейным, и паузы по вызову yield делались внутри функции, то не обойтись без переключения (или копирования/восстановления) стека. Для этого нужно знать, как реализация языка работает со стеком. Тут без ассемблерной обвязки вряд ли можно обойтись.
Не надо никаких переключений. PROTOTHREADS нормально делает без всякого переключения. Только структура с состоянием данного прототреда, как ты любишь
Здравствуйте, Marty, Вы писали:
M>Храни как поля структуры.
Я в таких случаях делаю объект.
M>Есть куча встраиваемых ОС, зачем писать ещё одну?
Полноценную ОС — разумеется, незачем. А просто диспетчер обработки событий часто проще сделать свой. Он всегда полностью понятен, управляем, в нем не бывает глюков, которых не понять без хорошего знания потрохов, его можно подстраивать под себя, а не подстраиваться под него, у него нет проблем с лицензированием и т.п.
Собственно, все это справедливо для любого вспомогательного кода до некоторого уровня сложности. После этого уровня уже выгоднее применять сторонние решения.
Здравствуйте, Marty, Вы писали:
M>почему бы автомат, сгенерированный компилятором, был бы сильно хуже
Мне трудно представить ситуацию, в которой автомат, сделанный компилятором, в то же самое время и заметно облегчил бы жизнь, и не оказался чрезмерно раздутым в реализации.
Если есть линейный алгоритм с ожиданиями, где количество состояний в районе десятка, то он примерно одинаково понятен и в линейном виде, и в виде единого switch, и в виде набора обработчиков, которые вызывает диспетчер событий. Если же состояний значительно больше, то в графе определенно должны быть явные узлы, которые нужно выделять в отдельные сущности и как-то изолировать.
Здравствуйте, Евгений Музыченко, Вы писали:
M>>почему бы автомат, сгенерированный компилятором, был бы сильно хуже
ЕМ>Мне трудно представить ситуацию, в которой автомат, сделанный компилятором, в то же самое время и заметно облегчил бы жизнь, и не оказался чрезмерно раздутым в реализации.
То есть ты до сих пор пишешь на асме, потому что код, сгенерированный компилятором не облегчает заметно жизнь, но чрезвычайно раздут?
ЕМ>Если есть линейный алгоритм с ожиданиями, где количество состояний в районе десятка, то он примерно одинаково понятен и в линейном виде, и в виде единого switch, и в виде набора обработчиков, которые вызывает диспетчер событий.
Диспечер событий какой-то городить. Зачем?
ЕМ>Если же состояний значительно больше, то в графе определенно должны быть явные узлы, которые нужно выделять в отдельные сущности и как-то изолировать.
Компутер умеет оптимизировать автоматы гораздо лучше человеков
Здравствуйте, Marty, Вы писали:
M>То есть ты до сих пор пишешь на асме, потому что код, сгенерированный компилятором не облегчает заметно жизнь, но чрезвычайно раздут?
Я не пишу на асме потому, что C/C++ качественно облегчает жизнь, но сгенерированный код при этом получается лишь незначительно больше, если его контролировать. Если не контролировать, а тупо применять рекомендуемые решения, то получается обычно в разы больше.
M>Диспечер событий какой-то городить. Зачем?
Чтоб код не выглядел так убого, как написанный для ProtoThreads.
M>Компутер умеет оптимизировать автоматы гораздо лучше человеков
Специализированный софт для построения автоматов — умеет. А что знает об автоматах компилятор C?
Здравствуйте, Евгений Музыченко, Вы писали:
M>>То есть ты до сих пор пишешь на асме, потому что код, сгенерированный компилятором не облегчает заметно жизнь, но чрезвычайно раздут?
ЕМ>Я не пишу на асме потому, что C/C++ качественно облегчает жизнь, но сгенерированный код при этом получается лишь незначительно больше, если его контролировать. Если не контролировать, а тупо применять рекомендуемые решения, то получается обычно в разы больше.
Почему ты думаешь, что с автоматами будет по другому?
И, кстати, а что такое — в разы больше?
Я вот, как-то забил что-то контролировать, у меня экзешник — полтора метра (x64, x86 — 1.2), всё сторонее канпеляется в него, никаких кути в отдельных DLL и тп — нет. Меня полтора метра вполне устраивают, ничего улучшать не тянет.
У меня там рисовалка на GDI/GDI+, парсер json и вирт машина сквирела, это из значимого. Мелочи даже не считаю. И да, плюсики использую в полный рост, не парюсь, что код будет пухнуть от шаблонов
M>>Диспечер событий какой-то городить. Зачем?
ЕМ>Чтоб код не выглядел так убого, как написанный для ProtoThreads.
С ProtoThreads код нормально выглядит. А вот твой диспечер, скорее всего, будет убог и коряв, и с кучей багов.
Можно пример твоего супер диспечера, чтобы он такое бы делал?
M>>Компутер умеет оптимизировать автоматы гораздо лучше человеков
ЕМ>Специализированный софт для построения автоматов — умеет. А что знает об автоматах компилятор C?
А в нем и async/await нету. Это раз. А во вторых, я думаю, даже компилятор C знает об автоматах на порядок больше тебя
Здравствуйте, Marty, Вы писали:
M>Почему ты думаешь, что с автоматами будет по другому?
Я уже объяснил пару раз, почему автомат, сгенерированный компилятором по линейному коду, будет либо примерно того же уровня сложности/управляемости, что и ручной, либо будет иметь изрядное количество обслуживающего кода, от которого невозможно ни избавиться, ни толком вмешаться в его работу.
M>И, кстати, а что такое — в разы больше?
Это вдвое, втрое, впятеро и т.п. по сравнению с тем же алгоритмом, реализованным на ассемблере.
M>Меня полтора метра вполне устраивают, ничего улучшать не тянет.
А кого-то устраивает и полтора гига, и тоже не тянет ничего улучшать. Вопрос-то не в абсолютной потребности в ресурсах, а относительно объективных параметров решаемой задачи.
M>У меня там рисовалка на GDI/GDI+, парсер json и вирт машина сквирела, это из значимого
Если из значимого это все, то полтора метра — ну очень много.
M>С ProtoThreads код нормально выглядит.
По сравнению с ассемблерным — безусловно.
M>А вот твой диспечер, скорее всего, будет убог и коряв, и с кучей багов.
Почему "скорее всего"?
M>Можно пример твоего супер диспечера, чтобы он такое бы делал?
Во-первых, он не "супер" — это совершенно заурядное решение, мне даже как-то странно, что Вы по этому поводу так возбудились. Во-вторых, я последний раз делал сколько-нибудь универсальную реализацию под DOS, исходники не факт, что сохранились, не вижу смысла их искать, ибо то был ни разу не rocket science.
M>думаю, даже компилятор C знает об автоматах на порядок больше тебя
Из каких соображений Вы так думаете, и что именно он знает?
Здравствуйте, vdimas, Вы писали:
V>>>Если пользуешь Си, то для оперирования объявленными статическими данными (структурами, массивами) необходим CRT. M>>Что именно из CRT нужно для этого?
V>Двухфазная инициализация глобальных данных. V>Причём, при внешнем загрузчике CRT обыгрывает только вторую фазу (динамической инициализации), а в гарварде CRT обыгрывает обе фазы.
V>Упреждая пинг-понг вопросов-ответов: V>
V>int a = 42;
V>int b = calcB();
V>
V>Здесь в бинарном образе в сегменте данных по адресу a будет число 42, по адресу b будет 0.
V>Внешний загрузчик грузит образ в память, переменная a уже инициализирована (это первая фаза), а переменная b будет инициализирована из глобального связанного списка инициализации, где каждый узел примерно такого вида: V>
V>CRT пробегается по этому списку, вызывая код динамической инициализации — это вторая фаза стандартной инициализации С/С++.
V>Для инициализации модуля, содержащего динамическую инициализацию (здесь глобальной переменной b), будет порождена безымянная ф-ия примерно такого вида: V>
V>void __some_init_xxx() {
V> b = calcB();
V>}
V>
V>Указатель на эту ф-ию будет в одном из узлов __init_module в глобальном списке динамической инициализации.
V>Для микриков обычно нет связанного списка, динамическая инициализация представляет собой развернутый (типа заинлайненный) код, но это всё-равно лишний код, который почти всегда не нужен, т.к. любые начальные данные или не нужны, или являются константами, тогда им не место в ОЗУ.
Честно говоря я ничего не понял. Можно пример минимальной компилируемой программы? Глобальные переменные нельзя инициализировать чем-либо кроме констант. Константы идут в .data и всё, без всяких init.
int calcB();
int a = 42;
int b = calcB();
Этот код не компилируется: error: initializer element is not constant
Здравствуйте, Евгений Музыченко, Вы писали:
vsb>>Этот код не компилируется: error: initializer element is not constant
ЕМ>В чистом C такого нельзя. В C++ можно.
А для чего-то ещё может понадобиться инициализация? Просто у меня в объектном файле откуда-то затесались пустые функции _init и _fini, при том, что libc вроде не линкуется, я и пытаюсь сообразить — что это и зачем. На С.
Здравствуйте, Евгений Музыченко, Вы писали:
vsb>>в объектном файле откуда-то затесались пустые функции _init и _fini
ЕМ>В объектном коде есть их вызовы? Они попадают в конечный бинарник? Если да, кто их там вызывает?
Нет, вызовов нет. В конечный бинарник попадают, вызовов там нет. Программа почти минимальная (без оптимизации).
Здравствуйте, Евгений Музыченко, Вы писали:
vsb>>В конечный бинарник попадают, вызовов там нет.
ЕМ>Посмотрите параметры командной строки линкера — возможно, там есть режимы для удаления неиспользуемых функций.
Это включено. Ладно, "вручную" удалил через линкер.