Эта история началась поставленной мне задачи переноса доработок функционала в документа Сверка взаиморасчетов 2.4 в документ Сверка взаиморасчетов 2.5.
Когда я увидел существующий код, мне сразу стало очевидно, что это вопиющие расточительство вычислительных ресурсов!
Мною был создан оптимизированный вариант. Замеры времени работы двух вариантов кода, на документе с табличной частью в пять тысяч строк, показали колоссальное различие в производительности:
Существующий код выполнился за 2006 секунд

Оптимизированный код выполнился за 2 секунды

Фантастика! Рост производительности в тысячу раз!
Ниже представлен существующий код:
&НаКлиенте
&После("ЗаполнитьПоОстаткамЗавершение")
Процедура ИНФ_ЗаполнитьПоОстаткамЗавершение(Результат, ДополнительныеПараметры) Экспорт
ИНФ_ЗаполнитьПоОстаткамПосле(ЭтаФорма.Команды.ЗаполнитьПоОстаткам);
КонецПроцедуры
&НаКлиенте
Процедура ИНФ_ЗаполнитьПоОстаткамПосле(Команда)
Для каждого Строка из Объект.ДетальныеЗаписиРасчеты Цикл
Если ТипЗНЧ(Строка.РасчетныйДокумент) = Тип("ДокументСсылка.ОтчетКомитенту") Тогда
ВыбранноеЗначение = Строка.РасчетныйДокумент;
НомерИМ = ПоискНомераЗаказаИМ(ВыбранноеЗначение);
Если ЗначениеЗаполнено(НомерИМ) Тогда
Строка.ОписаниеДокумента = "Заказ № " + НомерИМ.РезультатНомер + " от "+НомерИМ.РезультатДата;
КонецЕсли;
ИначеЕсли ТипЗНЧ(Строка.РасчетныйДокумент) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
Строка.ОписаниеДокумента = Строка.ОписаниеДокумента + " (" + Строка(Строка.РасчетныйДокумент) + ")";
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Функция ПоискНомераЗаказаИМ(ВыбранноеЗначение)
ЗапросНомер = Новый Запрос;
ЗапросНомер.УстановитьПараметр("Ссылка", ВыбранноеЗначение);
ЗапросНомер.УстановитьПараметр("Свойство", ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоРеквизиту("Имя", "НомерЗаказаIM_42933da691f74ed6bbadc02702fb9ce9"));
ЗапросНомер.Текст = "ВЫБРАТЬ
| ОтчетКомитентуДополнительныеРеквизиты.Значение КАК Значение
|ИЗ
| Документ.ОтчетКомитенту.ДополнительныеРеквизиты КАК ОтчетКомитентуДополнительныеРеквизиты
|ГДЕ
| ОтчетКомитентуДополнительныеРеквизиты.Ссылка = &Ссылка
| И ОтчетКомитентуДополнительныеРеквизиты.Свойство = &Свойство
|";
Выборка = ЗапросНомер.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
РезультатНомер = Выборка.Значение;
КонецЦикла;
ЗапросДата = Новый Запрос;
ЗапросДата.УстановитьПараметр("Значение", РезультатНомер);
ЗапросДата.УстановитьПараметр("Свойство", ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоРеквизиту("Имя", "НомерЗаказаIM_42933da691f74ed6bbadc02702fb9ce9"));
ЗапросДата.Текст = "ВЫБРАТЬ
| ЗаказКлиентаДополнительныеРеквизиты.Значение КАК Значение,
| ЗаказКлиентаДополнительныеРеквизиты.Ссылка.Дата КАК Дата
|ИЗ
| Документ.ЗаказКлиента.ДополнительныеРеквизиты КАК ЗаказКлиентаДополнительныеРеквизиты
|ГДЕ
| ЗаказКлиентаДополнительныеРеквизиты.Свойство = &Свойство
| И ЗаказКлиентаДополнительныеРеквизиты.Значение = &Значение
|";
ВыборкаДата = ЗапросДата.Выполнить().Выбрать();
Пока ВыборкаДата.Следующий() Цикл
РезультатДата = ВыборкаДата.Дата;
КонецЦикла;
РезультатНомер = СтрЗаменить(СокрЛ(СтрЗаменить(РезультатНомер,"0"," "))," ","0");
РезультатДата = Формат(РезультатДата,"ДФ=dd.MM.yyyy");
СтруктураТекст = Новый Структура;
СтруктураТекст.Вставить("РезультатНомер",РезультатНомер);
СтруктураТекст.Вставить("РезультатДата",РезультатДата);
Возврат ?(СтруктураТекст=Неопределено,0,СтруктураТекст);
КонецФункции
Разберём недостатки этого кода:
Первый источник потерь в процедуре "ИНФ_ЗаполнитьПоОстаткамПосле" реализован обход табличной части на клиенте с вызовом внутри тела цикла функции "ПоискНомераЗаказаИМ", выполняющейся на сервере.
Обращаю внимание, что перед объявлением функции "ПоискНомераЗаказаИМ" отсутствует какой-либо модификатор, это значит, что по умолчанию на сервер будет передаваться полный контекст формы.
При этом платформа 1С:
- на стороне клиента, сериализует объект формы (со всеми реквизитами формы и объекта включая табличные части)
- сериализованные данные передаются на сервер
- на сервере, сериализованные данные десереализуются в объект формы
- на сервере выполнятся вызванная функция
- сервер сериализует объект формы и возвращает его вместе с результатом функции на клиент
- на стороне клиента, сериализованные данные десереализуются в объект формы
Это довольно затратные операции, особенно когда документ имеет табличные части с большим количеством строк, и они выполняются каждую итерацию цикла, что естественно ведёт к расходу процессорного времени на описанные выше преобразования.
Второй источник потерь сама функция "ПоискНомераЗаказаИМ" в ней выполняются аж 4 запроса:
Два явных запроса и два неявных, выполняющих поиск ссылки на дополнительный реквизит, при передаче параметров явным запросам:
ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоРеквизиту("Имя", "НомерЗаказаIM_42933da691f74ed6bbadc02702fb9ce9")
Дополнительный реквизит можно было получить один раз перед телом цикла, поместить в переменную и далее использовать сохранённое в переменной значение.
Основные направления оптимизации - это сократить количество обращений к серверу и базе данных до минимума. Чтобы ускорить работу, нужно перенести обход табличной части на сервер и вместо получения информации из базы по каждой строке табличной части, получить данные пакетом сразу для всех строк таблицы, которые подлежат обработке.
Согласно концепции, изложенной выше, получился следующий оптимизированный код:
&НаКлиенте
&После("ЗаполнитьПоОстаткамЗавершение")
Процедура ИНФ_ЗаполнитьПоОстаткамЗавершение(Результат, ДополнительныеПараметры) Экспорт
ИНФ_КорректировкаТЧПриЗаполненииПоОстаткам();
КонецПроцедуры
&НаСервере
Процедура ИНФ_КорректировкаТЧПриЗаполненииПоОстаткам()
Перем тзИсходныеДанные, КЧ, МассивТипов, сеДопустимыеТипы,
Элем, Инд, КодТипа, ВГраница, Стр, НСтр, Свойство_НомерЗаказаIM,
Запрос, РезультатЗапроса, Выборка1, Выборка2,
НомерЗаказаIM, Инд_Пред;
// НомерЗаказаIM
Свойство_НомерЗаказаIM = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.ПолучитьСсылку(
Новый УникальныйИдентификатор("8367e4de-3643-11ec-91f0-005056b343de")
);
КЧ = Новый КвалификаторыЧисла(10, 0);
сеДопустимыеТипы = Новый Соответствие();
сеДопустимыеТипы.Вставить(Тип("ДокументСсылка.ОтчетКомитенту"), 1);
сеДопустимыеТипы.Вставить(Тип("ДокументСсылка.РеализацияТоваровУслуг"), 2);
МассивТипов = Новый Массив();
Для Каждого Элем Из сеДопустимыеТипы Цикл
МассивТипов.Добавить(Элем.Ключ);
КонецЦикла;
тзИсходныеДанные = Новый ТаблицаЗначений();
тзИсходныеДанные.Колонки.Добавить("Инд", Новый ОписаниеТипов("Число",,, КЧ));
тзИсходныеДанные.Колонки.Добавить("РасчетныйДокумент", Новый ОписаниеТипов(МассивТипов));
тзИсходныеДанные.Колонки.Добавить("КодТипа", Новый ОписаниеТипов("Число",,, КЧ));
ВГраница = ЭтаФорма.Объект.ДетальныеЗаписиРасчеты.Количество() - 1;
Для Инд = 0 По ВГраница Цикл
Стр = ЭтаФорма.Объект.ДетальныеЗаписиРасчеты[Инд];
КодТипа = сеДопустимыеТипы.Получить(ТипЗнч(Стр.РасчетныйДокумент));
Если КодТипа = Неопределено Тогда
Продолжить;
КонецЕсли;
НСтр = тзИсходныеДанные.Добавить();
НСтр.Инд = Инд;
НСтр.РасчетныйДокумент = Стр.РасчетныйДокумент;
НСтр.КодТипа = КодТипа;
КонецЦикла;
Запрос = Новый Запрос();
Запрос.Текст =
"//0////////////////////////////////////////////////////////
|ВЫБРАТЬ
| т.Инд,
| т.РасчетныйДокумент,
| т.КодТипа
|
| ПОМЕСТИТЬ втИсходныеДанные
|
|ИЗ &тзИсходныеДанные КАК т
|
|ИНДЕКСИРОВАТЬ ПО
| т.КодТипа
|
|;
|//1////////////////////////////////////////////////////////
|ВЫБРАТЬ
| т.Инд,
| ОтчетКомитентуДР.Значение КАК НомерЗаказаIM,
| ЕСТЬNULL(ЗаказКлиента.Дата, ДАТАВРЕМЯ(1,1,1)) КАК ДатаЗаказа
|
|ИЗ втИсходныеДанные КАК т
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ОтчетКомитенту.ДополнительныеРеквизиты КАК ОтчетКомитентуДР ПО
| ОтчетКомитентуДР.Ссылка = т.РасчетныйДокумент
| И ОтчетКомитентуДР.Свойство = &Свойство_НомерЗаказаIM
|
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.ДополнительныеРеквизиты КАК ЗаказКлиентаДР ПО
| ЗаказКлиентаДР.Свойство = &Свойство_НомерЗаказаIM
| И ЗаказКлиентаДР.Значение = ОтчетКомитентуДР.Значение
|
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента КАК ЗаказКлиента ПО
| НЕ ЗаказКлиентаДР.Ссылка ЕСТЬ NULL
| И ЗаказКлиента.Ссылка = ЗаказКлиентаДР.Ссылка
|
|ГДЕ
| т.КодТипа = 1 // ОтчетКомитенту
|
|УПОРЯДОЧИТЬ ПО
| т.Инд,
| ЕСТЬNULL(ЗаказКлиента.Проведен, Ложь) УБЫВ,
| ЕСТЬNULL(ЗаказКлиента.ПометкаУдаления, Ложь)
|;
|//2////////////////////////////////////////////////////////
|ВЫБРАТЬ
| т.Инд,
| ПРЕДСТАВЛЕНИЕ(ВЫРАЗИТЬ(т.РасчетныйДокумент КАК Документ.РеализацияТоваровУслуг)) КАК ПредставлениеДокумента
|
|ИЗ втИсходныеДанные КАК т
|ГДЕ
| т.КодТипа = 2 // РеализацияТоваровУслуг
|";
Запрос.УстановитьПараметр("тзИсходныеДанные", тзИсходныеДанные);
Запрос.УстановитьПараметр("Свойство_НомерЗаказаIM", Свойство_НомерЗаказаIM);
РезультатЗапроса = Запрос.ВыполнитьПакет();
// ОтчетКомитенту
Выборка1 = РезультатЗапроса[1].Выбрать();
Пока Выборка1.Следующий() Цикл
Если Выборка1.Инд = Инд_Пред Тогда
Продолжить;
КонецЕсли;
Инд_Пред = Выборка1.Инд;
Стр = ЭтаФорма.Объект.ДетальныеЗаписиРасчеты[Выборка1.Инд];
НомерЗаказаIM = СтрЗаменить(
СокрЛ(СтрЗаменить(Выборка1.НомерЗаказаIM, "0", " ")),
" ", "0"
);
Стр.ОписаниеДокумента = СтрШаблон(
"Заказ № %1 от %2",
НомерЗаказаIM,
Формат(Выборка1.ДатаЗаказа, "ДФ=дд.ММ.гггг")
);
КонецЦикла;
// РеализацияТоваровУслуг
Выборка2 = РезультатЗапроса[2].Выбрать();
Пока Выборка2.Следующий() Цикл
Стр = ЭтаФорма.Объект.ДетальныеЗаписиРасчеты[Выборка2.Инд];
Стр.ОписаниеДокумента = СтрШаблон(
"%1 (%2)",
Стр.ОписаниеДокумента,
Выборка2.ПредставлениеДокумента
);
КонецЦикла;
КонецПроцедуры // ИНФ_КорректировкаТЧПриЗаполненииПоОстаткам
Вкратце пройдусь по применённым техническим решениям:
С клиента управление сразу передаётся на сервер и далее все манипуляции с табличной частью документа происходят на сервере.
Для того чтобы получить одним запросом нужные нам данные (по всем обрабатываемым строкам табличной части) создаётся таблица значений, в которую записывается индекс строки табличной части, ссылка на расчётный документ и числовой код типа, который позволит быстро идентифицировать и индексировать содержимое временной таблицы по типам документов:
КЧ = Новый КвалификаторыЧисла(10, 0);
сеДопустимыеТипы = Новый Соответствие();
сеДопустимыеТипы.Вставить(Тип("ДокументСсылка.ОтчетКомитенту"), 1);
сеДопустимыеТипы.Вставить(Тип("ДокументСсылка.РеализацияТоваровУслуг"), 2);
МассивТипов = Новый Массив();
Для Каждого Элем Из сеДопустимыеТипы Цикл
МассивТипов.Добавить(Элем.Ключ);
КонецЦикла;
тзИсходныеДанные = Новый ТаблицаЗначений();
тзИсходныеДанные.Колонки.Добавить("Инд", Новый ОписаниеТипов("Число",,, КЧ));
тзИсходныеДанные.Колонки.Добавить("РасчетныйДокумент", Новый ОписаниеТипов(МассивТипов));
тзИсходныеДанные.Колонки.Добавить("КодТипа", Новый ОписаниеТипов("Число",,, КЧ));
Заполнение таблицы значений выполняется через обход строк табличной части документа, путём обращения к ним через индекс. Это решение позволит при чтении данных из выборки сразу по индексу получать строку табличной части, к которой относятся данные выборки, то есть обойтись без поиска строк каждую итерацию цикла, по какому-либо набору полей.
ВГраница = ЭтаФорма.Объект.ДетальныеЗаписиРасчеты.Количество() - 1;
Для Инд = 0 По ВГраница Цикл
Стр = ЭтаФорма.Объект.ДетальныеЗаписиРасчеты[Инд];
КодТипа = сеДопустимыеТипы.Получить(ТипЗнч(Стр.РасчетныйДокумент));
Если КодТипа = Неопределено Тогда
Продолжить;
КонецЕсли;
НСтр = тзИсходныеДанные.Добавить();
НСтр.Инд = Инд;
НСтр.РасчетныйДокумент = Стр.РасчетныйДокумент;
НСтр.КодТипа = КодТипа;
КонецЦикла;
Далее полученная таблица значений передаётся в качестве параметра в запрос, где к ней выполняются соединения таблиц базы, где находятся требующиеся данные.
Поскольку соединение таблиц в запросе, может приводить к размножению строк исходной таблицы, в код запроса введена сортировка по полю "Инд", содержащему индекс строки табличной части документа, а в цикле обхода, по первой выборке пакета запросов, выполняется сравнение поля "Инд" выборки с его же значением в предыдущей итерации цикла, и если значение повторяется, то данные выборки не обрабатываются. Это решение исключает влияние на результат размножения строк исходной таблицы при соединениях.
Выборка1 = РезультатЗапроса[1].Выбрать();
Пока Выборка1.Следующий() Цикл
Если Выборка1.Инд = Инд_Пред Тогда
Продолжить;
КонецЕсли;
Инд_Пред = Выборка1.Инд;
Стр = ЭтаФорма.Объект.ДетальныеЗаписиРасчеты[Выборка1.Инд];
НомерЗаказаIM = СтрЗаменить(
СокрЛ(СтрЗаменить(Выборка1.НомерЗаказаIM, "0", " ")),
" ", "0"
);
Стр.ОписаниеДокумента = СтрШаблон(
"Заказ № %1 от %2",
НомерЗаказаIM,
Формат(Выборка1.ДатаЗаказа, "ДФ=дд.ММ.гггг")
);
КонецЦикла;
Код тестировался на платформе 8.3.27.1688, на конфигурации КА 2.5.22.134.
Вступайте в нашу телеграмм-группу Инфостарт
