При расчете себестоимости и распределении затрат в конфигурациях на платформе 1С (в частности, в алгоритмах расчета себестоимости УПП) часто возникает задача идентификации уникальных аналитических комбинаций — состояний источников и приемников. Нередко базовый код решает эту задачу тройным вложенным циклом, что приводит к критическому падению производительности на больших объемах данных. Разберем, почему исходный алгоритм работает медленно, и рассмотрим эффективные варианты его оптимизации.
Проанализируем исходный код сопоставления. Для каждой строки таблицы Таб выполняется поиск в коллекции СоотвПараметровСостояний:
Для Каждого Строка Из Таб Цикл
// Состояния-источники
НайденоСостояние = Ложь;
Для Каждого ЭлементСостояние Из СоотвПараметровСостояний Цикл
НайденоСостояние = Истина;
Для Каждого Элемент Из СтруктураСостояния Цикл
Если Элемент.Ключ = "ВременнаяРазница" или Элемент.Ключ = "ПостояннаяРазница" тогда
Продолжить;
КонецЕсли;
Если НЕ (ЭлементСостояние.Значение[Элемент.Ключ] = Строка[Элемент.Ключ]) Тогда
НайденоСостояние = Ложь;
Прервать;
КонецЕсли;
КонецЦикла;
Если НайденоСостояние Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Если НайденоСостояние Тогда
ИндексСостояния = ЭлементСостояние.Ключ;
Иначе
СтрСост = Новый Структура;
Для Каждого Элемент Из СтруктураСостояния Цикл
Если Элемент.Ключ = "ВременнаяРазница" или Элемент.Ключ = "ПостояннаяРазница" тогда
Продолжить;
КонецЕсли;
СтрСост.Вставить(Элемент.Ключ, Строка[Элемент.Ключ]);
КонецЦикла;
ИндексСостояния = СоотвПараметровСостояний.Количество();
СоотвПараметровСостояний.Вставить(ИндексСостояния, СтрСост);
КонецЕсли;
Строка.Источник = ИндексСостояния;
// Аналогичный блок повторяется для приемника
КонецЦикла;
Выясним причину низкой скорости работы данной конструкции:
Соответствие: объект коллекции перебирается полным циклом Для Каждого, вместо прямого извлечения элемента по ключу за время O(1).Строка[Элемент.Ключ] заставляет платформу на каждой итерации искать идентификатор свойства по строковому имени в хэш-таблице метаданных объекта.ВременнаяРазница, ПостояннаяРазница) выполняется многократно внутри самого глубокого вложенного цикла.Посмотрим на объект Структура. Он является ссылочным типом данных, поэтому сравнение двух структур с одинаковыми свойствами (Новый Структура("А", 1) = Новый Структура("А", 1)) вернет Ложь. По этой причине структуру нельзя напрямую сделать ключом в Соответствии.
Однако мы можем сформировать составной строковый ключ или сериализовать значения ключевых колонок, чтобы использовать мгновенный поиск по ключу за время O(1). Предварительно исключим неиспользуемые колонки из перебора.
// Подготовим массив значимых колонок заранее, исключив лишние проверки из цикла
МассивКолонок = Новый Массив;
Для Каждого Элемент Из СтруктураСостояния Цикл
Если Элемент.Ключ <> "ВременнаяРазница" И Элемент.Ключ <> "ПостояннаяРазница" Тогда
МассивКолонок.Добавить(Элемент.Ключ);
КонецЕсли;
КонецЦикла;
// Вспомогательное соответствие: Ключ - уникальная строка параметров, Значение - Индекс
КартаИндексов = Новый Соответствие;
Разделитель = Символы.ВК;
Для Каждого Строка Из Таб Цикл
// --- Обработка источника ---
КлючИсточника = "";
Для Каждого ИмяКолонки Из МассивКолонок Цикл
КлючИсточника = КлючИсточника + Разделитель + Строка(ЗначениеВСтрокуВнутр(Строка[ИмяКолонки]));
КонецЦикла;
ИндексИсточника = КартаИндексов[КлючИсточника];
Если ИндексИсточника = Неопределено Тогда
ИндексИсточника = СоотвПараметровСостояний.Количество();
СтрСост = Новый Структура;
Для Каждого ИмяКолонки Из МассивКолонок Цикл
СтрСост.Вставить(ИмяКолонки, Строка[ИмяКолонки]);
КонецЦикла;
СоотвПараметровСостояний.Вставить(ИндексИсточника, СтрСост);
КартаИндексов.Вставить(КлючИсточника, ИндексИсточника);
КонецЕсли;
Строка.Источник = ИндексИсточника;
// --- Обработка приемника ---
КлючПриемника = "";
Для Каждого ИмяКолонки Из МассивКолонок Цикл
КлючПриемника = КлючПриемника + Разделитель + Строка(ЗначениеВСтрокуВнутр(Строка[ИмяКолонки + ПрефиксПараметровНовогоСостояния]));
КонецЦикла;
ИндексПриемника = КартаИндексов[КлючПриемника];
Если ИндексПриемника = Неопределено Тогда
ИндексПриемника = СоотвПараметровСостояний.Количество();
СтрСост = Новый Структура;
Для Каждого ИмяКолонки Из МассивКолонок Цикл
СтрСост.Вставить(ИмяКолонки, Строка[ИмяКолонки + ПрефиксПараметровНовогоСостояния]);
КонецЦикла;
СоотвПараметровСостояний.Вставить(ИндексПриемника, СтрСост);
КартаИндексов.Вставить(КлючПриемника, ИндексПриемника);
КонецЕсли;
Строка.Приемник = ИндексПриемника;
КонецЦикла;
Такой подход сокращает сложность с O(N × M × K) до линейной O(N × K), обеспечивая многократный прирост производительности.
Рассмотрим альтернативное решение, наиболее естественное для архитектуры 1С: перенос всей логики в СУБД. Реляционная база данных выполняет группировки и соединения несравнимо быстрее интерпретатора платформы за счет алгоритмов Hash Match и Merge Join.
Разберем по шагам логику решения через запрос:
Таб во временную таблицу в менеджере временных таблиц.ОБЪЕДИНИТЬ все уникальные комбинации полей источника и полей приемника.АВТОНОМЕРЗАПИСИ() формируем суррогатный числовой идентификатор для каждого состояния.Этот способ освобождает память виртуальной машины 1С и исключает ручной перебор больших коллекций.
Если данные уже находятся в таблицах значений и вызов СУБД нежелателен, эффективным вариантом является программное слияние двух предварительно отсортированных массивов данных.
Посмотрим на пример сопоставления двух таблиц по общему ключу поиска:
Таблица1.Сортировать("КлючПоиска Возр");
Таблица2.Сортировать("КлючПоиска Возр");
Счетчик1 = 0;
Счетчик2 = 0;
Всего1 = Таблица1.Количество();
Всего2 = Таблица2.Количество();
Пока Счетчик1 < Всего1 И Счетчик2 < Всего2 Цикл
СтрокаТаблицы1 = Таблица1[Счетчик1];
СтрокаТаблицы2 = Таблица2[Счетчик2];
Если СтрокаТаблицы1.КлючПоиска > СтрокаТаблицы2.КлючПоиска Тогда
Счетчик2 = Счетчик2 + 1;
ИначеЕсли СтрокаТаблицы1.КлючПоиска < СтрокаТаблицы2.КлючПоиска Тогда
Счетчик1 = Счетчик1 + 1;
Иначе
// Ключи совпали - переносим необходимые реквизиты
СтрокаТаблицы1.ЗначениеИзТаблицы2 = СтрокаТаблицы2.Значение;
Счетчик1 = Счетчик1 + 1;
КонецЕсли;
КонецЦикла;
Сложность алгоритма слияния составляет O(N log N) на этапе сортировки и O(N + M) при непосредственном обходе указателями, что на порядки быстрее вложенных циклов.
НайтиСтроки() по индексу, прямое получение по ключу из Соответствия или алгоритм слияния.Если ... Тогда миллионы раз.ТаблицуЗначений с настроенными индексами приводит к постоянной их перестройке, что существенно снижает скорость наполнения таблицы.