В процессе разработки на платформе 1С:Предприятие 8.3 с использованием управляемых форм программисты часто сталкиваются с неочевидными нюансами производительности. Один из самых интересных парадоксов — ситуация, когда обычный цикл Для каждого, выполняемый на стороне клиента, отрабатывает быстрее, чем специализированный метод платформы НайтиСтроки для объекта ДанныеФормыКоллекция. Разберем детально, почему так происходит, как устроена работа с данными в управляемом интерфейсе и какие стратегии оптимизации следует применять в различных сценариях.
Рассмотрим ситуацию, типичную для многих прикладных решений. У нас есть форма документа или справочника, содержащая табличную часть. На клиенте эта табличная часть представлена типом ДанныеФормыКоллекция. Разработчику необходимо найти строки, соответствующие определенному условию (например, по владельцу и флагу удаления). Проанализируем два подхода, предложенных в обсуждении.
Вариант 1: Ручной перебор элементов в цикле на клиенте
В этом случае разработчик пишет собственную функцию, которая обходит коллекцию и собирает подходящие строки в массив:
Функция НайтиСтрокиДанныеФормыКоллекции(_Коллекция, Знач _Владелец) Экспорт
М = Новый Массив;
Для каждого Стр из _Коллекция Цикл
Если Стр.Владелец = _Владелец и Стр.Удалено = Ложь тогда
М.Добавить(Стр);
КонецЕсли;
КонецЦикла;
Возврат М;
КонецФункции
Вариант 2: Использование штатного метода НайтиСтроки
Здесь используется встроенная функция платформы, которая принимает структуру с параметрами поиска:
ПараметрыОтбора = Новый Структура("Владелец, Удалено", _Владелец, Ложь);
МассивРезультатов = Движения.НайтиСтроки(ПараметрыОтбора);
На практике выясняется, что для коллекций, содержащих несколько сотен строк, первый вариант работает ощутимо быстрее. Выясним причину этого явления, заглянув «под капот» платформы 1С.
Основной секрет медлительности метода НайтиСтроки для объекта ДанныеФормыКоллекция заключается в том, что этот метод при вызове на клиенте всегда инициирует обращение к серверу. Даже если форма уже открыта и данные отображаются на экране, платформа не выполняет поиск локально в памяти клиентского приложения.
Когда мы вызываем НайтиСтроки, происходят следующие процессы:
Структура (параметры поиска).ДанныеФормыЭлементКоллекции в своей памяти.Все эти накладные расходы на сетевое взаимодействие (latency) и сериализацию данных часто превышают время, которое требуется процессору клиента на простой перебор нескольких сотен строк в локальной памяти.
Рассмотрим, как ДанныеФормыКоллекция ведет себя при открытии формы. Платформа 1С оптимизирует трафик и не передает сразу все 10 000 строк табличной части на клиент. Данные подкачиваются порциями (по 20–50 строк) по мере того, как пользователь прокручивает таблицу в интерфейсе.
Однако, когда мы запускаем цикл Для каждого на клиенте, мы принудительно заставляем платформу «дочитать» недостающие данные. Рассмотрим этот процесс подробнее:
Метод же НайтиСтроки не обладает эффектом кэширования в таком виде — каждый его вызов — это новый поход на сервер, независимо от того, загружены данные в кэш клиента или нет.
Важно понимать разницу между ТаблицаЗначений и ДанныеФормыКоллекция. Объект ТаблицаЗначений, существующий только на сервере, позволяет создавать индексы по колонкам. Если проиндексировать колонку Владелец, метод НайтиСтроки для ТаблицаЗначений будет работать практически мгновенно даже на десятках тысяч строк.
Но ДанныеФормыКоллекция (представление таблицы на форме) не поддерживает индексацию. Поиск в ней на сервере — это точно такой же полный перебор, который выполняет сам программный код платформы. Следовательно, никакого алгоритмического преимущества у штатного метода перед ручным циклом в данном контексте нет.
Несмотря на то что на малых объемах перебор на клиенте быстрее, при росте количества строк ситуация меняется. Проанализируем пороговые значения:
Посмотрим на пример правильной оптимизации для больших таблиц. Вместо того чтобы мучить ДанныеФормыКоллекция на сервере, мы преобразуем её в настоящую таблицу значений с индексами:
&НаСервере
Функция НайтиСтрокиОптимизировано()
// 1. Выгружаем данные формы в объектную таблицу значений
ТЗ = Объект.ИмяТабличнойЧасти.Выгрузить();
// 2. Создаем индекс для ускорения поиска
ТЗ.Индексы.Добавить("Владелец, Удалено");
// 3. Выполняем поиск штатными средствами ТЗ
Отбор = Новый Структура("Владелец, Удалено", НужныйВладелец, Ложь);
МассивСтрокТЗ = ТЗ.НайтиСтроки(Отбор);
// Дальнейшая логика обработки...
КонецФункции
Такой подход на больших данных (от нескольких тысяч строк) будет в сотни раз быстрее любого перебора.
Сформулируем правила, которые помогут выбрать оптимальный метод работы:
1. Используйте цикл на клиенте, если вам нужно выполнить простой поиск в небольшой таблице (например, при событии ПриИзменении в строке табличной части для проверки уникальности). Это обеспечит мгновенную реакцию интерфейса без «песочных часов».
2. Избегайте метода НайтиСтроки на клиенте для объекта ДанныеФормыКоллекция, если важна производительность. Помните, что за каждым таким вызовом стоит сетевой пакет до сервера.
3. Проанализируйте динамику роста данных. Если ваша табличная часть сегодня содержит 10 строк, а завтра в ней могут оказаться тысячи (как в случае с динамически строящимися деревьями или загрузкой из внешних файлов), сразу проектируйте поиск на сервере с использованием ТаблицаЗначений и индексов.
4. Учитывайте тип клиента. В веб-клиенте накладные расходы на HTTP-запросы еще выше, чем в тонком клиенте. Там использование НайтиСтроки на клиенте будет еще более заметно тормозить работу пользователя.
5. Обратите внимание на «живые ссылки». Метод НайтиСтроки возвращает массив объектов ДанныеФормыЭлементКоллекции. Это специальные обертки, которые позволяют изменять данные в массиве так, чтобы они сразу менялись в форме. Если вам нужны просто значения из колонок для расчетов, а не сами объекты строк, то перебор коллекции с чтением только нужных свойств будет гораздо экономнее для памяти.
В завершение отметим, что выбор между «красивым» штатным методом и «быстрым» ручным циклом всегда должен основываться на замерах производительности в конкретных условиях вашей конфигурации. Помните о механизме тонких вызовов и старайтесь минимизировать количество обращений к серверу из клиентского кода.