В практике разработки на платформе 1С часто встречается ситуация, когда обработка таблицы значений, содержащей всего несколько тысяч строк, занимает часы. Типичный пример — необходимость упорядочить строки по сложной логике (например, чтобы внутри одного дня и одного договора «Приходы» всегда шли перед «Расходами»), используя вложенные циклы и метод Сдвинуть(). Рассмотрим подробно, почему возникают такие задержки и как перевести выполнение алгоритма из часов в минуты или даже секунды.
Разберем классический пример «плохого» кода, который часто становится причиной тормозов:
Для i = 1 По Таблица.Количество() - 1 Цикл
Для j = 1 По Таблица.Количество() - i Цикл
Если Таблица[j].ВалютаРасчетов = Таблица[j - 1].ВалютаРасчетов
И Таблица[j].НомерДоговора = Таблица[j - 1].НомерДоговора Тогда
ВидОперацииТекущий = Строка(Таблица[j].ВидОперации.ПриходРасход);
ВидОперацииПредыдущий = Строка(Таблица[j - 1].ВидОперации.ПриходРасход);
Если ВидОперацииТекущий = "Приход" И ВидОперацииПредыдущий = "Расход" Тогда
Таблица.Сдвинуть(j, -1);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Проанализируем ситуацию: данный алгоритм имеет вычислительную сложность O(n²). При 10 000 строк количество итераций составит около 50 миллионов. Но главная проблема даже не в циклах, а в методе Сдвинуть(). Этот метод не просто меняет местами две строки, он фактически перестраивает внутренний массив памяти для всей таблицы. Если условие выполняется часто, платформа тратит колоссальное время на физическое перемещение данных в памяти. Дополнительно ситуацию усугубляют «обращения через точку» к реквизитам ссылочных типов внутри цикла, что может приводить к избыточным вызовам СУБД.
Первым шагом к ускорению будет минимизация работы внутри цикла. Выясним причину тормозов: преобразование Строка(Ссылка.Реквизит) — крайне дорогая операция. Если мы заранее подготовим данные, мы уже получим прирост скорости в разы.
Выполним следующие действия:
Посмотрим на пример оптимизированного кода:
// Предварительная подготовка данных
Таблица.Колонки.Добавить("ПриходРасходБулево", Новый ОписаниеТипов("Булево"));
Для Каждого СтрокаТЗ Из Таблица Цикл
СтрокаТЗ.ПриходРасходБулево = (СтрокаТЗ.ВидОперации.ПриходРасход = Перечисления.ПриходРасход.Приход);
КонецЦикла;
// Основной цикл теперь работает быстрее
РазмерТаблицы = Таблица.Количество();
Для i = 1 По РазмерТаблицы - 1 Цикл
Для j = 1 По РазмерТаблицы - i Цикл
Если Таблица[j].КлючАналитики = Таблица[j - 1].КлючАналитики Тогда
Если Таблица[j].ПриходРасходБулево И НЕ Таблица[j - 1].ПриходРасходБулево Тогда
Таблица.Сдвинуть(j, -1);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Самый эффективный способ оптимизации — полностью отказаться от ручного перемещения строк в пользу платформенного метода Сортировать(). Рассмотрим подробнее, как это сделать, если стандартная сортировка «не справляется».
Проблема автора заключалась в том, что простая сортировка по полям (Валюта, Договор, Дата) ломает какой-то другой, исходный порядок строк. Чтобы этого избежать, нам нужно использовать стабильную сортировку. В 1С это достигается введением колонки с исходным индексом.
Разберем по шагам алгоритм «умной» сортировки:
ИсходныйПорядок и заполним ее значениями от 1 до N.ПриоритетОперации (например, 1 для Прихода, 2 для Расхода).
Таблица.Колонки.Добавить("ИсходныйПорядок", Новый ОписаниеТипов("Число"));
Таблица.Колонки.Добавить("Приоритет", Новый ОписаниеТипов("Число"));
Для Инд = 0 По Таблица.Количество() - 1 Цикл
Стр = Таблица[Инд];
Стр.ИсходныйПорядок = Инд;
Стр.Приоритет = ?(Стр.ВидОперации.ПриходРасход = Перечисления.ПриходРасход.Приход, 1, 2);
КонецЦикла;
// Сортируем один раз средствами платформы
Таблица.Сортировать("ВалютаРасчетов, НомерДоговора, МестоУчета, ДатаОперации, Приоритет, ИсходныйПорядок");
Этот метод работает в сотни раз быстрее, так как функция Сортировать() реализована на низком уровне и оптимизирована для работы с большими массивами данных.
Если таблица значений очень большая (десятки или сотни тысяч строк), перекладывание задачи на плечи СУБД (SQL) даст наилучший результат. Проанализируем ситуацию: мы можем передать таблицу в запрос, там её упорядочить и выгрузить обратно.
Рассмотрим пример реализации через запрос:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &Таблица КАК Таблица;
|ВЫБРАТЬ * ИЗ ВТ_Данные КАК Данные
|УПОРЯДОЧИТЬ ПО
| Данные.ВалютаРасчетов,
| Данные.НомерДоговора,
| Данные.ДатаОперации,
| ВЫБОР КОГДА Данные.ВидОперации.ПриходРасход = ЗНАЧЕНИЕ(Перечисление.ПриходРасход.Приход) ТОГДА 1 ИНАЧЕ 2 КОНЕЦ";
Запрос.УстановитьПараметр("Таблица", Таблица);
Таблица = Запрос.Выполнить().Выгрузить();
Важный момент: При выгрузке таблицы в запрос важно помнить о типизации колонок. Если колонки таблицы не имеют четко заданного типа, запрос может отработать некорректно или медленно.
Выясним причину, почему обращения через точку (Таблица[j].ВидОперации.ПриходРасход) могут замедлять код. В 1С существует объектный кэш. При первом обращении к реквизиту ссылки платформа считывает весь объект целиком из базы данных и сохраняет его в памяти. Если в вашей таблице много уникальных ссылок, кэш будет постоянно очищаться и заполняться заново, создавая огромную нагрузку на сеть и СУБД.
Рекомендация: всегда используйте метод Запрос для получения данных из связанных справочников или документов перед началом тяжелых циклов. Это гарантирует, что вы получите только нужные поля за один вызов сервера.
Подводя итог: оптимизация циклов в 1С почти всегда сводится к замене ручного перебора на использование встроенных механизмов (сортировка, запросы) и предварительной подготовке данных в типизированных колонках. Помните, что использование Сдвинуть() внутри вложенного цикла — это критическая ошибка проектирования для высоконагруженных систем.