Замер производительности 1С — режим отладчика, который показывает, какие строки кода выполнялись во время операции, сколько раз и сколько времени заняли. Он нужен, когда тормозит конкретное действие: проведение документа, заполнение табличной части, кнопка в обработке, открытие формы. Ниже — как пользоваться замером производительности в конфигураторе 8.3, как дойти до медленной строки, в том числе в серверном коде, и пример обработки, которую после замера ускорили в разы.
Замеры производительности 1С 8.3 работают в типовых и самописных конфигурациях, на управляемых и обычных формах и правки кода не требуют.
Замер включается только во время отладки: запустите 1С:Предприятие из конфигуратора (F5) или подключитесь к клиенту, запущенному в режиме отладки, через «Отладка → Подключение».
Паузы между действиями в результат не входят: общее время замера — сумма промежутков, когда выполнялся код конфигурации. Зато входит всё выполненное: открытие форм, обработчики ожидания, соседние команды, поэтому замер включают прямо перед операцией. Если тормозит открытие формы, включите замер до открытия — попадут ПриСозданииНаСервере и ПриОткрытии.
Входите под пользователем, у которого тормозит: ограничения доступа на уровне записей усложняют запросы, а для полных прав не применяются. Подробнее — в статье о правах доступа 1С.
Результат — таблица со ссылками на строки модулей. Колонки в конфигураторе 8.3:
| Колонка | Что показывает |
|---|---|
| Модуль, Номер строки, Строка | Где находится строка и её текст |
| Кол. | Сколько раз строка выполнилась за время замера |
| Врем. | Суммарное время выполнения строки, в секундах |
| %(Врем.) | Доля от общего времени замера; за 100% принимается время выполнения кода на клиенте |
| Клиент, Сервер | Пиктограмма показывает, где выполнялась строка |
| Обр. сервер | Строка привела к вызову сервера — напрямую или через вызванную клиентскую процедуру |
Флажок «Для вызовов процедур и функций включать время выполнения» по умолчанию установлен: время строки с вызовом процедуры включает всё, что выполнялось внутри неё. Если флажок снять, останется «чистое время» — только операторы самой строки, а колонки времени сменят названия. Состояние флажка запоминается между сеансами.
Щелчок по заголовку колонки сортирует таблицу, двойной щелчок по строке открывает модуль. Результат виден и в редакторе модуля: у строк появляется колонка с числом выполнений и долей времени, двойной щелчок по ней возвращает к строке в окне замера.
Запрос.Выполнить(), Записать(), обращение к реквизиту через точку.| Что видно в замере | Причина | Что делать |
|---|---|---|
| Строка с запросом, «Кол.» равно числу строк таблицы | Запрос в цикле | Один запрос на весь набор данных |
СтрокаТоваров.Номенклатура.Артикул с тысячами выполнений | Чтение объекта по ссылке на каждой итерации | Получить реквизит в том же запросе |
| Клиентская строка в цикле с отметкой «Обр. сервер» | Вызов сервера на каждую строку | Собрать данные и вызвать сервер один раз |
| «Кол.» = 1, но большая доля времени | Тяжёлый запрос или запись | Разбирать текст запроса, индексы, блокировки |
В последнем случае разбирайте условия в параметрах виртуальных таблиц и соединения — см. статью об оптимизации запросов. Медленную строку в типовом модуле исправляйте расширением, а не снятием с поддержки.
Обработка готовит таблицу для выгрузки на сайт: товар, артикул, единица хранения и остаток на складе. Чем больше ассортимент, тем дольше заполнение. Код до замера — модуль объекта обработки, объекты УТ 11 и КА 2:
// Модуль объекта обработки «Остатки для выгрузки» (объекты УТ 11 и КА 2).
// Было: так код выглядел до замера. Товары - таблица значений с колонками
// Номенклатура, Артикул, ЕдиницаИзмерения, Остаток; Склад - СправочникСсылка.Склады.
Процедура ЗаполнитьОстаткиПострочно(Товары, Склад) Экспорт
Для Каждого СтрокаТоваров Из Товары Цикл
// Реквизиты «через точку»: для каждого товара - чтение объекта из базы
СтрокаТоваров.Артикул = СтрокаТоваров.Номенклатура.Артикул;
СтрокаТоваров.ЕдиницаИзмерения = СтрокаТоваров.Номенклатура.ЕдиницаИзмерения;
// Запрос в цикле: обращений к СУБД столько же, сколько строк в таблице
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(СУММА(Остатки.ВНаличииОстаток), 0) КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| ,
| Номенклатура = &Номенклатура
| И Склад = &Склад) КАК Остатки";
Запрос.УстановитьПараметр("Номенклатура", СтрокаТоваров.Номенклатура);
Запрос.УстановитьПараметр("Склад", Склад);
Выборка = Запрос.Выполнить().Выбрать();
СтрокаТоваров.Остаток = ?(Выборка.Следующий(), Выборка.Остаток, 0);
КонецЦикла;
КонецПроцедуры
В замере такой код даёт узнаваемую картину: почти всё время делят строки внутри цикла — Запрос.Выполнить() и чтение артикула через точку, а «Кол.» у них равно числу товаров. Одно выполнение занимает миллисекунды, но каждое — отдельное обращение к базе, и их тысячи.
Исправленный вариант — один пакетный запрос на всю таблицу:
// Модуль объекта обработки (продолжение). Стало: один пакетный запрос
// на всю таблицу вместо запроса и чтения объекта на каждую строку.
// Параметры те же, что у ЗаполнитьОстаткиПострочно.
Процедура ЗаполнитьОстаткиОднимЗапросом(Товары, Склад) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| СУММА(Остатки.ВНаличииОстаток) КАК Остаток
|ПОМЕСТИТЬ ВТ_Остатки
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| ,
| Склад = &Склад
| И Номенклатура В (&СписокНоменклатуры)) КАК Остатки
|
|СГРУППИРОВАТЬ ПО
| Остатки.Номенклатура
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
| ЕСТЬNULL(ВТ_Остатки.Остаток, 0) КАК Остаток
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки
| ПО Номенклатура.Ссылка = ВТ_Остатки.Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&СписокНоменклатуры)";
Запрос.УстановитьПараметр("Склад", Склад);
Запрос.УстановитьПараметр("СписокНоменклатуры", Товары.ВыгрузитьКолонку("Номенклатура"));
// Результат - в соответствие: строка находится по ссылке без перебора
Данные = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Данные.Вставить(Выборка.Номенклатура, Новый Структура("Артикул, ЕдиницаИзмерения, Остаток",
Выборка.Артикул, Выборка.ЕдиницаИзмерения, Выборка.Остаток));
КонецЦикла;
Для Каждого СтрокаТоваров Из Товары Цикл
ДанныеТовара = Данные.Получить(СтрокаТоваров.Номенклатура);
Если ДанныеТовара <> Неопределено Тогда
ЗаполнитьЗначенияСвойств(СтрокаТоваров, ДанныеТовара);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Номенклатура В (&СписокНоменклатуры) стоит в параметрах виртуальной таблицы Остатки: остатки считаются только по нужным товарам, а не по всему складу.СГРУППИРОВАТЬ ПО гарантирует одну строку на товар, даже если остаток разнесён по характеристикам, сериям и назначениям: такие измерения у регистра ТоварыНаСкладах есть.Соответствие находит данные товара по ссылке без перебора; повторяющиеся товары заполнятся одинаково.Повторный замер: вместо тысяч выполнений — одна строка Запрос.Выполнить() с «Кол.» = 1. Для десятков тысяч позиций список лучше передать временной таблицей (см. статью о таблице значений в запросе), другие приёмы — в статье о запросе в цикле. Запрос удобно проверить отдельно в консоли запросов.
Ускорение меряйте вне отладчика: под ним код работает медленнее. Стенд прогоняет оба варианта на одних данных:
// Модуль объекта обработки (продолжение): стенд «до/после» на одних данных.
// Запускать вне отладчика, из серверной процедуры формы обработки:
// Сообщить(РеквизитФормыВЗначение("Объект").СравнитьВарианты(Объект.Склад, 2000));
Функция СравнитьВарианты(Склад, КоличествоТоваров = 1000) Экспорт
Товары = НоваяТаблицаТоваров(КоличествоТоваров);
// Прогрев: первый проход по данным медленнее последующих, его не считаем
ЗаполнитьОстаткиОднимЗапросом(Товары, Склад);
Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
ЗаполнитьОстаткиПострочно(Товары, Склад);
Построчно = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
ЗаполнитьОстаткиОднимЗапросом(Товары, Склад);
ОднимЗапросом = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Возврат СтрШаблон("Строк: %1. Построчно: %2 мс, одним запросом: %3 мс, быстрее в %4 раза",
Формат(Товары.Количество(), "ЧН=0; ЧГ=0"),
Формат(Построчно, "ЧН=0; ЧГ=0"),
Формат(ОднимЗапросом, "ЧН=0; ЧГ=0"),
Формат(Построчно / Макс(ОднимЗапросом, 1), "ЧДЦ=1; ЧН=0"));
КонецФункции
// Тестовые данные: первые N элементов номенклатуры без групп и помеченных на удаление.
Функция НоваяТаблицаТоваров(Количество)
Товары = Новый ТаблицаЗначений;
Товары.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Товары.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка"));
Товары.Колонки.Добавить("ЕдиницаИзмерения");
Товары.Колонки.Добавить("Остаток", Новый ОписаниеТипов("Число"));
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1000
| Номенклатура.Ссылка КАК Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа
| И НЕ Номенклатура.ПометкаУдаления";
Запрос.Текст = СтрЗаменить(Запрос.Текст, "ПЕРВЫЕ 1000",
"ПЕРВЫЕ " + Формат(Макс(Количество, 1), "ЧГ=0"));
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Товары.Добавить().Номенклатура = Выборка.Номенклатура;
КонецЦикла;
Возврат Товары;
КонецФункции
Первый проход прогревочный: по холодным данным медленнее любой вариант. Итоговое число и есть ответ «в разы или нет» для вашей базы и объёма данных.
В файловой базе серверный код выполняется в процессе клиента и при запуске из конфигуратора попадает в замер без настроек. В клиент-серверной базе серверные строки появятся, только если сервер 1С работает в режиме отладки (агент кластера запущен с ключом -debug) и серверный предмет отладки подключён в «Отладка → Подключение» — см. статью об отладке на сервере 1С.
Без серверной отладки клиентская строка с вызовом сервера всё равно покажет полное время вызова и отметку в «Обр. сервер», но содержимое вызова не раскроется. Флажки «Клиент» и «Сервер» в окне результатов отбирают строки по месту выполнения; если они неактивны, серверный предмет не отлаживается и сервер в замер не попал.
Сортировка по «Обр. сервер» собирает вместе строки, которые вызывали сервер. Строка из клиентского цикла среди них означает вызов на каждой итерации со своими накладными расходами. Та же задача в форме обработки:
// Модуль формы обработки. На форме: табличная часть Объект.Товары
// (Номенклатура, Остаток), реквизит Объект.Склад и две команды.
// Было: вызов сервера на каждую строку. В замере у строки с вызовом
// отметка в колонке «Обр. сервер», а «Кол.» равно числу строк.
&НаКлиенте
Процедура ОбновитьОстаткиПострочно(Команда)
Для Каждого СтрокаТоваров Из Объект.Товары Цикл
СтрокаТоваров.Остаток = ОстатокТовара(СтрокаТоваров.Номенклатура, Объект.Склад);
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОстатокТовара(Номенклатура, Склад)
Список = Новый Массив;
Список.Добавить(Номенклатура);
Остаток = ОстаткиТоваров(Список, Склад).Получить(Номенклатура);
Возврат ?(Остаток = Неопределено, 0, Остаток);
КонецФункции
// Стало: один вызов сервера на всю таблицу.
&НаКлиенте
Процедура ОбновитьОстатки(Команда)
Список = Новый Массив;
Для Каждого СтрокаТоваров Из Объект.Товары Цикл
Список.Добавить(СтрокаТоваров.Номенклатура);
КонецЦикла;
Остатки = ОстаткиТоваров(Список, Объект.Склад);
Для Каждого СтрокаТоваров Из Объект.Товары Цикл
Остаток = Остатки.Получить(СтрокаТоваров.Номенклатура);
СтрокаТоваров.Остаток = ?(Остаток = Неопределено, 0, Остаток);
КонецЦикла;
КонецПроцедуры
// Остатки по списку товаров: Соответствие «товар - остаток».
// Знач: параметры не передаются обратно на клиент после вызова.
&НаСервереБезКонтекста
Функция ОстаткиТоваров(Знач Список, Знач Склад)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| СУММА(Остатки.ВНаличииОстаток) КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| ,
| Склад = &Склад
| И Номенклатура В (&Список)) КАК Остатки
|
|СГРУППИРОВАТЬ ПО
| Остатки.Номенклатура";
Запрос.УстановитьПараметр("Склад", Склад);
Запрос.УстановитьПараметр("Список", Список);
Остатки = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Остатки.Вставить(Выборка.Номенклатура, Выборка.Остаток);
КонецЦикла;
Возврат Остатки;
КонецФункции
ОстатокТовара(...) «Кол.» равно числу строк и есть отметка в «Обр. сервер».Соответствие — оба типа передаются между клиентом и сервером.Знач у параметров серверной функции избавляет от обратной передачи параметров на клиент.Код фонового или регламентного задания выполняется в отдельном сеансе. Проще всего замерить его, вызвав тот же метод из обработки в текущем сеансе, — приём есть в статье об отладке 1С.
Число серверных вызовов и объём данных видно и без отладчика. В 1С:Предприятии установите флажок «Отображать показатели производительности» в «Сервис → Параметры» или запустите клиент с ключом /DisplayPerformance. В правом нижнем углу появится кнопка, которая открывает окно со счётчиками «Текущие» и «Накопленные»; из контекстного меню этого окна открывается «История вызовов сервера» с колонками «Начало», «Вызовов», «Время», «Отправлено», «Принято». После правки команда обновления остатков должна давать один вызов сервера, а не вызов на строку.
-debug или не подключён серверный предмет отладки; признак — неактивные флажки «Клиент» и «Сервер».Товары или реквизита Склад.С флажком полного времени время вложенной процедуры учитывается и у строки с вызовом, и у строк внутри неё. Чтобы увидеть вклад отдельных операторов, снимите флажок.
Да, результат сохраняется в файл. Если открыть два замера, в редакторе модуля будут колонки обоих, а подсказка над колонкой покажет, из какого файла данные.
Повторите операцию на копии: файловой, где серверный код попадёт в замер без настройки кластера, или на тестовом сервере с отладкой. На файловой копии циклы и лишние вызовы видны так же, но время отдельных запросов может отличаться. Плавающие тормоза ловят технологическим журналом — см. настройку техжурнала.
Подсистема БСП меряет время ключевых операций у пользователей рабочей базы и пишет его в регистр «Замеры времени» для оценки по APDEX. В УТ 11.4 она включается флажком «Оценка производительности» в «Администрирование → Обслуживание», а в коде замер ставят парой ОценкаПроизводительности.НачатьЗамерВремени() и ОценкаПроизводительности.ЗакончитьЗамерВремени(КлючеваяОперация, ВремяНачала). Подсистема отвечает на вопрос «что и у кого тормозит», замер отладчика — «на какой строке».
Есть: панель «Замер производительности» открывается после завершения замера. Колонки там называются «Кол-во вызовов», «Полное время (с)» и «Чистое время (с)», результаты хранятся в рабочей области.
Замер показывает, где тормозит, но переписывать узкое место в проведении, обмене или типовом механизме нужно аккуратно, обычно через расширение. Если на это нет времени, приложите файл замера к задаче: профильная услуга — «1С тормозит», разместить задачу можно бесплатно, ответ с оценкой — в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.