Оптимизация кода 1С: простые приемы

20.09.16

База данных - HighLoad оптимизация

Речь пойдет о проблемах с производительностью, которые могут возникнуть из-за кода наподобие этого: Для каждого СтрокаТовары из Товары Цикл Количество = СтрокаТовары.Количество * СтрокаТовары.Коэффициент / СтрокаТовары.Номенклатура.ЕдиницаХраненияОстатков.Коэффициент; КонецЦикла; Дело в том, что этот, с виду ни чем не примечательный код, содержит запрос в цикле.
Для каждого СтрокаТовары из Товары  Цикл
    //.....................
    Количество = СтрокаТовары.Количество * СтрокаТовары.Коэффициент / СтрокаТовары.Номенклатура.ЕдиницаХраненияОстатков.Коэффициент;
    //......................
КонецЦикла;

В чем проблема?

Когда мы обращаемя к реквизиту объекта базы "через точку", программа выполняет запрос к базе, чтобы вытащить из базы значение этого реквизита. Строго говоря, это происходит не всегда. Известно, что при чтении одного реквизита считываются значения всех реквизитов этого объекта. Считанные значения кэшируется, но кэш имеет ограниченный размер, и ограниченное время жизни.

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

Кэширование, которое выполняет платформа, не всегда спасает: если в цикле каждый раз считывается новая номенклатура, то ее еще нет в кэше, и мы каждый раз несем те самые накладные расходы.

Я лично сталкивался с ситуацией, когда такой вот неаккуратный код замедлял проведение размещение заказа покупателя в 10 раз (справедливости разди, в заказе было 800 строк).

Естественно, нет необходимости переписывать все такие случаи. Если есть потребность оптимизировать какую-то операцию, то надо сначала сделать замер производительности (такая функция есть в конфигураторе, в меню "Отладка"). В нашем случае, мы с топе увидим простую операцию, которая выполняется многократно и съедает много времени. Не забудьте, что замер надо делать на клиент-серверной базе, а не на файловой копии.

Исправляем

Т.к. проблема в накладных расходах, то считав все необходимые данные одним запросом, мы многократно снизим накладные расходы и решим проблему. Первое что приходит в голову, это написать что-то наподобие такого кода:

//....................
Запрос.Текст = 
//............
|НакладнаяТовары.Количество КАК Количество,
|НакладнаяТовары.Коэффициент КАК Коэффициент,
|НакладнаяТовары.Номенклатура.БазоваяЕдиницаИзмерения.Коэффициент КАК НоменклатураБазоваяЕдиницаИзмеренияКоэффициент,
//.........
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    //.................
    Количество = Выборка.Количество*Выборка.Коэффициент*Выборка.НоменклатураБазоваяЕдиницаИзмеренияКоэффициент;
    //.................
КонецЦикла;


Это решит проблему, только мы можем столкнуться с некоторыми неудобствами. Во-первых нам придется везде заменить СтрокаТовары на Выборка. Хорошо, обзовем выборку "СтрокаТовары" (что не очень красиво) или выгрузим запрос в таблицу значений (что уже лучше).

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

Поразмыслив, пишем более приятный и удобный вариант:

Запрос.Текст = 
"ВЫБРАТЬ
|Номенклатура.Ссылка,
|Номенклатура.ЕдиницаХраненияОстатков.Коэффициент КАК ЕдиницаХраненияОстатковКоэффициент
|ИЗ
|Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|Номенклатура.Ссылка В (&МассивНоменклатуры)";
Запрос.Параметры.Вставить("МассивНоменклатуры", Товары.ВыгрузитьКолонку("Номенклатура"));
тзРеквизитовНоменклатуры = Запрос.Выполнить().Выгрузить();
тзРеквизитовНоменклатура.Индексы.Добавить("Ссылка");
Для каждого СтрокаТовары из Товары Цикл
    СтрокаНоменклатуры = тзРеквизитовНоменклатуры.Найти(СтрокаТовары.Номенклатура, "Ссылка");
    Если СтрокаНоменклатуры = Неопределено Тогда
        ВызватьИсключение "Не найдена номенклатура"+СтрокаТовары.Номенклатура;
    КонецЕсли;
    //...................
    Количество = СтрокаТовары.Количество*СтрокаТовары.Коэффициент*СтрокаНоменклатуры.ЕдиницаХраненияОстатковКоэффициент;
    //....................
КонецЦикла;

Мы внесли в код локальные измененения:

  1. Перед циклом вытащили из базы то, что нам нужно, и сложили в таблицу значений.
  2. В начале цикла нашли в этой таблице нужную строку.
  3. Везде, где есть обращение к полям "через точку", заменили на обращение к строке таблицы значений.

