Сегодня начну цикл статей по вопросам базовых оптимизаций кода. И начнем, пожалуй, с самых банальных недочетов в коде, которые сплошняком допускают начинающие 1С программисты.
Дисклеймер. Всегда важно понимать, что за задача стоит перед программистом. Порой важнее сделать задачу быстрее, чем качественнее. А если заказчик – это микропредприятие, то они вероятно даже не заметят отличия в работе оптимального кода от неоптимального.
Правило №1. Старайтесь не получать значения реквизитов через точку.
Плохо:
1 |
Наименование = Объект.Номенеклатура.Наименование; |
Хорошо:
1 2 3 4 5 |
Наименование = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Объект.Номенеклатура, "Наименование"); // или ЗначенияРеквизитов = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Объект.Номенеклатура, "Код, Наименование"); Код = ЗначенияРеквизитов.Код; Наименование = ЗначенияРеквизитов.Наименование; |
При обращении к ссылке через точку, выполняется неявный запрос к базе данных, который получает все реквизиты и табличные части и временно помещает полученные значения в кэш СУБД. А методы БСП модуля ОбщегоНазначения. ЗначенияРеквизитовОбъекта(), . ЗначениеРеквизитаОбъекта(), .ЗначенияРеквизитовОбъектов(), . ЗначениеРеквизитаОбъектов() помогают получить данных только конкретных реквизитов объекта, тем самым уменьшая нагрузку на СУБД.
Для того, чтобы посмотреть на это более наглядно, проведем эксперимент с замером по методике APDEX
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
#Область ПолучениеРеквизитаЧерезТочку &НаКлиенте Процедура ПолучитьРеквизитЧерезТочку(Команда) УИДЗамера = ОценкаПроизводительностиКлиент.ЗамерВремени("ЧерезТочку"); ЧерезТочку(Объект.Номенеклатура); ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамера); НазначитьСлучайнуюНоменклатуру(); КонецПроцедуры &НаСервереБезКонтекста Процедура ЧерезТочку(Номенеклатура) Код = Номенеклатура.Код; Наименование = Номенеклатура.Наименование; КонецПроцедуры #КонецОбласти #Область ПолучениеРеквизитаЧерезЗначениеРеквизита &НаКлиенте Процедура ПолучитьРеквизитЧерезЗначениеРеквизита(Команда) УИДЗамера = ОценкаПроизводительностиКлиент.ЗамерВремени("ЧерезЗначениеРеквизитов"); ЧерезЗначениеРеквизитов(Объект.Номенеклатура); ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамера); НазначитьСлучайнуюНоменклатуру(); КонецПроцедуры &НаСервереБезКонтекста Процедура ЧерезЗначениеРеквизитов(Номенеклатура) РеквизитыОбъекта = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Номенеклатура, "Код, Наименование"); Код = РеквизитыОбъекта.Код; Наименование = РеквизитыОбъекта.Наименование; КонецПроцедуры #КонецОбласти &НаСервере Функция НазначитьСлучайнуюНоменклатуру() Если КэшНоменклатуры.Количество() = 0 Тогда Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | Номенклатура.Ссылка КАК Ссылка |ИЗ | Справочник.Номенклатура КАК Номенклатура |ГДЕ | Номенклатура.ЭтоГруппа = ЛОЖЬ"; РезультатЗапроса = Запрос.Выполнить(); КэшНоменклатуры.Загрузить(РезультатЗапроса.Выгрузить()); КонецЕсли; КоличествоРезультатов = КэшНоменклатуры.Количество(); ГСЧ = Новый ГенераторСлучайныхЧисел; СлучайныйИндекс = ГСЧ.СлучайноеЧисло(0, КоличествоРезультатов - 1); Объект.Номенеклатура = КэшНоменклатуры[СлучайныйИндекс].Ссылка; КонецФункции |
Дополнительно, после каждого замера будем менять номенклатуру случайным образом. Произведем замер по методике APDEX и…. не сказать, что разница сколько-нибудь существенная…

Но не будем торопиться с выводами, в правиле №2 я докажу, что пользоваться функциями БСП намного лучше.
Правило №2. Если нужно получить сразу много значений, то получай их одним запросом.
Рассмотрим случай, когда нам нужно вывести наименования по всему справочнику номенклатура. И сделаем это тремя способами:
- Через точку по ссылке,
- Через значение реквизита объекта,
- Получение одним запросом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
#Область СообщениеЧерезТочку &НаКлиенте Процедура СообщитьЧерезТочку(Команда) УИДЗамераЧерезТочку = ОценкаПроизводительностиКлиент.ЗамерВремени("СообщитьЧерезТочку"); СообщитьЧерезТочкуНаСервере(); ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамераЧерезТочку); КонецПроцедуры &НаСервереБезКонтекста Процедура СообщитьЧерезТочкуНаСервере() Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | Номенклатура.Ссылка КАК Ссылка |ИЗ | Справочник.Номенклатура КАК Номенклатура"; Выборка = Запрос.Выполнить().Выбрать(); Пока Выборка.Следующий() Цикл Наименование = Выборка.Ссылка.Наименование; КонецЦикла; КонецПроцедуры #КонецОбласти #Область СообщениеЧерезЗначениеРеквизита &НаКлиенте Процедура СообщитьЧерезЗначениеРеквизита(Команда) УИДЗамераЧерезЗначениеРеквизита = ОценкаПроизводительностиКлиент.ЗамерВремени("СообщитьЧерезЗначениеРеквизита"); СообщитьЧерезЗначениеРеквизитаНаСервере(); ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамераЧерезЗначениеРеквизита); КонецПроцедуры &НаСервереБезКонтекста Процедура СообщитьЧерезЗначениеРеквизитаНаСервере() Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | Номенклатура.Ссылка КАК Ссылка |ИЗ | Справочник.Номенклатура КАК Номенклатура"; Выборка = Запрос.Выполнить().Выбрать(); Пока Выборка.Следующий() Цикл Наименование = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Выборка.Ссылка, "Наименование").Наименование; КонецЦикла; КонецПроцедуры #КонецОбласти #Область СообщениеНапрямую &НаКлиенте Процедура СообщитьНапрямую(Команда) УИДЗамераНапрямую = ОценкаПроизводительностиКлиент.ЗамерВремени("СообщитьНапрямую"); СообщитьНапрямуюНаСервере(); ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамераНапрямую); КонецПроцедуры &НаСервереБезКонтекста Процедура СообщитьНапрямуюНаСервере() Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | Номенклатура.Наименование КАК Наименование |ИЗ | Справочник.Номенклатура КАК Номенклатура"; Выборка = Запрос.Выполнить().Выбрать(); Пока Выборка.Следующий() Цикл Наименование = Выборка.Наименование; КонецЦикла; КонецПроцедуры #КонецОбласти |
Но здесь нужно быть очень аккуратным с замерами, чтобы не попасть в заблуждение о том, что обращение через точку может быть быстрее. Начнем с того, что стоит дождаться очистки кэша СУБД, чтобы СУБД производила физическое, а не логическое считывание данных.
Так как при быстром нажатие кнопки для инициирования алгоритма и замера времени выполнения, приведет к тому, что данные произойдет логическое считывание данных, временно помещенной таблицы в оперативную память. Поэтому между нажатиями делаем паузу в 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 раз.