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