Другие статьи по оптимизации 1С:

«Анализ запросов с помощью SQL Profiler»

«Как ускорить 1С за 5 минут – Протокол Shared Memory»

«Как диагностировать ошибки платформы «1С:Предприятие 8»»

Как ускорить 1С – Многопоточная обработка данных

Что Вы узнаете из этой статьи?

  • Вы изучите варианты ускорения операций обработки данных
  • Научитесь запускать в «1С:Предприятие 8» несколько потоков
  • Узнаете об ограничениях многопоточного режима в 1С

Периодически возникает необходимость в ускорении некоторых участков кода, в которых при беглом ознакомлении нет потенциала для ускорения.

Рассмотрим в качестве примера выгрузку и/или загрузку большого количества данных.

В данном случае «планкой производительности» зачастую выступает непосредственно производительность аппаратных средств. При этом сами процедуры загрузки-выгрузки выполняются достаточно длительное время, часто измеряемое в часах.

Не все знают, что в платформе «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 потоков, а их оптимальное количество можно определить экспериментально.

Попробуйте самостоятельно провести эксперимент, базу и обработки можно скачать здесь.

Бурмистров Андрей

PDF-версия статьи для участников группы ВКонтакте

Мы ведем группу ВКонтакте — http://vk.com/kursypo1c.

Если Вы еще не вступили в группу — сделайте это сейчас и в блоке ниже (на этой странице) появятся ссылка на скачивание материалов.

Если Вы уже участник группы — нужно просто повторно авторизоваться в ВКонтакте, чтобы скрипт Вас узнал. В случае проблем решение стандартное: очистить кеш браузера или подписаться через другой браузер.
Станьте экспертом по оптимизации 1С, изучив наш курс
«Ускорение и оптимизация систем на 1С:Предприятие 8.3 (2016). Подготовка на 1С:Эксперт по технологическим вопросам»

Содержание курса и форма заказа: http://курсы-по-1с.рф/1c-v8/optimization/

35 учебных часов, подготовка к 1С:Эксперт, правильная настройка серверной части, оптимизация кода, мониторинг загруженности оборудования и прочие взрослые вещи.