Необходимость добавлять индекс в таблицу значений - вопрос спорный, я рассуждаю так:

  1. Индексирование маленькой тз не даст заметной задержки. Даже если мы потеряем больше времени, чем сэкономим - мы не гонимся за рекордами, нам важно, чтобы пользователю было комфортно.
  2. Отсутствие индекса в большой тз может дать заметную задержку. Справедливости ради, таблица должна быть действительно большой, начиная с 1000 записей, если я ничего не путаю.

Немного сложнее

Бывает, что неявный микрозапрос, который многократно вызывается, закопан глубоко в общих модулях, да еще используется по всей конфигурации. Что делать в этом случае? Рассмотрим пример:

Функция ПолучитьКурсВалюты(Валюта, ДатаКурса) Экспорт
	
	Структура = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(?(ДатаКурса = Дата('00010101'),ТекущаяДата(),ДатаКурса), Новый Структура("Валюта", Валюта));
	Возврат Структура;
	
КонецФункции // ПолучитьКурсВалюты()

Если многократный вызов этой функции съедает время наших пользователей, мы можем ее усовершенствовать:

Функция ПолучитьКурсВалюты(Валюта, ДатаКурса, СтруктураКэш = Неопределено) Экспорт
	
	// добавили тут
	лкДата = ?(ДатаКурса = Дата('00010101'),ТекущаяДата(),ДатаКурса);
	Если СтруктураКэш <> Неопределено Тогда
		Если НЕ СтруктураКэш.Свойство("КэшКурсов") Тогда
			СтруктураКэш.Вставить("КэшКурсов", Новый ТаблицаЗначений);
			СтруктураКэш.КэшКурсов.Колонки.Добавить("Валюта");
			СтруктураКэш.КэшКурсов.Колонки.Добавить("Период");
			СтруктураКэш.КэшКурсов.Колонки.Добавить("Курс");
			СтруктураКэш.КэшКурсов.Колонки.Добавить("Кратность");
		КонецЕсли;
		СтрокиКэш = СтруктураКэш.КэшКурсов.НайтиСтроки(Новый Структура("Валюта, Период", Валюта, лкДата));
		Если СтрокиКэш.Количество() > 0 Тогда
			Возврат Новый Структура("Курс, Кратность", СтрокиКэш[0].Курс, СтрокиКэш[0].Кратность);
		КонецЕсли;
	КонецЕсли;
	
	Структура = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(лкДата, Новый Структура("Валюта", Валюта));
	
	// и тут
	Если СтруктураКэш <> Неопределено Тогда
		НоваяСтрока = СтруктураКэш.КэшКурсов.Добавить();	
		НоваяСтрока.Валюта = Валюта;
		НоваяСтрока.Период = лкДата;
		НоваяСтрока.Курс = Структура.Курс;
		НоваяСтрока.Кратность = Структура.Кратность;
	КонецЕсли; 
	
	Возврат Структура;
	
КонецФункции // ПолучитьКурсВалюты()

Теперь нам надо объявить переменную СтруктураКэш в модуле того объекта, который оптимизируем. Иницилизировать ее не надо, пусть будет Неопределено. Будем передавать эту переменную во все процедуры, которые в конечном итоге вызывают ПолучитьКурсВалюты() - отладчик, замер производительности и стек вызовов в помощь. Придется добавить в типовые процедуры параметр СтруктураКэш = Неопределено, ничего не поделаешь. Но в общем и целом изменений в типовом коде получится немного, а не затронутые нами участки типового кода будут работать по-старому.

Вывод

Оптимизация работы системы не всегда требует чего-то сложного, наподобие анализа блокировок или планов запроса. Часто простой замер производительности и чистка запросов в цикле дает более, чем ощутимый результат.

См. также

HighLoad оптимизация Программист Платформа 1С v8.3 Бесплатно (free)

Метод очень медленно работает, когда параметр приемник содержит намного меньше свойств, чем источник.

06.06.2024    9262    Evg-Lylyk    61    

44

HighLoad оптимизация Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Анализ простого плана запроса. Оптимизация нагрузки на ЦП сервера СУБД используя типовые индексы.

13.03.2024    5097    spyke    28    

49

HighLoad оптимизация Программист Платформа 1С v8.3 Бесплатно (free)

Оказывается, в типовых конфигурациях 1С есть, что улучшить!

13.03.2024    7577    vasilev2015    20    

42

HighLoad оптимизация Инструменты администратора БД Системный администратор Программист Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Обработка для простого и удобного анализа настроек, нагрузки и проблем с SQL сервером с упором на использование оного для 1С. Анализ текущих запросов на sql, ожиданий, конвертация запроса в 1С и рекомендации, где может тормозить.

2 стартмани

15.02.2024    12429    241    ZAOSTG    82    

115

