При интеграции нестандартного торгового, складского или промышленного оборудования разработчики часто сталкиваются с задачей: протоколы обмена постоянно меняются и пополняются, а обновлять конфигурацию ради небольших правок парсера неудобно. Возникает идея хранить алгоритмы обработки входящих данных прямо в справочнике или текстовых файлах и вызывать их с помощью функции Выполнить(). Проанализируем, как устроен этот механизм, насколько он уступает встроенному коду по скорости и какие альтернативы позволяют избежать постоянных обновлений конфигурации.
Главное отличие кода, написанного в общих модулях или модулях форм, от динамических строк в Выполнить() заключается в предварительной компиляции. Штатные модули конфигурации компилируются платформой заранее при старте сеанса или первом обращении к контексту. Скомпилированный байт-код хранится в оперативной памяти и исполняется виртуальной машиной 1С без повторной подготовки.
С динамическим кодом ситуация принципиально иная. Платформа 1С:Предприятие не кэширует байт-код для произвольных строк, переданных в метод Выполнить(). Каждый раз, когда поток выполнения доходит до этой команды, платформа последовательно выполняет:
Если код вызывается разово (например, при проведении одного документа вручную или по нажатию сервисной кнопки), время компиляции составляет доли миллисекунды, и пользователь визуально не заметит никакой задержки. Однако при потоковой обработке данных от оборудования, когда порции байтов приходят несколько раз в секунду, постоянная перекомпиляция создает существенную нагрузку на процессор и приводит к заметным задержкам интерфейса.
При попытке замерить скорость через конфигуратор можно столкнуться с парадоксальными результатами, когда динамическое выполнение Вычислить() или Выполнить() в замерах кажется сопоставимым по скорости с обычными вызовами. Разберем, почему так происходит.
Включение режима отладки вносит серьезные искажения в работу виртуальной машины 1С. Штатные механизмы вызова методов обрастают отладочными перехватами и служебным трафиком между клиентом/сервером и конфигуратором. Чтобы получить объективную картину быстродействия, все тесты производительности необходимо выполнять исключительно в рабочем режиме без подключенной отладки, фиксируя время с помощью функции ТекущаяУниверсальнаяДатаВМиллисекундах() на больших сериях итераций (например, от 10 000 до 100 000 вызовов подряд).
В качестве компромисса можно попробовать минимизировать операции внутри динамического фрагмента, заранее подгружая статические справочные данные в контекст вызывающей процедуры. Посмотрим на пример изоляции предварительных настроек:
Перем МоеПространствоИмен Экспорт;
Функция ВыполнитьПарсинг(ТекстАлгоритма, ВходящийПакет)
Если МоеПространствоИмен = Неопределено Тогда
// Загружаем тяжелые таблицы коэффициентов и сопоставлений один раз
МоеПространствоИмен = ИнициализироватьТаблицыСоответствия();
КонецЕсли;
// В динамический код передается уже подготовленный контекст
Выполнить(ТекстАлгоритма);
КонецФункции
Такой подход избавляет от повторного чтения настроек из базы данных, но не решает фундаментальную проблему: сам текст ТекстАлгоритма все равно будет компилироваться заново при каждом входящем событии.
Помимо просадки производительности, использование Выполнить() несет в себе архитектурные и платформенные ограничения:
БезопасныйРежим (актуально для механизмов БСП, разделенных баз и внешних подключаемых модулей), работа с Выполнить() жестко блокируется платформой.ИнформацияОбОшибке() не покажет реальную строку в исходном файле, возвращая стек вида {(1)}, что крайне затрудняет расследование сбоев на работающей системе.Выполнить() разделяет контекст с процедурой, из которой вызван. Случайно созданная внутри строка переменная с именем, совпадающим с переменной вызывающей процедуры, молча перезапишет ее значение.Если задача состоит в том, чтобы оперативно обновлять алгоритмы взаимодействия с оборудованием без необходимости блокировать пользователей монопольными обновлениями конфигурации, рассмотрим проверенные на практике варианты:
Обработку с модулем парсера можно хранить в двоичных данных справочника базы. При старте сеанса или открытии формы обработки мы загружаем внешнюю обработку один раз через ВнешниеОбработки.Создать(). В этот момент модуль обработки компилируется единожды. Далее при поступлении каждого события оборудования вызываются экспортные функции полученного объекта напрямую, со скоростью нативного кода платформы.
Вынесение протоколов обмена в расширение конфигурации позволяет применять доработки динамически, не снимая конфигурацию поставщика с поддержки и не прерывая работу пользователей структурными изменениями метаданных.
Разбор нестандартных протоколов часто требует побитовых сдвигов, расчета контрольных сумм (CRC16, CRC32) и работы с сырыми байтовыми массивами. Перенос этого узкого места в компактную внешнюю библиотеку на C++ или Rust полностью устраняет вычислительную нагрузку с платформы 1С и обеспечивает мгновенную реакцию интерфейса на поступающие пакеты.