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

Но не будем торопиться с выводами, в правиле №2 я докажу, что пользоваться функциями БСП намного лучше.
Правило №2. Если нужно получить сразу много значений, то получай их одним запросом.
Рассмотрим случай, когда нам нужно вывести наименования по всему справочнику номенклатура. И сделаем это тремя способами:
- Через точку по ссылке,
- Через значение реквизита объекта,
- Получение одним запросом.
#Область СообщениеЧерезТочку
&НаКлиенте
Процедура СообщитьЧерезТочку(Команда)
УИДЗамераЧерезТочку = ОценкаПроизводительностиКлиент.ЗамерВремени("СообщитьЧерезТочку");
СообщитьЧерезТочкуНаСервере();
ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамераЧерезТочку);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура СообщитьЧерезТочкуНаСервере()
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Наименование = Выборка.Ссылка.Наименование;
КонецЦикла;
КонецПроцедуры
#КонецОбласти
#Область СообщениеЧерезЗначениеРеквизита
&НаКлиенте
Процедура СообщитьЧерезЗначениеРеквизита(Команда)
УИДЗамераЧерезЗначениеРеквизита = ОценкаПроизводительностиКлиент.ЗамерВремени("СообщитьЧерезЗначениеРеквизита");
СообщитьЧерезЗначениеРеквизитаНаСервере();
ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамераЧерезЗначениеРеквизита);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура СообщитьЧерезЗначениеРеквизитаНаСервере()
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Наименование = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Выборка.Ссылка, "Наименование").Наименование;
КонецЦикла;
КонецПроцедуры
#КонецОбласти
#Область СообщениеНапрямую
&НаКлиенте
Процедура СообщитьНапрямую(Команда)
УИДЗамераНапрямую = ОценкаПроизводительностиКлиент.ЗамерВремени("СообщитьНапрямую");
СообщитьНапрямуюНаСервере();
ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамераНапрямую);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура СообщитьНапрямуюНаСервере()
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Наименование = Выборка.Наименование;
КонецЦикла;
КонецПроцедуры
#КонецОбласти
Но здесь нужно быть очень аккуратным с замерами, чтобы не попасть в заблуждение о том, что обращение через точку может быть быстрее. Начнем с того, что стоит дождаться очистки кэша СУБД, чтобы СУБД производила физическое, а не логическое считывание данных.
Так как при быстром нажатие кнопки для инициирования алгоритма и замера времени выполнения, приведет к тому, что данные произойдет логическое считывание данных, временно помещенной таблицы в оперативную память. Поэтому между нажатиями делаем паузу в 10-15 секунд.
Вот кстати интересная выдержка из официальной документации к MS SQL
The I/O from an instance of the SQL Server Database Engine includes logical and physical reads. A logical read occurs every time the Database Engine requests a page from the buffer cache. If the page is not currently in the buffer cache, a physical read first copies the page from disk into the cache.
То есть, мы делаем вывод, что MS SQL копирует страницу (часть или всю таблицу) в оперативную память и до тех пор, пока считает данные в оперативной памяти валидными, считывает их из оперативной памяти.

И вот здесь мы уже видим, что при хоть сколько-нибудь значительных объемах данных, получение реквизита через точку, нервно покуривает в сторонке.
Но получение нужных нам данных напрямую сразу из множества объектов, может дать огромный прирост в производительности. В нашем случае в 67 раз.