HighLoad оптимизация Системный администратор Программист Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Принимать, хранить и анализировать показания счетчиков (метрики) в базе 1С? Почему бы нет? Но это решение быстро привело к проблемам с производительностью при попытках построить какую-то более-менее сложную аналитику. Переход на PostgresSQL только временно решил проблему, т.к. количество записей уже исчислялось десятками миллионов и что-то сложное вычислить на таких объемах за разумное время становилось все сложнее. Кое-что уже практически невозможно. А что будет с производительностью через пару лет - представить страшно. Надо что-то предпринимать! В этой статье поделюсь своим первым опытом применения СУБД Clickhouse от Яндекс. Как работает, что может, как на нее планирую (если планирую) переходить, сравнение скорости работы, оценка производительности через пару лет, пример работы из 1С. Все это приправлено текстами запросов, кодом, алгоритмами выполненных действий и преподнесено вам для ознакомления в этой статье.

1 стартмани

24.01.2024    5671    glassman    18    

40

HighLoad оптимизация Программист Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Встал вопрос: как быстро удалить строки из ТЗ? Рассмотрел пять вариантов реализации этой задачи. Сравнил их друг с другом на разных объёмах данных с разным процентом удаляемых строк. Также сравнил с выгрузкой с отбором по структуре.

09.01.2024    14028    doom2good    49    

71
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. pbazeliuk 1969 20.09.16 16:11 Сейчас в теме
Количество = Выборка.Количество*Выборка.Коэффициент*Выборка.НоменклатураБазоваяЕдиницаИзмеренияКоэффициент;

Может правильнее это уже посчитать в запросе?

Номенклатура.Ссылка В (&МассивНоменклатуры)

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

Если вас беспокоит микро-запрос, сделайте вручную индекс в базе данных под запрос.
Так же можно включить "Повторно использование возвращаемых значений" в общем модуле, то есть создать свой ОбщийМодульПовтИсп (повторного использования) и выполнять переадресацию на него из базового модуля.
Andreeei; nekit_rdx; user600203_7377360; sergelemon; abasovit; Vlad_M; CSiER; Zhilyakovdr; +8 Ответить
9. Frogger1971 21.09.16 13:24 Сейчас в теме
(1) pbazeliuk,
Если вас беспокоит микро-запрос, сделайте вручную индекс в базе данных под запрос.

снова временная таблица, а учитывая, если ведущие параметры не индексируются - будем иметь дополнительный тормоз
10. pbazeliuk 1969 21.09.16 13:37 Сейчас в теме
(9) Frogger1971, речь идет о другом, для MS SQL, например, будет что-то на подобие такой команды:
CREATE INDEX [missing_index_294875_294874__InfoRg4215] ON [yt11].[dbo].[_InfoRg4215] ([_Fld4217RRef], [_Fld11175]) INCLUDE ([_Fld4216_TYPE], [_Fld4216_RTRef], [_Fld4216_RRRef], [_Fld4218_TYPE], [_Fld4218_L], [_Fld4218_N], [_Fld4218_T], [_Fld4218_S], [_Fld4218_RTRef], [_Fld4218_RRRef])
11. DenisCh 21.09.16 14:12 Сейчас в теме
(10) pbazeliuk, и попрощайся с этим индексом после реструктуризации ))
14. pbazeliuk 1969 21.09.16 15:35 Сейчас в теме
(11) DenisCh, каждое блюдо нужно уметь готовить.
Там где сейчас работаю, реструктуризация делась недавно впервые за 4 года, что бы настроить сжатие DB на лету, плюс полное журналирование. Рейд на SSD дисках под рабочую DB заменить не было возможности. Следующая реструктуризация неизвестно когда будет, возможно, через пару лет (торговля 24/7).
В высоко нагруженных проектах, специфичные индексы дают ощутимый прирост скорости работы.
Без специфичных индексов очередь к высокопроизводительному рейду на SSD дисках, где размещены только файлы tempdb, достигала 50-100. После создания необходимых индексов, очередь всего 0.1-0.5.

Тему оптимизации кода\запросов\планирования индексов в конфигураторе не подымаю. :)
31. Sergey.Noskov 1406 26.09.16 17:36 Сейчас в теме
(1) pbazeliuk,
>>Номенклатура.Ссылка В (&МассивНоменклатуры)
Для небольших массивов имеет право на жизнь, но для больших лучше предварительно поместить во временную таблицу и в следующем запросе выполнить отсечение.
а вот тут спорно, если массив более 128 элементов, платформа сама переделает на временную таблицу, лучше, по возможности, просто передать ссылку
41. bugagashenka 203 30.04.21 11:51 Сейчас в теме
(1)для больших(>=256) MSSQL сам положит в ВТ
2. utyv 65 20.09.16 22:17 Сейчас в теме
Решения о том что нужно оптимизировать, а что нет лучше принимать на основании замеров. Я набросал несколько искусственных примеров, которые демонстрирую суть проблемы.

