При разработке сложных алгоритмов, таких как расчет производственных графиков, разузлование спецификаций или построение графов зависимостей, разработчики 1С часто сталкиваются с проблемами производительности. Типичный сценарий: запрос выполняется внутри цикла сотни или тысячи раз, так как каждая последующая итерация зависит от результатов выполнения предыдущей. В поисках решения возникают идеи перенести вычисления во внешнюю компоненту (на C++, C# или Rust). Разберем по шагам, даст ли это прирост скорости, в каких случаях внешняя компонента действительно эффективна и какие альтернативные решения существуют.
Рассмотрим причину, по которой написание внешней компоненты не поможет, если внутри цикла продолжает вызываться запрос к базе данных. Когда код 1С или код внешней компоненты отправляет запрос к СУБД (MS SQL Server, PostgreSQL), основное время расходуется на следующие операции:
Даже если сам цикл крутится во внешней компоненте, скомпилированной в машинный код, СУБД все равно работает отдельно на стороне сервера. Компонента не может ускорить обработку данных на стороне SQL-сервера, если на каждой итерации происходит обращение к базе данных.
Внешняя компонента покажет колоссальное ускорение (в десятки и сотни раз) только при кардинальном изменении архитектуры работы с данными. Проанализируем правильный подход:
std::unordered_map или графовых библиотек).Перед тем как писать внешнюю компоненту, выясним, можно ли решить задачу средствами самой платформы 1С. Очень часто причиной задержек является использование объекта Запрос для обработки данных, которые можно заранее выгрузить в оперативную память. Если заменить постоянные запросы к БД на работу с проиндексированными таблицами значений или соответствиями, производительность существенно вырастет.
Рассмотрим по шагам алгоритм работы с проиндексированной таблицей значений:
ТаблицаЗначений одним запросом.Индексы.Добавить().НайтиСтроки() или обращение по ключу.Посмотрим на пример кода подготовки и поиска данных в проиндексированной таблице значений:
// 1. Получаем все исходные данные одним запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спецификации.Ссылка КАК Владелец,
| Спецификации.Номенклатура КАК ИсходныйУзел,
| Спецификации.Материал КАК ВыходнойУзел,
| Спецификации.Количество КАК Количество
|ИЗ
| Справочник.Спецификации.Содержимое КАК Спецификации";
ТЗСпецификации = Запрос.Выполнить().Выгрузить();
// 2. Индексируем колонку для мгновенного поиска в памяти
ТЗСпецификации.Индексы.Добавить("ИсходныйУзел");
// 3. В цикле ищем строки без обращения к базе данных
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("ИсходныйУзел", ТекущийУзел);
НайденныеСтроки = ТЗСпецификации.НайтиСтроки(ПараметрыОтбора);
Для Каждого Стр Из НайденныеСтроки Цикл
// Обработка данных в оперативной памяти
КонецЦикла;
Для ещё более высокого распознавания и обработки данных можно использовать объект Соответствие, так как поиск по хэш-ключу в Соответствие работает быстрее, чем отбор в ТаблицаЗначений.
Если спецификация или граф имеет прогнозируемую глубину рекурсии, можно объединить итерации в один пакетный запрос, передавая промежуточные результаты через временные таблицы.
Рассмотрим пример пакетного запроса, который выполняет несколько уровней разузлования за один вызов СУБД:
// Формирование пакетного запроса для нескольких уровней вложенности
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спецификация.Ссылка КАК Родитель,
| Спецификация.Узел КАК Узел,
| Спецификация.Количество КАК КолУзел
|ПОМЕСТИТЬ ВТ_Уровень1
|ИЗ
| Справочник.Номенклатура.Спецификация КАК Спецификация
|ГДЕ
| Спецификация.Ссылка = &ИсхСсылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ1.Родитель КАК Родитель,
| ВЫБОР КОГДА Спец.Ссылка ЕСТЬ NULL ТОГДА ВТ1.Узел ИНАЧЕ Спец.Узел КОНЕЦ КАК Узел,
| ВЫБОР КОГДА Спец.Ссылка ЕСТЬ NULL ТОГДА ВТ1.КолУзел ИНАЧЕ ВТ1.КолУзел * Спец.Количество КОНЕЦ КАК КолУзел
|ПОМЕСТИТЬ ВТ_Уровень2
|ИЗ
| ВТ_Уровень1 КАК ВТ1
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура.Спецификация КАК Спец
| ПО ВТ1.Узел = Спец.Ссылка";
Запрос.УстановитьПараметр("ИсхСсылка", ИсходнаяСпецификация);
Результат = Запрос.ВыполнитьПакет();
Если задача требует разворачивания графов произвольной вложенности напрямую на стороне СУБД, отличной альтернативой является использование прямого доступа к базе данных и рекурсивных CTE-запросов.
Рекурсивный запрос CTE позволяет СУБД развернуть дерево любой глубины за одно обращение, выполняя оптимизацию на уровне ядра базы данных. Это полностью исключает необходимость крутить сотни запросов из кода 1С.
ТаблицаЗначений с индексами или в Соответствие.