Как оптимизировать медленный цикл при обработке таблицы значений в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С часто встречается ситуация, когда обработка таблицы значений, содержащей всего несколько тысяч строк, занимает часы. Типичный пример — необходимость упорядочить строки по сложной логике (например, чтобы внутри одного дня и одного договора «Приходы» всегда шли перед «Расходами»), используя вложенные циклы и метод Сдвинуть(). Рассмотрим подробно, почему возникают такие задержки и как перевести выполнение алгоритма из часов в минуты или даже секунды.

Проблема: Почему вложенный цикл и метод Сдвинуть() работают медленно

Разберем классический пример «плохого» кода, который часто становится причиной тормозов:


Для i = 1 По Таблица.Количество() - 1 Цикл
    Для j = 1 По Таблица.Количество() - i Цикл
        Если Таблица[j].ВалютаРасчетов = Таблица[j - 1].ВалютаРасчетов 
            И Таблица[j].НомерДоговора = Таблица[j - 1].НомерДоговора Тогда
            
            ВидОперацииТекущий = Строка(Таблица[j].ВидОперации.ПриходРасход);
            ВидОперацииПредыдущий = Строка(Таблица[j - 1].ВидОперации.ПриходРасход);
            
            Если ВидОперацииТекущий = "Приход" И ВидОперацииПредыдущий = "Расход" Тогда
                Таблица.Сдвинуть(j, -1);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Проанализируем ситуацию: данный алгоритм имеет вычислительную сложность O(n²). При 10 000 строк количество итераций составит около 50 миллионов. Но главная проблема даже не в циклах, а в методе Сдвинуть(). Этот метод не просто меняет местами две строки, он фактически перестраивает внутренний массив памяти для всей таблицы. Если условие выполняется часто, платформа тратит колоссальное время на физическое перемещение данных в памяти. Дополнительно ситуацию усугубляют «обращения через точку» к реквизитам ссылочных типов внутри цикла, что может приводить к избыточным вызовам СУБД.

Решение 1: Оптимизация обращений к данным и исключение лишних преобразований

Первым шагом к ускорению будет минимизация работы внутри цикла. Выясним причину тормозов: преобразование Строка(Ссылка.Реквизит) — крайне дорогая операция. Если мы заранее подготовим данные, мы уже получим прирост скорости в разы.

Выполним следующие действия:

  1. Добавим в таблицу значений вспомогательные колонки для всех проверяемых условий.
  2. Заполним эти колонки в одном единственном проходе по таблице.
  3. В основном цикле будем сравнивать уже готовые значения (примитивные типы: Число, Булево), а не обращаться к реквизитам через точку.

Посмотрим на пример оптимизированного кода:


// Предварительная подготовка данных
Таблица.Колонки.Добавить("ПриходРасходБулево", Новый ОписаниеТипов("Булево"));
Для Каждого СтрокаТЗ Из Таблица Цикл
    СтрокаТЗ.ПриходРасходБулево = (СтрокаТЗ.ВидОперации.ПриходРасход = Перечисления.ПриходРасход.Приход);
КонецЦикла;

// Основной цикл теперь работает быстрее
РазмерТаблицы = Таблица.Количество();
Для i = 1 По РазмерТаблицы - 1 Цикл
    Для j = 1 По РазмерТаблицы - i Цикл
        Если Таблица[j].КлючАналитики = Таблица[j - 1].КлючАналитики Тогда
            Если Таблица[j].ПриходРасходБулево И НЕ Таблица[j - 1].ПриходРасходБулево Тогда
                Таблица.Сдвинуть(j, -1);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Решение 2: Замена вложенного цикла на стандартный метод Сортировать()

Самый эффективный способ оптимизации — полностью отказаться от ручного перемещения строк в пользу платформенного метода Сортировать(). Рассмотрим подробнее, как это сделать, если стандартная сортировка «не справляется».

Проблема автора заключалась в том, что простая сортировка по полям (Валюта, Договор, Дата) ломает какой-то другой, исходный порядок строк. Чтобы этого избежать, нам нужно использовать стабильную сортировку. В 1С это достигается введением колонки с исходным индексом.

Разберем по шагам алгоритм «умной» сортировки:

  1. Добавим колонку ИсходныйПорядок и заполним ее значениями от 1 до N.
  2. Добавим колонку ПриоритетОперации (например, 1 для Прихода, 2 для Расхода).
  3. Выполним сортировку по всем ключевым полям аналитики, затем по приоритету, и в конце — по исходному порядку.

Таблица.Колонки.Добавить("ИсходныйПорядок", Новый ОписаниеТипов("Число"));
Таблица.Колонки.Добавить("Приоритет", Новый ОписаниеТипов("Число"));

Для Инд = 0 По Таблица.Количество() - 1 Цикл
    Стр = Таблица[Инд];
    Стр.ИсходныйПорядок = Инд;
    Стр.Приоритет = ?(Стр.ВидОперации.ПриходРасход = Перечисления.ПриходРасход.Приход, 1, 2);
КонецЦикла;

// Сортируем один раз средствами платформы
Таблица.Сортировать("ВалютаРасчетов, НомерДоговора, МестоУчета, ДатаОперации, Приоритет, ИсходныйПорядок");

Этот метод работает в сотни раз быстрее, так как функция Сортировать() реализована на низком уровне и оптимизирована для работы с большими массивами данных.

Решение 3: Использование запросов для временных таблиц

Если таблица значений очень большая (десятки или сотни тысяч строк), перекладывание задачи на плечи СУБД (SQL) даст наилучший результат. Проанализируем ситуацию: мы можем передать таблицу в запрос, там её упорядочить и выгрузить обратно.

Рассмотрим пример реализации через запрос:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &Таблица КАК Таблица;
    |ВЫБРАТЬ * ИЗ ВТ_Данные КАК Данные
    |УПОРЯДОЧИТЬ ПО 
    |   Данные.ВалютаРасчетов, 
    |   Данные.НомерДоговора, 
    |   Данные.ДатаОперации, 
    |   ВЫБОР КОГДА Данные.ВидОперации.ПриходРасход = ЗНАЧЕНИЕ(Перечисление.ПриходРасход.Приход) ТОГДА 1 ИНАЧЕ 2 КОНЕЦ";

Запрос.УстановитьПараметр("Таблица", Таблица);
Таблица = Запрос.Выполнить().Выгрузить();

Важный момент: При выгрузке таблицы в запрос важно помнить о типизации колонок. Если колонки таблицы не имеют четко заданного типа, запрос может отработать некорректно или медленно.

Советы по работе с кэшем объектов

Выясним причину, почему обращения через точку (Таблица[j].ВидОперации.ПриходРасход) могут замедлять код. В 1С существует объектный кэш. При первом обращении к реквизиту ссылки платформа считывает весь объект целиком из базы данных и сохраняет его в памяти. Если в вашей таблице много уникальных ссылок, кэш будет постоянно очищаться и заполняться заново, создавая огромную нагрузку на сеть и СУБД.

Рекомендация: всегда используйте метод Запрос для получения данных из связанных справочников или документов перед началом тяжелых циклов. Это гарантирует, что вы получите только нужные поля за один вызов сервера.

Подводя итог: оптимизация циклов в 1С почти всегда сводится к замене ручного перебора на использование встроенных механизмов (сортировка, запросы) и предварительной подготовке данных в типизированных колонках. Помните, что использование Сдвинуть() внутри вложенного цикла — это критическая ошибка проектирования для высоконагруженных систем.

← На главную