Код с микро-запросами в цикле:
	Для каждого СтрокаТЧ из ЗаказПокупателя.СтруктураИзделияТЧ Цикл
		Артикул = СтрокаТЧ.Номенклатура.Артикул;
	КонецЦикла;

Замер - 6,2 сек после прогрева на документе с примерно 2000 строк.

Один запрос вытаскивает те же данные. Отбор массивом:
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	Номенклатура.Ссылка,
	|	Номенклатура.Артикул
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Ссылка В(&МассивНоменклатуры)";
	Запрос.Параметры.Вставить("МассивНоменклатуры", ЗаказПокупателя.СтруктураИзделияТЧ.ВыгрузитьКолонку("Номенклатура"));
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		Артикул = Выборка.Артикул;
	КонецЦикла;
Показать

Замер 200 мс.

Фильтрация с использованием временной таблицы, те же данные:
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	ВЫРАЗИТЬ(ЗаказПокупателяСтруктураИзделияТЧ.Номенклатура КАК Справочник.Номенклатура) КАК Номенклатура
	|ПОМЕСТИТЬ втНоменклатуры
	|ИЗ
	|	Документ.ЗаказПокупателя.СтруктураИзделияТЧ КАК ЗаказПокупателяСтруктураИзделияТЧ
	|ГДЕ
	|	ЗаказПокупателяСтруктураИзделияТЧ.Ссылка = &Ссылка
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	Номенклатура
	|;
	|
	|////////////////////////////////////////////////////////////­////////////////////
	|ВЫБРАТЬ
	|	Номенклатура.Ссылка,
	|	Номенклатура.Артикул
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Ссылка В
	|			(ВЫБРАТЬ
	|				т.Номенклатура
	|			ИЗ
	|				втНоменклатуры КАК т)";
	Запрос.Параметры.Вставить("Ссылка", ЗаказПокупателя);
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		Артикул = Выборка.Артикул;
	КонецЦикла;
Показать

Замер 70 мс.

Так что, в данном случае, в первую очередь надо убирать микрозапросы, потом думать о способе фильтрации, если разница в 130 мс нас еще беспокоит.
Индексы тут не помогут, потому что поле Ссылка и так всегда проиндексировано. Повторное использование возвращаемых значений - далеко не всегда подойдет. Это надо чтобы значения на самом деле повторно вычислялись, и была на готове функция, которую можно туда записать.

Сравнивать по скорости умножение в запросе и во встроенном языке нет особого желания. Я бы стал этим заниматься, если бы на замере увидел, что умножение у меня - самая медленная операция. Но такого пока не было, поэтому я не стесняюсь умножать там где мне удобнее.
ketr; _7445_; nekit_rdx; sergelemon; A7_Sash; Yit; sevushka; tormozit; orfos; mrflatcher; Evil Beaver; корум; dj_serega; Sholl; GATTUSO; swiss-garant; herfis; eskor; servs; NeviD; karpik666; orefkov; +22 Ответить
33. Sergey.Noskov 1406 26.09.16 18:02 Сейчас в теме
"в ИТС, конечно, написано, но кто ж его читает"(с) Поэтому автор правильно сделал, напоминать об этом периодически надо.
(2) не понятное сравнение запросов с фильтрацией по массиву и по временной таблице. Если бы в последнем запросе так же была передача именно массива, то мы увидим эффект именно от временной таблицы, точнее увидели бы, что стало хуже т.к. весь профит (200мс VS 70мс) получен из-за передачи ссылки а не от использования ВТ.
Если просто передать в запрос ссылку без временных таблиц
ВЫБРАТЬ РАЗЛИЧНЫЕ
	ВЫРАЗИТЬ(ЗаказПокупателяСтруктураИзделияТЧ.Номенклатура КАК Справочник.Номенклатура) КАК Номенклатура,
	ВЫРАЗИТЬ(ЗаказПокупателяСтруктураИзделияТЧ.Номенклатура КАК Справочник.Номенклатура).Артикул КАК Артикул
ИЗ
	Документ.ЗаказПокупателя.СтруктураИзделияТЧ КАК ЗаказПокупателяСтруктураИзделияТЧ
ГДЕ
	ЗаказПокупателяСтруктураИзделияТЧ.Ссылка = &Ссылка
то будет еще быстрее.
nekit_rdx; sergelemon; Evil Beaver; +3 Ответить
3. t278 58 21.09.16 02:57 Сейчас в теме
На мой взгляд, в статью нужно добавить примеры других статей по оптимизации. То, что здесь описано (точнее основная суть) оно уже где-то упоминалось, и регулярно упоминается., Что сократить число обращений к базе. Ценность статьи , освежить память, что БД ссылаться минимальное число раз.
4. warpath 39 21.09.16 08:06 Сейчас в теме
На мой взгляд статья отличная, если рассматривать ее для разработчиков с небольшим опытом.
Чтобы прийти к таком пониманию, придется потратить не один год, причем именно на разработку и качественную.

