Совсем даже не глупый. Побудило меня к созданию ПЛК то, что потребовалось организовать взаимодействие между устройствами по сети. Это обстоятельство, потянуло за собой другую смежную задачу, нужно было каким-то образом предобрабатывать полученные значения от других устройств, и только потом давать команду на исполнительные механизмы самого устройства. Менять логику устройства каждый раз перешивая бинарник довольно муторное занятие. Напротив, ПЛК по TCP-IP получает новый скрипт буквально за одну секунду, и прямо на горячую (без перезагрузки), принимает его к исполнению. Как плюшка, зацикленный или битый скрипт будет просто проигнорирован ПЛК, поэтому можно тут же исправить ошибку и перезалить скрипт.
Замерил скорость исполнения байт-кода такого вот простейшего сценария мигалки. На выходе получил 43кГц меандр (платформа ESP8266 80МГц). Не идеальная производительность, но всё же раз в десять лучше чем у Lua и Espruino. Для моих задач вполне хватает. http://esp8266freq.blogspot.com/2016/12/esp-speed-compare-in-lua-micropython.html https://www.espruino.com/Performance Как это выглядит в работе, здесь: http://mgt24.ru/en/home.html#5ba362d917f9911528d00810/1
Как печально-то, на самом деле. Либо байт-код - гуано, либо интерпретатор байт-кода - такое же. Имхо.
Чой-то решил проверить на Uno, пока тупо сделал так, чтобы понять, от чего отталкиваться: Код (C++): //-------------------------------------------------------------------------------------------------------------------------------- void setup() { Serial.begin(57600); pinMode(13,OUTPUT); uint16_t passes = 1000; uint32_t start = millis(); for(uint16_t i=0;i<passes;i++) { digitalWrite(13,HIGH); digitalWrite(13,LOW); } uint32_t end = millis(); uint32_t elapsed = end - start; Serial.print(F("Count passes: ")); Serial.println(passes); Serial.print(F("Elapsed time, ms: ")); Serial.println(elapsed); float hz = (1.0/elapsed)*1000*passes; Serial.print(F("Frequency: ")); Serial.println(hz); } //-------------------------------------------------------------------------------------------------------------------------------- void loop() { // put your main code here, to run repeatedly: } //-------------------------------------------------------------------------------------------------------------------------------- Выхлоп в мониторе порта: Теперь попробую набросать простенький интерпретатор одного блока байт-кода - и глянем, смогу ли из 166кГц получить 43кГц
Погоди, я тут навскидку писал, расчёт частоты надо проверить - вдруг прокосячил. Да не вдруг, а точно - прокосячил, не то считал Короче, щас всё выясним Там же надо частоту меандра посчитать, а не то, что я считал Или - таки правильно считал? Код (C++): float hz = (1.0/elapsed)*1000*passes; // сколько раз за секунду выполнится тело цикла Например, у нас 1000 проходов заняло 5 мс. Значит: (1.0/5)*1000*1000 = с частотой 200 кГц вызываются две строки Код (C++): digitalWrite(13,HIGH); digitalWrite(13,LOW); Так будет понятнее, короче. В любом случае - нам интересны накладные расходы на байткод. Проверяю.
Простите, но вот мои 5 копеек: Исходя из того, что у локального цикла исполнения (как в Step7): 1) цикл чтения входов и флагов/параметров в памяти; 2) цикл исполнения(логика); 3) цикл записи выходов и флагов/параметров в память. 1 и 3 для глобальных переменных, а для логики(блока) есть и локальные. Примечание: флаги/параметры могут обрабатываться и в цикле 2 без 1 и 2... но не в этом дело. Нужен "механизм" подмены новых логических блоков (коррекция на горячую), который подменяет старые только после завершения всех 3-х циклов. А это некое подобие ОС. Можете ли Вы это реализовать на AVR(Arduino). Без этого как правило идея не работает. Как пример OMRON, OWEN и др. Все они сначала тормозят работу программы, потом заливают, и затем запускают... и при этом без инициализации параметров, и то не все/всегда. И ещё среды разработки производят всё-таки компиляцию(какую-то) перед заливкой. Поверьте механизм подмены не так прост. Извините!
Итак: Код (C++): //-------------------------------------------------------------------------------------------------------------------------------- typedef enum { incrementForBlock, endForBlock, pinWriteBlock } BlockType; //-------------------------------------------------------------------------------------------------------------------------------- uint8_t block[] = { incrementForBlock, // это цикл for c инкрементом 0x03, 0xE8, // 1000 проходов 1, // шаг инкремента // тут тело цикла pinWriteBlock, // пишем в пин 13, // номер пина HIGH, // уровень pinWriteBlock, // пишем в пин 13, // номер пина LOW, // уровень // конец тела цикла endForBlock // цикл for закончился }; //-------------------------------------------------------------------------------------------------------------------------------- void executePinWriteBlock(uint8_t* bytecode) { bytecode++; // пропустили идентификатор блока (pinWriteBlock) //Serial.println(*bytecode); //Serial.println(*(bytecode+1)); digitalWrite(*bytecode, *(bytecode+1)); } //-------------------------------------------------------------------------------------------------------------------------------- void executeIncrementForBlock(uint8_t* bytecode) { bytecode++; // пропустили идентификатор блока (incrementForBlock) uint16_t passes = (*bytecode++ << 8) | (*bytecode++); uint8_t increment = *bytecode++; Serial.print(F("Passes: ")); Serial.println(passes); Serial.print(F("Increment: ")); Serial.println(increment); uint32_t start = millis(); uint8_t* startCode = bytecode; bool canExitWhile = false; for(uint16_t i=0;i<passes;i+=increment) { canExitWhile = false; while(!canExitWhile) // шаримся по блокам тела цикла { switch(*startCode) { case pinWriteBlock: executePinWriteBlock(startCode); startCode += 3; // пропустили блок break; case endForBlock: //Serial.println("BEGIN"); startCode = bytecode; canExitWhile = true; break; default: //Serial.println("!!!!!"); break; } // switch } // while } // for uint32_t end = millis(); uint32_t elapsed = end - start; Serial.print(F("Count passes: ")); Serial.println(passes); Serial.print(F("Elapsed time, ms: ")); Serial.println(elapsed); float hz = (1.0/elapsed)*1000*passes; Serial.print(F("Frequency: ")); Serial.println(hz); } //-------------------------------------------------------------------------------------------------------------------------------- void setup() { Serial.begin(57600); pinMode(13,OUTPUT); uint16_t passes = 1000; uint32_t start = millis(); for(uint16_t i=0;i<passes;i++) { digitalWrite(13,HIGH); digitalWrite(13,LOW); } uint32_t end = millis(); uint32_t elapsed = end - start; Serial.print(F("Count passes: ")); Serial.println(passes); Serial.print(F("Elapsed time, ms: ")); Serial.println(elapsed); float hz = (1.0/elapsed)*1000*passes; // сколько раз за секунду выполнится тело цикла Serial.print(F("Frequency: ")); Serial.println(hz); Serial.println(); Serial.println("BYTE CODE TEST: "); Serial.println(); executeIncrementForBlock(block); } //-------------------------------------------------------------------------------------------------------------------------------- void loop() { // put your main code here, to run repeatedly: } //-------------------------------------------------------------------------------------------------------------------------------- Выхлоп: Без байт-кода: ~140кГц (округляем). С байт-кодом: 100кГц. Накладные расходы, очевидно, е, и немалые, но! Во-первых, даже при таком колхозном подходе частота вызова двух строчек Код (C++): digitalWrite(13,HIGH); digitalWrite(13,LOW); через байт-код - составляет порядка 100 кГц. Во-вторых: никакой, нет, НИКАКОЙ оптимизации не проводилось, а там - есть где разгуляться, если делать всё строго по теории. Да даже отказаться от вызова функции executePinWriteBlock, заинлайнив её - уже будет быстрее, вангую Ессно, данное поделие не претендует на полноценный интерпретатор байт-кода, просто разместил объяву о том, что частота вызова двух строчек в 43кГц (ну пусть 43*2, меандр же) под камнем в 80МГц - это как-то совсем не алё, пмсм. Ну хотя бы пять сотен килогерц, не? В моём примере получается соотношение к тактовой 1/160, если подставить это к 80 МГц, то получим 500 кГц, навскидку. Где я неправ? Может, чего ступил и неправильно где посчитал (сегодня день заполошный, бошка уже не варит)? И да, строго говоря, отталкиваться от тактовой ядра - не совсем верно, вот тут могу как раз и наколоться, ибо разные блоки МК могут тактироваться по разному. Короче, дискуссии - быть, щитаю. Присоединяйтесь З.З.Ы. Ещё раз: тестировал на Uno, ибо под рукой что попалось - туда и впихнул. Могу NodeMCU достать, в принципе - и глянуть, чего там будет. Но - там ещё стек Wi-Fi чухается, возможно, именно этим обусловлены столь печальные результаты, приведённые выше, в сообщении #45. Так что, вполне вероятно, что был неправ, и виной не интерпретатор байт-кода - а стек Wi-Fi в ESP, которому надо овердохрена процессорного времени для нормальной работы. Кто закачает в NodeMCU и попробует? Мне лень чой-то
ну у тебя довольно таки узкоспециализированный байт-код. Он ясное дело будет быстрее джавы или луа с питоном
Да, ещё раз подчеркну, чтобы не получилось так, что сравнимаем сферических коней в вакууме и тёплое с круглым: речь идёт о том, что сказано с сообщении #45, т.е. простейший пример простейшего байткода, который интерпретатор просто обязан развернуть и выплюнуть, как здрасьте. И да - речь не идёт о Lua и JS - это другая лига.
Вот только вот выше - дополнил, во избежание разночтений Речь не о Lua, питоне или JS - как я понял, в сообщении #45 приведён пример простого байткода под некий интерпретатор, отличный от Lua, питона или JS, не? Речь именно об этом. Если там таки другая лига - сорри, был неправ, ибо в таком случае все проведённые мной сравнения - некорректны.
Мега 2560 Count passes: 1000 Elapsed time, ms: 11 Frequency: 90909.10 BYTE CODE TEST: Passes: 1000 Increment: 1 Count passes: 1000 Elapsed time, ms: 15 Frequency: 66666.67