Постановка проблемы
Чтобы с ходу погрузить вас в проблематику, приведу иллюстративный пример задачи.
Требуется создать несколько вариантов реализации интерфейса Множество. Общая идея представлена на UML-диаграмме.
Требуется соблюдать принцип LSP (3-й принцип S.O.L.I.D): клиентский код не должен зависеть от того, с каким вариантом реализации имеет дело. Другими словами, нужен классический полиморфизм, основанный на абстрактном интерфейсе.
Однако, данный пример я привел лишь для иллюстрации проблемы. Эта статья не про множества, а про использование двух различных подходов для реализации полиморфных объектов в 1С: в виде структур и в виде обработок. Я задался целью сравнить эти два подхода с точки зрения производительности.
Изложение подходов
Реализация полиморфного объекта в виде Структуры:
- Для каждого варианта реализации создается общий модуль, содержащий функцию-конструктор структуры и реализацию методов интерфейса.
- Свойства структуры служат для хранения свойств объекта.
- Также структура обязательно содержит свойство Модуль, содержащее ссылку на общий модуль реализации.
- Клиентский код вызывает методы в виде МойОбъект.Модуль.МойМетод(МойОбъект, <другие параметры...>).
- Клиентский код должен рассматривать структуру как черный ящик и не обращаться ни к каким свойствам, кроме свойства Модуль (исключение составляют публичные свойства, если они есть). Иначе будет нарушен принцип LSP. Ответственность за соблюдение этого правила возлагается на разработчика клиентского кода.
Реализация полиморфного объекта в виде Обработки:
- Для каждого варианта реализации создается Обработка, в которой модуль объекта содержит реализацию методов интерфейса.
- Приватные свойства хранятся как переменные модуля объекта.
- Клиентский код вызывает методы в виде МойОбъект.Метод(<параметры...>).
- Инкапсуляция обеспечивается средствами языка: переменные модуля объекта недоступны извне.
Оба подхода имеют свои плюсы и минусы. Обработка больше похожа на объект в понимании ООП. Она естественным образом обеспечивает защиту данных. Методы вызываются в объектном стиле.
Структура же - это всего лишь структура, подход из эпохи процедурного программирования. Она не обеспечивает никакой защиты данных объекта, и должна явным образом передаваться в каждый метод в виде параметра. Наличие смешанного состава публичных и приватных свойств сильно осложняет следование правилу черного ящика - разработчик клиентского кода всегда должен помнить, какие свойства разрешено использовать, а какие должны попадать в "слепое пятно". Это утомляет. С другой стороны, структура может применяться на клиенте, тогда как обработка - только на сервере.
Но это методологические различия, а какой из подходов будет работать быстрее?
Тестовый пример
Для задачи сравнительного анализа был реализован тестовый пример, изображенный на следующей диаграмме
Объект содержит умеренное количество свойств, из которых реально используется только одно, остальные служат для придания веса.
Конструктор объекта инициализирует все свойства начальными значениями.
Метод Тест() выполняет заданное количество итераций цикла, в котором свойство Значение увеличивается на 1, после чего возвращает Значение.
Все вышеописанное поведение было эквивалентным образом реализовано двумя способами.
Методика сравнения
Сравнение проводилось с варьированием трех факторов:
- Интенсивность создания экземпляров объекта.
- Интенсивность вызова методов объекта.
- Интенсивность обращения к свойствам внутри метода.
В ходе замера выполнялось заданное количество циклов создания объекта. Внутри каждого цикла создания выполнялось заданное количество циклов вызова метода. Внутри метода выполнялось заданное количество циклов обращения к свойству объекта.
В разных тестах использовалось разное соотношение количества итераций на каждом уровне цикла. При этом общее количество итераций внутреннего цикла всегда было одинаковым - 100000.
Результаты замеров
Для порядка укажу, что замеры выполнялись на машине с такими параметрами: Intel(R) Core(TM) i5-10500 CPU @ 3.10GHz, 16,0 ГБ ОЗУ, Win 10, 1С 8.3.27. Однако, абсолютные значения в данном случае не столь важны, важно соотношение величин.
Замер выполнялся без режима отладки. Под отладчиком все существенно медленнее и соотношения другие.
Результаты замеров приведены в таблице:
Расшифровка:
- "Циклов создания" - количество циклов создания экземпляра объекта (самый внешний цикл)
- "Циклов вызова" - количество циклов вызова метода (средний цикл)
- "Циклов внутренних" - количество циклов внутри метода (самый внутренний цикл)
- "Структура время" - время выполнения для объекта Структура (мс)
- "Обработка время" - время выполнения для объекта Обработка (мс)
- "Эффект обработки" - выигрыш времени при использовании обработки по сравнению с использованием структуры (%).
Анализ результатов
Экземпляр обработки создается дольше, чем экземпляр структуры. Это видно в тесте №1, где вся масса итераций приходится на создание объектов. В данном примере - в 3 раза дольше (-187%). Однако, при увеличении количества свойств объекта эта разница сокращается. Например, если добавить к нашему тестовому объекту еще 10 свойств, эффект обработки будет уже (-81%), причем исключительно из-за увеличения времени создания структуры - обработка создается за то же время.
А вот дальше наблюдается такая закономерность: по мере смещения интенсивности от внешнего цикла к внутреннему, обработка начинает выигрывать у структуры. Наивысший эффект обработки достигается, когда интенсивность по максимуму сосредоточена внутри метода, т.е. основная нагрузка приходится на код, выполняющий обращения к свойствам объекта. Здесь обработка вдвое превосходит структуру по скорости. Но и в большинстве промежуточных вариантов обработка либо выигрывает, либо выглядит по крайней мере не хуже структуры.
Секрет кроется в том, что операция обращения к свойству структуры - вовсе не дешевое удовольствие, она выполняется существенно медленнее, чем обращение к переменной модуля. Это наглядно видно по результатам замера производительности:
Здесь также можно оценить время, уходящее на создание и инициализацию свойств структуры: 4.74, против 14.35 на создание обработки. Для структуры время создания линейно зависит от количества свойств, тогда как для обработки - не зависит почти никак.
Вызов метода обработки выполняется несколько медленнее, чем вызов общего модуля через свойство структуры. Полагаю, это связано с выполнением контроля прав доступа при вызове методов обработки.
Интересная аномалия наблюдается в тестах №№2-3. Если в других случаях эффект обработки растет по мере смещения интенсивности от среднего цикла к внутреннему, то здесь почему-то наблюдается обратная динамика. Пока не нашел этому объяснения.
Выводы
Реализация объекта в виде структуры дает выигрыш только при следующих условиях: объекты с малым количеством свойств создаются с высокой частотой, при этом являясь по сути одноразовыми, т.е. для каждого экземпляра будут выполняться единичные вызовы методов, которые будут выполнять единичные обращения к свойствам.
Если предполагается интенсивное обращение к свойствам экземпляра, однозначно выгоднее реализовать объект как обработку.
Если объект содержит большое количество свойств, выигрыш структуры в скорости создания экземпляра сводится на нет и обработка начинает смотреться как минимум не хуже.
Разумеется, все это справедливо только при выполнении на сервере. На клиенте структура пока является единственным вариантом. Есть, правда, вариант использовать модуль формы, но в силу того, что создание экземпляра формы невозможно без обращения к серверу, данный вариант всерьез не рассматривается.
Из всего вышесказанного следует, что было бы очень неплохо, если бы в платформе 1С существовал "легковесный объект", как альтернатива обработке.
Обработка в 1С выполнена в стиле "бизнес-сущности". Она несет значительный груз наследственности от своего базового класса: 4 метода, 1 событие, реквизиты, формы, команды, макеты и контроль прав доступа на уровне ролей (подозреваю, что именно последнее вносит главный вклад в затраты на создание экземпляра). Для объекта внутренней архитектуры, который не предназначен для взаимодействия с пользователем, всё это - лишний балласт.
Легковесный объект не должен содержать ничего, кроме модуля объекта и модуля менеджера. Как общий модуль, только с возможностью создания экземпляров. А также он порождал бы новый тип, например Тип("Объект.МойОбъект"). И было бы неплохо, если бы использование было возможно не только на сервере, но и на клиенте (по аналогии с общим модулем: признаки Клиент, Сервер).
Вступайте в нашу телеграмм-группу Инфостарт