Спасибо автору, содержательно и доступно.
5. nafa 661 21.09.16 08:37 Сейчас в теме
Я бы добавил в статью, что все это касается ссылок (т.е. "Товары" - это ТЧ объекта "ДокументССылка"), а то кто-нибудь из начинающих точно к объекту ("ДокументОбъект") применит с соответствующим результатом.
6. premierex 204 21.09.16 08:54 Сейчас в теме
(0)
программа выполяет запрос к базе
считываются значения всех реквизтов
надо сначала сделасть замер производительности
написать что-то надобоие такого кода
мы можем столкнуться с некоторыми неудобстами
Потому-то боимся что-нибуль поломать

Автор, не ломайте русский язык. Статья - прописные истины для программиста 1С.
А фраза то надо сначала сделасть замер производительности (такая функция есть в конфигураторе, в меню "Отладка") просто в шок повергла: а мужики-то не знали!
Krasnyj; Uncore; ZOMI; Evil Beaver; Frogger1971; +5 Ответить
7. dmt 67 21.09.16 09:25 Сейчас в теме
Статья понравилась. Оптимизация минимальными усилиями.
8. kiv1c 818 21.09.16 11:46 Сейчас в теме
В примере с курсами валют для минимальных изменений нужно использовать Временное Хранилище.
Если мы еще не получали курс данной валюты на текущий день, получим и сохраним в Хранилище. Если получали, то возьмем из Хранилища без запроса к базе.
код примерно такой:

Перем АдресВременногоХранилищаКурсов;
Функция ПолучитьКурсВалюты(Валюта, ДатаКурса) 
	перем структураНайдено;
	ДатаКурсаВРегистр = ?(ДатаКурса = Дата('00010101'),КонецДня(ТекущаяДата()),ДатаКурса);
	КлючПоиска = Строка(Валюта)+Формат(ДатаКурсаВРегистр,"ДФ=dd_MM_yyyy");//ключ для хранилища - комбинация валюты и даты.
	сообщить("Ключ="+КлючПоиска);
	если АдресВременногоХранилищаКурсов=неопределено тогда
		//еще не клали ничего в хранилище
		СтруктураХранилищаНовая = Новый Структура();
		Структура = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(ДатаКурсаВРегистр, Новый Структура("Валюта", Валюта));
		СтруктураХранилищаНовая.Вставить(КлючПоиска,Структура);
		АдресВременногоХранилищаКурсов = ПоместитьВоВременноеХранилище(СтруктураХранилищаНовая);
		сообщить("еще не клали, адрес="+АдресВременногоХранилищаКурсов);
		Возврат Структура;
	иначе		
		//тип Структура, ключ - КлючПоиска, значение - Структура с полями Курс, Кратность.
		СтруктураКурсов = ПолучитьИзВременногоХранилища(АдресВременногоХранилищаКурсов);
		//теперь поищем записи в СтруктураКурсов по ключу
		
		если СтруктураКурсов.Свойство(КлючПоиска,структураНайдено) тогда
			//нашли, вернем...
			сообщить("Нашли, возвращаем..."+АдресВременногоХранилищаКурсов);
			СтруктураКурса = структураНайдено;
			Возврат СтруктураКурса;
		иначе
			//такого ключа нет,получим из базы и поместим в Хранилище
			сообщить("Ключа="+КлючПоиска+" еще нет, поместим в хранилище");
			
			Структура = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(ДатаКурсаВРегистр, Новый Структура("Валюта", Валюта));
			СтруктураКурсов.Вставить(КлючПоиска,Структура);
			АдресВременногоХранилищаКурсов = ПоместитьВоВременноеХранилище(СтруктураКурсов,АдресВременногоХранилищаКурсов);
			Возврат Структура;
		конецесли;		
	конецесли;
КонецФункции // ПолучитьКурсВалюты()
Показать



Фактически, у нас есть куча модулей которым нужен курс валюты, и в каждом добавлять переменную СтруктураКэш очень накладно.
Atilla9999; sergelemon; корум; +3 Ответить
12. Dmitri_1C 195 21.09.16 14:42 Сейчас в теме
Не совсем понятно, в чем заключается оптимизация, для первого примера.
Особенно строка:
СтрокаНоменклатуры = тзРеквизитовНоменклатуры.Найти(СтрокаТовары.Номенклатура, "Ссылка");


Уже не говоря о смысле примера:
То есть запросом получили таблицу значений, затем её перебираем, в цикле осуществляем поиск самого себя, а потом уже делаем расчеты.

Когда 10 элементов - это еще куда не шло.
А когда 1000 элементов для обработки и справочник номенклатура на 100 000 элементов
Сколько по времени будет выполняться данный цикл?

