Периодически возникает необходимость в ускорении некоторых участков кода, в которых при беглом ознакомлении нет потенциала для ускорения.
Рассмотрим в качестве примера выгрузку и/или загрузку большого количества данных.
В данном случае «планкой производительности» зачастую выступает непосредственно производительность аппаратных средств. При этом сами процедуры загрузки-выгрузки выполняются достаточно длительное время, часто измеряемое в часах.
Не все знают, что в платформе «1С: Предприятие 8» часть операций можно выполнять параллельно, путем одновременного выполнения в несколько потоков.
Такая многопоточная обработка вполне применима в случае, когда нужно обработать независимые блоки данных. В частности, создать большой объем элементов данных, осуществить проведение документов, не пересекающихся по значениям набора измерений, сделать массовое обновление элементов справочников, выполнить загрузку данных в регистры и т.д. Для примера рассмотрим задачу: необходимо провести обновление реквизита «Цена» для всех элементов справочника «Товары». Количество элементов справочника «Товары» равно 100 000. Самым простым и очевидным решением выступает нижеприведенный код обработки, который быстро пишется, но при этом очень долго работает:
&НаСервере
Процедура ОбновитьЦену()
ВремяНачала = ТекущаяДата();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка
|ИЗ
| Справочник.Товары КАК Товары";
ТаблицаТоваров = Запрос.Выполнить().Выгрузить();
Наценка = 1.10;
Для каждого ТекСторока Из ТаблицаТоваров Цикл
ТоварОбъект = ТекСторока.Ссылка.ПолучитьОбъект();
ТоварОбъект.Цена = ТоварОбъект.Цена*Наценка;
ТоварОбъект.Записать();
КонецЦикла;
Длительность = ТекущаяДата()-ВремяНачала;
Сообщить("Длительность: " + Длительность + " сек.");
КонецПроцедуры
В нашем случае обработка выполнялась 1 187 секунд или 19,7 минуты. Теперь воспользуемся многопоточным выполнением программного кода. Перепишем код обработки следующим образом:
&НаСервере
Процедура ОбновитьЦену()
ВремяНачала = ТекущаяДата();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка
|ИЗ
| Справочник.Товары КАК Товары";
ТаблицаТоваров = Запрос.Выполнить().Выгрузить();
// определяем максимальное количество потоков
ЧислоПотоков = 8;
ЧислоСтрокВТаблице = ТаблицаТоваров.Количество();
// объем порции данных для обработки каждым потоком
РазмерПорции = Цел(ЧислоСтрокаВТаблице/ЧислоПотоков);
// массив, где будут храниться фоновые задания
МассивЗаданий = НовыйМассив;
Для НомерПотока = 1 По ЧислоПотоков Цикл
// определяем индекс для начала обработки данных данным потоком
// разные потоки обрабатывают разные части таблицы
ИндексНачала = (НомерПотока - 1)*РазмерПорции;
Если (НомерПотока = ЧислоПотоков) Тогда
// если это последний поток, то он обрабатывает все оставшиеся данные
// т.к. число потоков может не быть кратно количеству строк в таблице
РазмерПорции = ЧислоСтрокВТаблице -(ЧислоПотоков*РазмерПорции)+РазмерПорции;
КонецЕсли;
// определяем массив параметров для процедуры
НаборПараметров = Новый Массив;
НаборПараметров.Добавить(ТаблицаТоваров);
НаборПараметров.Добавить(ИндексНачала);
НаборПараметров.Добавить(РазмерПорции);
// запуск фонового задания
Задание = ФоновыеЗадания.Выполнить("ОбщийМодуль1.ОбновитьЦенуТовара", НаборПараметров);
// добавляем задание в массив, чтобы потом отследить выполнение
МассивЗаданий.Добавить(Задание);
КонецЦикла;
// проверим результат выполнения фоновых заданий
Если МассивЗаданий.Количество() > 0 Тогда
Попытка
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
Исключение
// действия в случае ошибки
КонецПопытки;
КонецЕсли;
Длительность = ТекущаяДата()-ВремяНачала;
Сообщить("Длительность: " + Длительность + "сек.");
КонецПроцедуры
Процедура ОбновитьЦенуТовара (ТаблицаТоваров,ИндексНачала,РазмерПорции) Экспорт
Наценка = 1.10; // наценка 10%
// обновляем цену только для определенной части таблицы
Для Сч = 1 По РазмерПорцииЦикл
Индекс = ?(Сч=1,ИндексНачала,Индекс+1);
ТоварОбъект = ТаблицаТоваров[Индекс].Ссылка.ПолучитьОбъект();
ТоварОбъект.Цена = ТоварОбъект.Цена * Наценка;
ТоварОбъект.Записать();
КонецЦикла;
КонецПроцедуры
В нашем случае использовалось восемь потоков, обновление цен было выполнено за 859 секунд или 14,3 минуты.
То есть обработка одной и той же Таблицы Значений параллельно разными потоками приводит к выигрышу в скорости.
Код выполнялся на виртуальной машине с одним процессором без RAID массивов. На реальном и хорошем «железе» выигрыш в скорости будет существенно больше.
Заметим, что данную задачу можно решить по-другому, мы привели лишь один пример реализации.
Важно! Не нужно устанавливать слишком большое количество потоков, так как большого прироста скорости вы от этого все равно не получите, а стабильность работы может нарушиться.
Наилучшим будет использование 8-10 потоков, а их оптимальное количество можно определить экспериментально. Попробуйте самостоятельно провести эксперимент, база и обработки в приложении к статье.
Бурмистров Андрей
Другие полезные статьи и видео смотрите на нашем сайте: http://