При разборе больших файлов XML и загрузке их содержимого в табличные части документов разработчики часто сталкиваются с существенным падением производительности. В этой статье мы разберем типичные ошибки при обходе объектов XDTO и рассмотрим методы оптимизации, позволяющие ускорить обработку тысяч строк с нескольких минут до нескольких секунд.
Проанализируем базовый сценарий: из входящего XML-файла с помощью фабрики XDTO считывается структура данных, после чего выполняется обход свойства и заполнение строк табличной части документа. Ниже приведен пример исходного кода, вызывающего задержки:
// Чтение XML в ОбъектXDTO
ЧтениеXML.УстановитьСтроку(XMLСтрока);
Об = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML, Тип_);
// Заполнение табличной части SLUCH
НС = Объект.SLUCH.Добавить();
Для Каждого Эл Из Об.SLUCH[0].Свойства() Цикл
ЗаполнитьПоле(НС[Эл.Имя], Об.SLUCH[0][Эл.Имя], , "SLUCH ");
КонецЦикла;
Процедура ЗаполнитьПоле(Поле, Значение, РазрешитьДобавить = Ложь, Коментарий = "")
Если Значение <> Неопределено Тогда
Если ТипЗнч(Поле) = Тип("Строка") Или ТипЗнч(Поле) = Тип("Число") Или ТипЗнч(Поле) = Тип("Дата") Тогда
Поле = Значение;
Иначе
Мд = Метаданные.НайтиПоТипу(ТипЗнч(Поле));
Спр = Справочники[Мд.Имя];
Если СокрЛП(Значение) <> "" Тогда
Если ТипЗнч(Поле.Код) = Тип("Строка") Тогда
Поле = Спр.НайтиПоКоду(Строка(Значение));
ИначеЕсли ТипЗнч(Поле.Код) = Тип("Число") Тогда
Попытка
Поле = Спр.НайтиПоКоду(Число(Значение));
Исключение
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Выясним главные проблемы данного подхода:
НайтиПоКоду() выполняет отдельный SQL-запрос к базе данных при каждом вызове. Если в документе тысячи строк и десятки ссылочных колонок, система генерирует сотни тысяч одиночных запросов.Метаданные.НайтиПоТипу() на каждой итерации внутри цикла по свойствам создает высокую нагрузку на платформу 1С из-за постоянно повторяющейся рефлексии.НС[Эл.Имя] в процедуру меняет только значение локальной переменной Поле, но не записывает результат непосредственно в строку табличной части.Рассмотрим первый метод оптимизации — отказ от поиска элементов справочника непосредственно в момент обхода строки. Вместо вызова НайтиПоКоду() в цикле мы можем один раз собрать все уникальные коды из XML, получить ссылки единым запросом и сохранить их в Соответствие.
Проанализируем порядок действий:
В (&МассивКодов) для каждого типа справочника.Соответствие, где ключом выступает код из XML, а значением — полученная ссылка на элемент справочника.Посмотрим на пример реализации алгоритма кэширования кодов:
// Инициализация кэша ссылок
КэшКодов = Новый Соответствие; // Ключ - Код, Значение - Ссылка
// Пример заполнения кэша для справочника "Номенклатура"
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код КАК Код
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Код В(&МассивКодов)";
Запрос.УстановитьПараметр("МассивКодов", МассивПрочитанныхКодов);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КэшКодов.Вставить(СокрЛП(Выборка.Код), Выборка.Ссылка);
КонецЦикла;
Для исключения постоянных обращений к глобальному контексту метаданных рекомендуется зафиксировать типы и свойства реквизитов табличной части до начала загрузки данных. Создадим описание структуры колонок один раз перед циклом обработки.
// Сформируем описание типов реквизитов табличной части
КартаРеквизитов = Новый Структура;
ТаблицаДокумента = Объект.SLUCH;
МетаданныеТЧ = Метаданные.Документы[Объект.Метаданные().Имя].ТабличныеЧасти.SLUCH;
Для Каждого Реквизит Из МетаданныеТЧ.Реквизиты Цикл
ОписаниеРеквизита = Новый Структура;
ОписаниеРеквизита.Вставить("Имя", Реквизит.Имя);
ОписаниеРеквизита.Вставить("Тип", Реквизит.Тип);
// Сохраняем имя справочника для ссылочных типов
Если Реквизит.Тип.СодержитТип(Тип("СправочникСсылка.Клиенты")) Тогда
ОписаниеРеквизита.Вставить("ИмяСправочника", "Клиенты");
КонецЕсли;
КартаРеквизитов.Вставить(Реквизит.Имя, ОписаниеРеквизита);
КонецЦикла;
Построчное добавление элементов через метод Объект.SLUCH.Добавить() заставляет платформу пересчитывать индексы и обновлять коллекцию строк на каждом шаге. Чтобы ускорить процесс, заполним оперативную ТаблицуЗначений в памяти, после чего загрузим её в документ одной операцией.
Разберем пошаговый пример высокопроизводительной загрузки:
// Выгружаем структуру ТЧ в новую Таблицу Значений
ТЗЗагрузки = Объект.SLUCH.ВыгрузитьКолонки();
// Заполняем строку таблицы значений
Для Каждого СтрокаXML Из Об.SLUCH Цикл
НоваяСтрока = ТЗЗагрузки.Добавить();
Для Каждого Свойство Из СтрокаXML.Свойства() Цикл
ИмяПоля = Свойство.Имя;
ЗначениеXML = СтрокаXML[ИмяПоля];
Если ЗначениеXML = Неопределено Тогда
Продолжить;
КонецЕсли;
// Получаем ссылку из заранее подготовленного кэша
НайденнаяСсылка = КэшКодов[СокрЛП(ЗначениеXML)];
Если НайденнаяСсылка <> Неопределено Тогда
НоваяСтрока[ИмяПоля] = НайденнаяСсылка;
Иначе
НоваяСтрока[ИмяПоля] = ЗначениеXML;
КонецЕсли;
КонецЦикла;
КонецЦикла;
// Однократная загрузка всех подготовленных строк в документ
Объект.SLUCH.Загрузить(ТЗЗагрузки);
Сводные правила для ускорения обработки XML при работе с XDTO:
&НаСервере без передач промежуточных данных на клиент.Загрузить() вместо построчного добавления через Добавить().