Что если разбивка на множество подпрограмм привносит дополнительную нагрузку на процессор ПЛК и снижает быстродействие?
Быстродействие важно и любые телодвижения в сторону абстракций, приводят к увеличению количества вызовов подпрограмм (функций, методов, других кусков кода). А ведь нам нужно успеть уложиться в миллисекунды базового времени (Base Time, в первую очередь), и только затем улучшить сопровождаемость (когда-нибудь потом, но лучше прямо сейчас).
Спагетти код ругают: все-таки трудно ориентироваться в бесконечной портянке неструктурированного кода непонятного назначения. Поэтому я хочу знать — имеет смысл экономить на абстракциях или я могу разбивать код на сколько угодно удобных мне частей?
Итак, исследуем влияние количества вызовов подпрограмм на загруженность процессора. Проверять TwinCAT 2 мне не очень-то и хотелось, но я проверил и не пожалел.
PROGRAM MAIN VAR i: UINT; END_VAR FOR i := 0 TO 65000 DO PRG2(); END_FOR (* ********************** *) PROGRAM PRG2 VAR c : UINT; END_VAR ; // c := c + 1; // c := c + 1;
Основная программа MAIN каждый цикл совершает ряд вызовов подпрограммы PRG2. Количество вызовов определяется количеством итераций цикла. Их максимальное число равно 65000 — как в примере выше. Так как нас в основном интересует нагрузка от ветвления программы, то подпрограмма PRG2 пустая. Точнее, она состоит из единственного оператора точка-с-запятой (;). Чтобы не было совсем банально, я разбавил подпрограмму парой строк со счетчиком (и это было правильным решением).
По вертикали (Y) — нагрузка процессора в процентах, по горизонтали (X) — количество вызовов подпрограммы за время одного цикла:
Ничего необычного: чем больше вызовов, тем больше нагрузка на процессор. При этом нужно учесть два факта: первый — подпрограмма PRG2 ничего не делает и нагрузка растет только за счет количества передачи управления из одной программы в другую; и второй — тест синтетический и в реальных ситуациях такого количеств вызовов за один цикл не бывает.
Теперь проверим TwinCAT 3:
...и он показывает более линейную зависимость.
Теперь об аномалиях второй версии TwinCAT. В подпрограмме PRG2 я убрал пустой оператор ";" и раскомментировал одну строку со счетчиком. Зафиксировал количество вызовов на числе 65000. Замерил нагрузку. Раскомментировал вторую строчку со счетчиком (теперь их две). Замерил нагрузку и впал в задумчивость.
Итак, внимание! С одной строкой счетчика, нагрузка составила 72%. С двумя строками счетчика — 43%. Больше кода — меньше нагрузка! Объяснить такое поведение можно только работой некоего оптимизирующего звена в компиляторе кода, но такая неявность, явно вводит в заблуждение. Я немедленно проверил то же самое в TwinCAT 3...
...и он по прежнему оказался линеен и предсказуем: 1 строка — 56%, две строки — 58%.
Выводы
- Вызовы подпрограмм добавляют нагрузку на процессор.
- Я решил, что не буду обращать на эту нагрузку внимание, так как ощутимой она становится только при каких-то очень больших числах вызовов за время одного цикла. Даже для CX8xxx серии.
- TwinCAT 3 стабильнее и предсказуемей, чем его вторая реализация.
- Upd. 29 мая 2019 года, TwinCAT 2 v2.11.2302 странности с "меньше кода - больше нагрузка" не наблюдаются. Возможно был баг с выравниванием переменных в памяти.
P.S.: посчитаем вызовы
А как вообще прикинуть — сколько вызовов происходит в проекте?
Возьмем проект ЧПУ системы, она точно будет большой. Посчитаем скольку всего происходит вызовов подпрограмм (функций и т. п.) во всем проекте. Конечно же, за один цикл будут происходить не все вызовы, но и я собираюсь считать приблизительно, и по максимуму. Заодно отбросим вызовы, происходящие внутри библиотечных функций... что-то там в итоге должно сойтись.
Для начала, открываем проект и экспортируем его в единый файл экспорта *.exp. Затем, открываем EXP-файл в текстовом редакторе (Notepad++) и автозаменой удаляем все вхождения следующих строк: (*, STRING(, IF (. Файл в итоге будет испорчен, но для нас важнее исключить посторонние элементы. Остается сделать глобальный поиск символа открывающей скобки (, который традиционно используется при вызове подпрограмм. Как вариант, поискать закрывающие скобки, а точнее конец вызова функции, что-то похожее на );
Количество вхождений искомых символов, будет приблизительно равно количеству вызовов функций и подпрограмм. Часть совпадений придется на математические формулы, но их не так много. В то же время часть вызовов мы все равно не обнаружим, так как они скрыты внутри библиотек и в других трудно доступных местах. Одно компенсирует другое, и в среднем получится более-менее правдоподобно.
На картинке выше подсвечены не все скобки, но они учтены и подсчитаны правильно.
В итоге, я нашел 560 вызовов с открывающими скобками и 433 с закрывающими. Это в два раза меньше 1000 (1-2% CPU, как в пустой программе) и совсем далеко от 65000 (56-58% CPU). Еще раз, это максимум, который можно вызвать, но который никогда не будет достигнут: программа разбита на шаги (машина состояний), в каждом из которых выполняется лишь небольшая часть всей технологической программы.
No comments
Post a Comment
Note: Only a member of this blog may post a comment.