Оптимизация - это расчет внутри запроса, результатом которого будет готовая таблица.
Andreeei; +1 Ответить
13. herfis 513 21.09.16 15:16 Сейчас в теме
(12) Dmitri_1C, Смысл оптимизации очень простой - исключение лишних обращений к БД.
Это самая существенная оптимизация которая может быть сделана в 1С.
А когда 1000 элементов для обработки и справочник номенклатура на 100 000 элементов
Сколько по времени будет выполняться данный цикл?

Чем больше элементов для обработки - тем существеннее будет выигрыш.
Поиск по индексированной структуре данных в памяти - штука быстрая, хоть и выглядит громоздко.
Размер справочника вообще не имеет значения, т.к. считываются только необходимые данные.
Простор для оптимизации, конечно, остается. Но на фоне первого и главного этапа оптимизации выжать получится слезы.
Это только кажется - оооо, мы исключили операцию поиска в цикле на 1000 элементов! На практике это будут доли секунды.
В то время, как первичная оптимизация сэкономит минуты.
Если, конечно, можно не затрачивая усилий переписать чтобы вообще все запросом делалось - ну, отлично, чо.
Но в статье говорилось о внедрении оптимизации минимальными усилиями в существующий код. Пусть вас не обманывает простота примера. Он упрощен для легкости восприятия статьи.
tgg; dj_serega; utyv; +3 Ответить
15. pbazeliuk 1969 21.09.16 15:45 Сейчас в теме
(12) Dmitri_1C, там есть строчка -
тзРеквизитовНоменклатура.Индексы.Добавить("Ссылка"); 

Поиск очень быстро выполняется по индексу, но это действительно плохой пример.
Более адекватный пример, когда нужно проанализировать результат запроса -> таблицу значений по разной комбинации колонок (по дереву настроек). Кто сталкивался с матричным управлением ассортиментом по специфическим характеристикам, оборачиваемости и полкоместам, наверняка сталкивался.
16. utyv 65 21.09.16 16:27 Сейчас в теме
pbazeliuk, эта статья конечно для новичков, я даже не собирался ставить тег hiload - это модератор мне прицепил. Хардкор с ручным прикручиванием индекса и терабайтными базами 24*7 не рассматриваю.
17. Evil Beaver 8244 22.09.16 00:03 Сейчас в теме
Господи-боже... еще один вдруг понял, что разыменование ссылок приводит к запросу в СУБД....
А еще, внезапно, ДокументСсылка.Номер приведет к считыванию в память документа со всеми его табличными частями, ага. В документации написано. И про объектный кеш, и много еще чего, но кто же читает то...

С курсами валют вообще не догнал, ад какой-то....
Andreeei; sergelemon; awa; NeeDiGeo; _also; orfos; Новиков; PrinzOfMunchen; kraynev-navi; корум; webester; minimajack; pbazeliuk; Rik30; karpik666; zqzq; Noxie41; ZOMI; t278; +19 1 Ответить
21. webester 26 22.09.16 16:28 Сейчас в теме
Старая шутка и не интересная :) если написать:
Ребят обращение к данным через несколько точек, это плохо и приводит к чрезмерной нагрузке на БД. Запомните это правило ибо оно написано кровью многих хороших 1Сников.

Будет слишком коротко, и все до одного напишут "и чо?". А вот если размазать на пару абзацев с запросами... то сразу найдутся те кто напишут "для начинающих самое то!" Вон некоторым, что бы это осознать вообще надо несколько лет качественной разработки. Про курс валют, соглашусь с (17), ни в коем случае не открывай модуль приложения в торговле. Вдруг там за тебя че нить уже оптимизировали.
18. bdimaw 22.09.16 09:52 Сейчас в теме
О проблеме получения данных по точке знал, но не придавал особого значения, старался просто редка использовать.
Протестировал на практике, эффект колоссальный, в некоторый места производительность повысилась в 4 раза, документ стал быстрее открываться.
Спасибо автору, подтолкнул к изменениям.
19. ivanov660 4578 22.09.16 10:12 Сейчас в теме
Считаю что приведенные решения по оптимизации не завершенные, а полумеры:

- оптимально в первом примере все оставшиеся действия вынести в запрос - умножения и деления;
- во втором примере также в конечном варианте получать данные запросом.

