Ключевые слова
Во времена TwinCAT 2 динамической памяти не было. Если что-то изменяло свой размер, то под него выделялся буфер максимального размера, рядом ставился счетчик длины или количества элементов, или как-то еще, но в итоге как-то отмечали окончание цепочки символов. Например, нулевой символ \0 в конце строки. Как вариант, можно было создать массив структур, и по мере необходимости добавлять в него указатели, и таким образом нафантазировать себе списки, очереди, стеки, деревья, а также другие типы данных переменной длины.
Теперь же появились новые ключевые слова __NEW и __DELETE. С помощью них можно создавать переменные во время выполнения программы. Место для переменных выделяется в большой, неразмеченной куче памяти, а по окончании работы место возвращается. Можно создавать и снова удалять, и так далее без конца.
PROGRAM NewDeleteRepeatAgain VAR ptr : POINTER TO ARRAY[0..4095] OF BYTE; END_VAR [...] ptr := __NEW(BYTE, 4096); __DELETE(ptr);
Оба ключевых слова встроены в язык программирования и являются расширением стандарта IEC 61131-3. Еще раз, это не функциональные блоки, это встроенные средства языка программирования. Обе команды работают с указателями типа POINTER TO ТипДанных. С "голыми" адресами типа DINT и PVOID команды работать не умеют и тем самым достигается какая-никакая безопасность при работе с арифметикой указателей.
Кроме динамических структур, можно на лету создавать экземпляры функциональных блоков. В результате получится указатель на функциональный блок, который затем используется как обычная функция. Чтобы функциональный блок или структуру стали динамически создаваемыми, их необходимо пометить специальным атрибутом {attribute 'enable_dynamic_creation'}.
Память роутера
Память занимается у роутера. Ее объем — это обычная настройка проекта:
Роутеру же память и возвращается — если, конечно, разработчик не забудет вернуть. Если забудет, то память закончится и __NEW начнет возвращать нули вместо указателей. Я пробовал выделять блоки длиной в 4 мегабайта и тут же удалять их, и так каждый ПЛК цикл. Ни каких проблем с этим не возникло. Время работы практически не отличается от работы с фиксированными данными. Выделение и освобождение памяти происходит максимально быстро и практически не заметно. Разве что, чуть-чуть.
В эксперименте принимал участие контроллер С6015 с атомом E3845 на борту (Windows 10 IoT) и CX9020 c ARMv7 и WinCE Emb. Результаты по таймингам идентичные. Более слабый процессор CX9020 позволяет обрабатывать меньшее количество данных за цикл, поэтому размер блоков пришлось сократить до 100 килобайт, но задержки по прежнему аналогичны старшему брату. От разрядности и операционной системы тоже ничего не зависит. Вот за эту универсальность все и любят третью версию TwinCAT.
__DELETE возвращает теперь уже свободное место в кучу. С нулевыми указателями команда не работает: она просто игнорируя их. Указатели в TC3 типизированные, поэтому кроме как трюками с ADS их не испортить. Если не забывать освобождать занятую память, то все должно быть нормально. На картинке ниже, я ежесекундно занимаю по 4 мегабайта из памяти роутера, а затем... не освобождаю ее: когда-нибудь память у роутера закончится.
Самоконтроль
Библиотека Tc2_Utilities предлагает функцию FB_GetRouterStatusInfo, отвечающую за контроль состояния роутера. Поле maxMemAvail выходной структуры ST_TcRouterStatusInfo хранит текущий объем свободной памяти роутера в байтах. Эта же память является "кучей" динамической памяти.
Аналогичную информацию разработчик может получить и без программирования, просто ткнув правой кнопкой мыши в иконку TwinCAT, а затем в Router → Info.
К сожалению, в данный момент (для TwinCAT 3.1.4022.29) единственный способ очистить кучу не отключая контроллер — это перезапустить системный сервис TwinCAT. Перезапуск программы ПЛК, перезаливка конфигурации, холодный или полный сброс переменных, переключение в режим конфигурации, любые другие телодвижения — не скроют следы утечек памяти у неаккуратного разработчика. Бдите!
Производительность
Память занимается и освобождается быстро. Что еще? Копирование динамически созданных блоков памяти происходит с той же скоростью, что и у статических (статически выделенных на этапе сборки проекта переменных). Проверка на массивах большого размера различия не показала.
Как там дела с обработкой элементов массива или динамического списка (vector, List, итп типов данных)? Здесь есть небольшое различие, которые не так чтобы сильно заметны и возможно это просто накладные расходы на работу с указателями, но все-таки чуть медленнее. По вертикали отложены миллисекунды от 4 до 10 мс:
Каждый цикл программа пробегает по всем элементам массива: 8 миллисекунд длится забег по статическому массиву и 9 миллисекунд по динамическому. Резкие провалы вниз — это выделение динамической памяти под массив (которые, как нам теперь известно, почти не занимает времени цикла).
Итого, не сильно большая разница чтобы осторожничать, но проверить стоило.
PS: Копаем глубже
При создании нового экземпляра происходит инициализация объекта и его полей. Если в объекте есть вложенные объекты (структура в структуре), они также будут инициализированы. С классами немного сложнее, так как в них есть конструкторы и деструкторы, но это уже отдельная тема.
No comments
Post a Comment
Note: Only a member of this blog may post a comment.