29 Responses to Как ускорить 1С – Многопоточная обработка данных

  • irek

    Здравствуйте.
    Есть конфигурация с таким кодом: «ФоновыеЗадания.ОжидатьЗавершения», этот код в файловой базе не будет совсем работать или будет работать очень медленно?
    Проблема в том что при запуске этого метода, нет никакого видимого процесса работы: ни обращение к процессору, ни к файлам, ожидал больше 20 минут без видимого результата. В конфигураторе не вижу созданного фонового задания.
    Развернул на Sql сервере базу метод отрабатывает за пару минут.
    В чём может быть проблема?
    Спасибо.

    • Андрей Бурмистров

      «Проблема» в файловой базе, там фоновые задание работают не так как в клиент-серверном варианте. Данный пример предназначен для клиент-серверного варианта.

  • Артем

    Добрый день!
    Если данный способ распараллеливания использовать для обработки большой таблицы значений получаемой не запросом а кодом, то при передачи её в фоновое задание был замечен непропорциональный рост объема занятой оперативной памяти, равное 2-3 объемам передаваемой таблицы, при этом если запуск фоновых заданий происходит достаточно часто через 2-3 дня происходит жуткое замедление работы рабочих процессов сервера приложений, спасает только перезапуск. Есть какой-то более эффективный способ передачи данных в фоновое задание, без записи в базу? Или способ борьбы с ростом и высвобождением объема занятой памяти?

    • Андрей Бурмистров

      Данная статья это просто демонстрация возможностей, нет необходимости передавать ТЗ в каждое фоновое задание. Необходимо что бы каждое фоновое задание считывало из базы данных только тот объем, который необходим именно этому заданию для обработки.

      Например, оптимизация подобной ситуации является составной частью финального задания курса по оптимизации.

      • Артем

        Это да, но получать данные в самом фоновом не всегда возможно. Я и уточнил в комментарии «значений получаемой не запросом а кодом», т.е. какие-то расчеты таблицы в целом, например тот же расчет себестоимости, а потом чтоб запись дынных в регистр(к сожалению в 1С с этим проблемы) выполнять уже в фоновых. Это как один из примеров.
        Поэтому вопрос был немного шире темы затронутой в статье, но все же актуальный для многопоточной обработки данных, а именно: Есть какой-то более эффективный способ передачи данных в фоновое задание, без записи в базу? Или способ борьбы с ростом и высвобождением объема занятой памяти?

        • Андрей Бурмистров

          1. Есть какой-то более эффективный способ передачи данных в фоновое задание, без записи в базу?

          Может и есть, но мне он не известен. Хотя я не вижу ничего плохого в том, что бы для обмена между ФЗ писать часть данных в базу.

          2. Или способ борьбы с ростом и высвобождением объема занятой памяти?

          Как минимум стандартная рекомендация, ставить интервал перезапуска рабочих процессов 1 раз в сутки.

  • Антон

    Да что там, зачастую многие пользователи не догадываются об элементарщине — возможности открытия нескольких сеансов для работы с базой, и просиживают с утра до обеда, ожидая перепроведения квартала или формирования тяжелого отчета, с умным видом вперивши ясны очи в монитор.

    P.S. А строго говоря, применительно к описанной задаче самым быстрым (на практике в десятки, а нередко и в сотни раз, зависит от данных) будет что-то вроде:
    UPDATE _Reference53 SET _Fld9204 = _Fld9204 * (значение коэффициента) ;-))

    Думаю присутствующие здесь эксперты со мной согласятся.

    К сожалению, платформа 1С такие подходы не реализует, и в задачах изменения серьезных объемов данных приходится выкручиваться именно таким образом. Время — дорогой ресурс.

    • Андрей Бурмистров

      Согласен с вами, конечно через прямые запросы SQL было бы быстрее, их кстати тоже можно было бы запустить в несколько потоков, но задача как раз в том, как сделать это исключительно посредством 1С.
      Приведенный способ тоже не самый оптимальный, и одним из финальных заданий тренинга будет оптимизация данного способа.

  • soulseller76

    Добрый день. Организовала многопоточную обработку данных для обработки табличной части документа. По времени оптимизация просто колоссальная. Но! Фоновое задание не возвращает данные обратно. :( Можно ли это как-то обойти? Можно ли как-то организовать возврат значений от фонового задания допустим в таблицу значения\или нескольких таблиц, которые потом объединяться в одну и будут помещены в табличную часть документа?

    • Андрей Бурмистров

      Можно попробовать это сделать через объекты метаданных (например через регистр сведений) или сохранять результат в XML, а из др. сессии его читать.
      Есть еще дугой способ, посмотрите здесь: http://kb.mista.ru/article.php?id=696

      • shachneff

        Так приятно, что мои наработки 2008 года здесь цитируют (я про ссылку на мисту) :)

    • Starik-2005

      Для возврата данных от фонового задания можно воспользоваться в нем методом Сообщить(), для получения данных из потока можно воспользоваться методом ФоновоеЗадание.ПолучитьСообщенияПользователю() (или как-то так — смотрите синтаксис помощник).

  • Светлана

    Добрый день!
    Не получается запустить обработку «ОбновлениеЦеныТовараМногопоточная» из-за ошибки:

    {Форма.Форма.Форма(46)}: Ошибка при вызове метода контекста (Выполнить)
    Задание = ФоновыеЗадания.Выполнить(«ОбщийМодуль1.ОбновитьЦенуТовара», НаборПараметров);
    по причине:
    Менеджер заданий не активен.

    Наверное, я что- то упустила.

    • Татьяна Гужавина

      Добрый день, Светлана!
      У Вас файловая база? Если да, то сделайте тест на клиент-серверном варианте.

  • Тимур

    Добрый день

    Немножко не убедительный пример.

    В данном случае при 8 потоках ускорение всего составило примерно 1,38. Если померить эффективность (отношение ускорения к количеству потоков), то оно составило всего 0,17. Как бы 80% усилий мы потратили на накладные расходы.

    Подход, конечно, хороший. Но нужно выбирать задачу, в которой обработка данных потока займет существенно больше времени, чем работа с накладными расходами. Тогда и цифры будут красивее, да и эффект от усилий ощутимее.

    Еще один момент, когда мы разбиваем задачу на отдельные части, нужно также учитывать равномерную загрузку каждого потока. Иначе наша эффективность также может улететь в трубу.

    • Татьяна Гужавина

      Здравствуйте, Тимур!
      Данная статья представлена просто как пример, что бы показать каким именно образом можно использовать фоновые задания для распараллеливания.
      Можно переписать код по другому, более удобным вам образом, тут главное сам принцип.
      При определении количества потоков нужно так же учитывать возможности вашего оборудования, слишком большое количество потоков может только замедлить выполнение.
      Текущие цифры получены на обычном ноутбуке, да еще и внутри виртуальной машины, на нормальном многопроцессорном сервере с RAID массивом разница между однопоточным и многопоточным выполнением будет отличаться в разы, возможно даже на порядки.
      Такой прием обычно используется для операций которые идут десятки минут или даже часы, там разница будет весьма ощутима.
      Типичная сфера использования, выгрузка большого объема разных данных, например вам надо выгрузить данные за год, о продажах, перемещениях и списаниях в разные текстовые файлы.
      Наверняка в 3 потока (каждый тип данных в своем потоке) выгрузка пройдет быстрее чем в один, даже включая все накладные расходы, но делать 10 потоков тут вряд ли стоит.
      Насчет равномерной загрузки вы абсолютно правы, именно поэтому в моем случае каждое задание (за исключением последнего) обрабатывает одинаковое количество строк.
      Надо понимать, что это не панацея, а лишь один из приемов ускорения и как у любого другого приема тут есть свои плюсы и минусы.

  • Наталья

    Добрый день.
    Я правильно понимаю, что описанным методом нельзя воспользоваться в файловой базе?

    • Евгений Гилев (Мастер-тренер)

      Добрый день!

      В 8.3 в файловой базе работают регламентные задания.
      Однако, по понятным причинам параллельности выполнения операций добиться не получится.
      Скорее будет обратный эффект — замедление обработки, из-за наличия накладных расходов.

  • kasper076

    «ФоновыеЗадания.ОжиданияЗавершения(МассивЗаданий);» Только мне кажется, что тут ошибка синтаксическая?

    • Татьяна Гужавина

      Добрый день!
      Ошибка уже исправлена. Благодарим за внимательность.

  • Евгений Шабалин

    Я понимаю, что мой вопрос выйдет за рамки темы топика, но думаю уместен в рамках поставленной задачи

    Для примера рассмотрим задачу: необходимо провести обновление реквизита «Цена» для всех элементов справочника «Товары». Количество элементов справочника «Товары» равно 100 000.

    Как вы предлагаете организовать транзакцию и возможность «откатиться» в случае если метод будет завершен с ошибкой?

    Поправьте, если я не прав, но фоновые задания будут выполнены как изолированные транзакции (в отдельных сеансах).

    • Татьяна Гужавина

      Здравствуйте, Евгений!
      Все верно, в данном случае возможность «откатится» можно организовать только самостоятельно, это плата за параллельность.
      Здесь надо хорошо подумать, а нужна ли нам вообще возможность отката, например, для данной задачи это не обязательно.
      В случае ошибки мы можем просто запустить обработку повторно и товары у которых цена уже обновилась просто не будут обновлены.
      В остальных случаях можно придумать различные механизмы контроля.

      • Юрий

        «В случае ошибки мы можем просто запустить обработку повторно и товары у которых цена уже обновилась просто не будут обновлены.»
        По какому признаку в данной обработке определяется, что цена уже обновилась?

        • Татьяна Гужавина

          Добрый день, Юрий!
          В прошлый раз мы ответили применительно к другой задаче, когда нужно было обработать товары, только с определенным значением цены.
          Для данной задачи действительно необходимо будет добавить признак того, что цена уже обновилась и обновлять ее больше не следует, т.е. транзакционность надо будет делать самостоятельно.

  • Антон

    В строке кода ФоновыеЗадания.ОжиданияЗавершения(МассивЗаданий);
    ошибка в названии метода.

    • Татьяна Гужавина

      Добрый день, Антон!
      Благодарим за внимательность. Страница исправлена.

  • Алексей

    Большое спасибо за статью!

    У меня вопрос: Запуск фонового задания возможно только через общий модуль или можно запустить из формы или модуля внешней процедуры?

    // запуск фонового задания
    Задание = ФоновыеЗадания.Выполнить(“ОбщийМодуль1.ОбновитьЦенуТовара”, НаборПараметров);

    • Евгений Гилев (Мастер-тренер)

      Добрый день, Алексей!

      Запуск фонового задания возможен из серверного контекста, в т.ч. из управляемой формы.
      При этом метод выполняемый фоновым заданием должен находится в общем неглобальном модуле.

Написать ответ

Ваш email не будет опубликован. Обязательные поля отмечены *