Придумывайте более адекватные примеры.
Andreeei; +1 Ответить
23. unichkin 1579 22.09.16 21:11 Сейчас в теме
(19) ivanov660, не всегда хорошо формулы выносить в запрос. имхо - этого как раз нужно избегать, применяя только там где совсем никак без них не обойтись. Запрос - инструмент сбора данных, а не обработки. Формулы усложняют чтение, и в запросе их нельзя отладить. Лучше потратить 5 копеек производительности, и иметь возможность отладки формулы в читабельном коде.
eugeniezheludkov; +1 Ответить
25. minimajack 80 23.09.16 08:03 Сейчас в теме
(23) unichkin, Очень спорное предложение...
ИМХО лучше переложить по максимуму в запрос, чем вычислять ручными операциями
26. herfis 513 23.09.16 09:17 Сейчас в теме
(23) (25) Есть простой критерий.
Если разница производительности несущественна, то руководствоваться нужно соображениями читабельности и удобства сопровождения.
Иногда удобно так, иногда - эдак.
Если формулы простые и понятные, то глупо ради их расчета прикручивать дополнительный этап постобработки.
И наоборот, если постобработка и так уже есть по какой-то причине, а расчетные формулы сложные, то глупо их расчет запихивать в запрос ради выигрыша пары миллисекунд.
nekit_rdx; TeMochkiN; sergelemon; unichkin; Artem-B; mrflatcher; корум; +7 Ответить
20. logarifm 1122 22.09.16 11:14 Сейчас в теме
Поставил плюс.
Я вот что хочу сказать. Статья расчитана для начинающих оптимизаторов. Ну и также напоминает большим дядькам или мужикам как это было сказано в http://forum.infostart.ru/forum24/topic158625/message1623673/#message1623673
Не забываем, что маленькие но зато весьма частые вызовы также могут делать проблемы. И то что отследить такое поведение очень и очень не просто даже мужикам с большой буквы М.

Потому как в нагрузочных тестах СКЛ мы в топах не увидим это и накладных расходов тоже практически ничего не увидим кроме как количества вызовов за время.

Насчет кэш валюты ну а что ведь не плохо и к примеру при уже на выходе 8.3.9 никто не мешает -то сделать &Вместо не меняя штатную конфигурацию.

Да и статья - не закончена это только начало ИЗ возможных всех проблем. Да тут есть примеры и т.д. Но это крайне глубокая тема и как мне кажеться для этого надо развивать в подфорумы вот где и будут статьи такого рода объединяться в опыт.
22. unichkin 1579 22.09.16 21:06 Сейчас в теме
А вот этого лучше не делать:
тзРеквизитовНоменклатуры = Запрос.Выполнить().Выгрузить();

Причем "не делать" в двух частях.
1) Выборка к оперативке более лояльна чем тз, и для обработки результата (тем более при проведении) лучше использовать именно выборку. Еще у нее есть метод "НайтиСледующий()" - это если тз нужна была для поиска.
2) Для себя давно уже сделал аксиомой писать не "Запрос.Выполнить().Выбрать()" или "Запрос.Выполнить().Выгрузить()", а "РезультатЗапроса = Запрос.Выполнить(); Выборка = РезультатЗапроса.Выгрузить()" - чтобы удобнее в отладчике работать.
_
Запрос.Параметры.Вставить("МассивНоменклатуры", Товары.ВыгрузитьКолонку("Номенклатура")); 

- и это, я думаю - лишние накладные расходы. Можно передать в запрос ссылку, и отобрать необходимые данные табл. части.
Andreeei; chernov.gigansk.ru; Evil Beaver; +3 Ответить
24. dmt 67 23.09.16 04:03 Сейчас в теме
(22) unichkin,
1. А как быстрее? Там в таблице индекс для скорости добавлен.
2. Документ не обязательно записан - из ссылки брать не чего.
27. DenisCh 23.09.16 09:22 Сейчас в теме
(22) unichkin,
Выборка к оперативке более лояльна чем тз


На клиенте да. Но вот на sql-сервере... Там же курсор открывается и висит...
29. unichkin 1579 23.09.16 22:19 Сейчас в теме
(27) DenisCh, "на sql-сервере... Там же курсор открывается и висит..." - таких глубоких познаний sql у меня нет:) Мой подход прост: при разработке нужно следовать ИТС, если тормоза\тупняки - начинать изобретать. Имхо в контексте данной публикации говорить такое - опасно для неопытной публики)) Для такого обсуждения лучше подошел бы 1С++ или миста.
(24) dmt,
1) Не обязательно будет быстрее, но оперативки будет жрать меньше. Я уже ответил как: использовать выборку и метод "НайтиСледующий()"
2) В статье речь о проведении документа вроде-бы? При проведении он записан 100% :) Если зачем-то перед записью это делать то да... Но лучше все-же сперва подумать. Особенно, если есть многостраничные доки.

з.ы. ИТС: Оптимизация использования оперативной памяти
32. DenisCh 26.09.16 17:39 Сейчас в теме
(29) unichkin,
Для такого обсуждения лучше подошел бы 1С++ или миста.


