При разработке и оптимизации алгоритмов в 1С:Предприятие часто возникает вопрос: как эффективнее искать нужные данные в таблице значений? Выбрать встроенный метод платформы НайтиСтроки() или написать собственный перебор строк в цикле с условиями? Давайте подробно разберем этот вопрос, проанализируем результаты замеров производительности и выясним, в каких ситуациях каждый из подходов будет наиболее оправданным.
С точки зрения теории, встроенные функции платформы 1С, написанные на уровне ядра (C++), всегда должны отрабатывать быстрее, чем эмуляция аналогичного алгоритма кодом на встроенном языке. При вызове НайтиСтроки() платформа использует внутренние оптимизированные механизмы перебора и сравнения. Однако на практике возможны нюансы.
Рассмотрим ситуацию, когда в результатах поиска оказывается подавляющее большинство строк исходной таблицы. Если после выполнения поиска вам всё равно придется обходить полученный массив в цикле для обработки данных, то первоначальная экономия времени на поиске может свестись к нулю. В таком случае обычный проход циклом по исходной таблице с проверкой условия внутри итерации отработает ничуть не хуже, а память не будет засоряться промежуточными массивами.
Проанализируем главный фактор, влияющий на быстродействие — индексацию колонок таблицы значений.
Если мы просто выполняем тупой перебор большого количества строк (например, 1 миллион записей) в поиске конкретного значения, то алгоритм выполняет полное сканирование таблицы. В тестах, проведенных разработчиками, поиск одного значения в неиндексированной таблице на 1 000 000 строк может занимать значительное время (от десятков миллисекунд до нескольких секунд, в зависимости от типов данных и сложности условий).
Посмотрим на замеры производительности при поиске по колонке с типом УникальныйИдентификатор:
НайтиСтроки() без индекса: затрачено около 75 мс.НайтиСтроки() по проиндексированной колонке: затрачено всего 1 мс!Казалось бы, разница очевидна, и нужно всегда использовать индексы. Но давайте выясним скрытую причину, почему это не всегда так.
Индекс — это не магическая сущность, которая появляется мгновенно. Это отсортированная структура данных (массив колонок), и на ее создание платформа тратит вычислительные ресурсы. При использовании алгоритмов быстрой сортировки время построения индекса пропорционально N * log2(N), где N — количество строк. В то время как обычный линейный поиск требует времени, пропорционального просто N.
Разберем результаты тестирования из обсуждения. При индексации колонки с 1 000 000 записей (UUID) время, затраченное на создание самого индекса, составило 4 141 мс.
Отсюда выводим важное правило: если вам нужно осуществить единоразовый поиск в таблице, создание индекса приведет к серьезной деградации производительности. Вы потратите 4 секунды на индексацию, чтобы сэкономить 74 миллисекунды на самом поиске! Индексирование оправдывает себя исключительно в тех случаях, когда поиск по одной и той же таблице значений будет выполняться многократно (в цикле или при множественных вызовах).
Рассмотрим пример кода, с помощью которого можно провести подобное тестирование:
Функция ПолучитьЗаполненнуюТЗ()
МассивУИД = ПолучитьСписокУИД();
ГСЧ = Новый ГенераторСлучайныхЧисел;
ТЗ = Новый ТаблицаЗначений;
Для К = 1 По 10 Цикл
ТЗ.Колонки.Добавить("Кол" + К);
КонецЦикла;
Для К = 1 По 1000000 Цикл
СтрТЗ = ТЗ.Добавить();
СтрТЗ["Кол1"] = МассивУИД[ГСЧ.СлучайноеЧисло(0, МассивУИД.ВГраница())];
КонецЦикла;
УИДПоиска = МассивУИД[ГСЧ.СлучайноеЧисло(0, МассивУИД.ВГраница())];
// Замер времени индексации
ВремяНачалаЗамера = ТекущаяУниверсальнаяДатаВМиллисекундах();
ТЗ.Индексы.Добавить("Кол1");
ВремяОкончанияЗамера = ТекущаяУниверсальнаяДатаВМиллисекундах();
Сообщить("Время индексации ТЗ: " + (ВремяОкончанияЗамера - ВремяНачалаЗамера));
Возврат ТЗ;
КонецФункции
Функция ПолучитьСписокУИД()
МассивУИД = Новый Массив;
Для К = 1 По 1000 Цикл
МассивУИД.Добавить(Новый УникальныйИдентификатор);
КонецЦикла;
Возврат МассивУИД;
КонецФункции
Чтобы принимать правильное решение об оптимизации, давайте подробнее изучим, как работает данный метод и какие у него есть ограничения:
Структура. Вы можете добавить в эту структуру сразу несколько свойств (имена ключей должны совпадать с именами колонок таблицы значений). Это позволяет выполнять одновременный поиск по совпадению сразу в нескольких колонках.НайтиСтроки() умеет искать значения исключительно на точное равенство (подобно оператору =). Если ваша бизнес-логика требует условий вида "Больше", "Меньше", "Входит в строку" или "Не равно", данный метод вам не подойдет. В таких случаях придется использовать обход в цикле или помещать таблицу во временную таблицу базы данных и применять Запрос.Массив. Важно понимать, что этот массив содержит ссылки на строки оригинальной таблицы значений, а не их копии. Если мы переберем полученный массив циклом и поменяем значения в полях, данные мгновенно изменятся и в самой исходной таблице значений.НайтиСтроки() с функцией Найти(). Метод Найти() возвращает только первую найденную строку и прекращает поиск. Кроме того, при неудаче он возвращает Неопределено, в то время как НайтиСтроки() вернет пустой Массив. Обязательно учитывайте это в коде: перед обращением к элементу массива по индексу всегда проверяйте Количество() > 0, иначе возникнет ошибка исполнения.Особо проанализируем ситуацию с управляемыми формами. Если вы работаете с типом ДанныеФормыКоллекция (например, с табличной частью документа, размещенной на форме) в контексте тонкого клиента, использование НайтиСтроки() может сыграть с производительностью злую шутку.
Вызов этого метода для коллекций формы на клиенте приводит к неявному серверному вызову. Платформа берет всю таблицу, передает ее на сервер, там выполняет поиск и возвращает результат обратно на клиент. На больших объемах данных это вызовет серьезные "тормоза" и зависания интерфейса. По стандартам разработки 1С, для таких случаев лучше применять явный серверный вызов (передать управление на сервер директивой компиляции и сделать все там) или обойти коллекцию обычным циклом Для Каждого непосредственно на стороне клиента.
Так что же выбрать? Давайте резюмируем:
ДанныеФормыКоллекция на клиенте.