При оптимизации кода в 1С часто возникает вопрос: какой из циклов — Для каждого, Для ... По или Пока — является самым быстрым? Ответ не так однозначен, как кажется на первый взгляд, и сильно зависит от задачи, которую вы решаете. Особенно большие различия в производительности проявляются при работе с результатами запросов.
Рассмотрим, какой цикл и в какой ситуации лучше использовать, чтобы добиться максимальной производительности и эффективного использования памяти.
Если мы говорим о переборе коллекций, которые уже полностью загружены в оперативную память (например, Массив, СписокЗначений или ТаблицаЗначений), то существует общее правило производительности.
От самого быстрого к самому медленному циклы располагаются так:
Для Каждого ... Из ... ЦиклДля ... По ... ЦиклПока ... ЦиклПочему такая разница? Причина кроется во внутренней реализации на уровне платформы 1С.
Цикл Для Каждого работает на более низком уровне. Он напрямую использует внутренний механизм обхода коллекции (итератор), что делает его максимально эффективным. Платформа просто последовательно получает следующий элемент, не выполняя лишних операций.
В то же время, цикл Для ... По ... Цикл, использующий индекс, на каждой итерации выполняет больше действий на уровне встроенного языка: получает элемент коллекции по его индексу (МойМассив[i]), что является дополнительной операцией с накладными расходами.
Посмотрим на пример:
// Более быстрый и предпочтительный вариант для перебора массива
Для Каждого Элемент Из МояКоллекция Цикл
// Действия с элементом
КонецЦикла;
// Более медленный вариант с явным получением по индексу
Для i = 0 По МояКоллекция.ВГраница() Цикл
Элемент = МояКоллекция[i];
// Действия с элементом
КонецЦикла;
Вывод: для обхода уже готовых коллекций в памяти всегда отдавайте предпочтение циклу Для Каждого. Он не только быстрее, но и делает код более читаемым.
Ситуация кардинально меняется, когда речь заходит об обработке результатов запроса к базе данных. Здесь сравнение скорости самих циклов отходит на второй план, а на первый выходит способ получения данных. Проанализируем два основных подхода.
Этот подход кажется логичным: сначала получаем все данные в таблицу значений, а потом обрабатываем ее самым быстрым циклом Для Каждого. Разберем по шагам:
Выгрузить() создает в оперативной памяти полную копию результата запроса в виде объекта ТаблицаЗначений. Это самый затратный этап. Если результат запроса содержит сотни тысяч или миллионы строк, это может занять много времени и потребовать значительный объем оперативной памяти.Для Каждого перебирает уже готовую таблицу в памяти.Посмотрим на пример кода:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Номенклатура.Ссылка ИЗ Справочник.Номенклатура КАК Номенклатура";
// Шаг 1: Полная выгрузка результата в память. Может быть очень медленно и ресурсоемко.
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
// Шаг 2: Быстрый цикл по уже готовой таблице в памяти.
Для Каждого Строка Из ТаблицаРезультата Цикл
// Обработка строки
Сообщить(Строка.Ссылка);
КонецЦикла;
Несмотря на то, что сам цикл работает быстро, общая производительность связки Выгрузить() + Для Каждого часто оказывается низкой из-за "тяжелой" операции выгрузки данных.
Этот подход является более профессиональным и производительным для обработки результатов запросов. Разберем его:
ВыборкаИзРезультатаЗапроса.Пока Выборка.Следующий() итерационно обходит результат. На каждом шаге платформа подгружает данные порциями, не создавая полную копию в памяти.Посмотрим на пример:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Номенклатура.Ссылка ИЗ Справочник.Номенклатура КАК Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
// Создается объект "Выборка", данные из базы будут подгружаться по мере необходимости
Выборка = РезультатЗапроса.Выбрать();
// Сам цикл "Пока" медленнее, чем "Для каждого",
// но за счет отсутствия этапа полной выгрузки общий выигрыш в скорости и памяти огромен.
Пока Выборка.Следующий() Цикл
// Обработка текущей записи выборки
Сообщить(Выборка.Ссылка);
КонецЦикла;
Сравнение подходов: Хотя сам по себе цикл Пока медленнее, чем Для Каждого, связка Выбрать() + Пока Выборка.Следующий() почти всегда выигрывает при работе с запросами. Она потребляет на порядки меньше оперативной памяти и позволяет начать обработку данных немедленно, не дожидаясь полной выгрузки.
Борьба за миллисекунды при выборе цикла — это чаще всего преждевременная оптимизация. Гораздо больший эффект дает правильный подход к алгоритмам и работе с данными.
Подведем итоги и сформулируем простые правила:
Для Каждого. Он самый быстрый и удобный.Пока Выборка.Следующий(). Это экономит память и в большинстве случаев оказывается быстрее по общему времени выполнения.Для ... По ... Цикл.Сосредоточьтесь на оптимизации алгоритмов и запросов, а выбор самого цикла делайте, исходя из этих простых и проверенных практик.