На 1с++ это и так знают, а миста - это скопище ненатуралов. Тут намного более профессиональный форум.
artbear; Evil Beaver; +2 Ответить
37. herfis 513 27.09.16 09:43 Сейчас в теме
(32) DenisCh, А 1С++ это хде? :)
Я только сайт проекта знаю...
34. Evil Beaver 8244 26.09.16 19:39 Сейчас в теме
(27) DenisCh,
на sql-сервере... Там же курсор открывается и висит...


Насколько я знаю, сотрудники 1С отвечали, что нет никакого курсора. Выборка вся размещается в памяти сервера приложений 1С, несмотря на "Следующий()". Курсорные (вернее, порционные) запросы на больших объемах предлагают реализовывать самостоятельно.
35. herfis 513 27.09.16 09:35 Сейчас в теме
(34) Evil Beaver,
Выборка вся размещается в памяти сервера приложений 1С, несмотря на "Следующий()"

Да ладно. На ИТС прямым текстом говорится о блочном считывании (как для динамических списков) и о связанных с этим особенностях.
Курсоров в самом деле вроде нет (в 7.7 кажись использовались), на 8-ке реализовали свой механизм "скользящих" выборок на обычных запросах. В основном из соображений кроссплатформенности, как я понимаю.
38. Evil Beaver 8244 28.09.16 14:08 Сейчас в теме
(35) herfis, блочное (а если точнее - интервальное) считывание есть ТОЛЬКО в дин. списках. И отборы интервалов строит сама платформа, можно видеть в профайлере SQL, например.

В обычных пользовательских запросов интервальности нет. Хотя, я, возможно, чего-то не видел.
40. herfis 513 28.09.16 15:05 Сейчас в теме
(38) Evil Beaver, Да, я уже скумекал. Но не только в дин-списках, еще в тех выборках которые объектные. Они по тому же принципу. А в выборках результата запроса - нет. Но оно и понятно, если подумать. Интервальный просмотр с предсказуемой производительностью только по индексу можно сделать. Хотя вот в динамических списках с произвольным текстом запроса - таки делают. Но там и производительность ровно такая, насколько сложна выборка и насколько она ложится на индексы основной таблицы. В запросах такого даром не надо. Во-первых, интервальный просмотр данных БД никак не сочитается с транзакционностью, во-вторых он на сложных выборках будет тормозить вообще пипец как.
(39) Зашел, посмотрел на активность, почтил память.
28. Ekovichev 825 23.09.16 14:02 Сейчас в теме
1. В первом случае умножаем в запросе и не парим мозг;
2. Во втором случае кэширование в таблице значений неоптимально из-за медленного поиска. Следует использовать соответствие, где Ключ = Валюта, Значение = соответствие (Ключ = Дата, Значение = Структура("Курс, Кратность",...).

Функция ПолучитьКурсВалюты(Валюта, ДатаКурса, СтруктураКэш = Неопределено) Экспорт
	
	// добавили тут
	лкДата = ?(ДатаКурса = Дата('00010101'),ТекущаяДата(),ДатаКурса);
	
	Если СтруктураКЭШ = Неопределено Тогда
		СтруктураКэш = Новый Соответствие;
	КонецЕсли;
		
	Попытка                                     			
		Если СтруктураКэш[Валюта][ДатаКурса] = Неопределено Тогда ВызватьИсключение(""); КонецЕсли;				
		Возврат СтруктураКэш[Валюта][ДатаКурса];    		
	Исключение
		ДобавитьСрезВалютыВКЭШ(СтруктураКэш, Валюта, лкДата);
		Возврат СтруктураКэш[Валюта][ДатаКурса];
	КонецПопытки; 	                                
	
КонецФункции // ПолучитьКурсВалюты()

Функция ДобавитьСрезВалютыВКЭШ(СтруктураКэш, Валюта, лкДата)
	
	Структура = РегистрыСведений.КурсыВалют.ПолучитьПоследнее(лкДата, Новый Структура("Валюта", Валюта));	
	Если СтруктураКэш[Валюта] = Неопределено Тогда
		СтруктураСрезКурсаНаДату = Новый Соответствие
		СтруктураСрезКурсаНаДату[лкДата] = Структура;
		СтруктураКэш[Валюта] = СтруктураСрезКурсаНаДату;
	Иначе
		СтруктураКэш[Валюта][лкДата] = Структура;
	КонецЕсли;
	
КонецФункции
Показать


Писал на коленке, торговли нет проверять. Посмотрите метод с курсом и если он быстрее то вставьте его в статью. Новичкам это сэкономит еще больше времени:)
30. roma_semenov79 207 23.09.16 22:59 Сейчас в теме
Для типовых бухгалтерий и ЗуПов все это ведь малоактуально. До первого обновления такая оптимизация.
36. herfis 513 27.09.16 09:40 Сейчас в теме
Хотя, пожалуй, я гоню. К выборке результата запроса это скорее всего не относится...
Оставьте свое сообщение