Автор статьи: H A D G E H O G s
исходник: http://kb.mista.ru/article.php?id=678
В статье описываются приемы оптимизация работы с прикладными объектами платформы через COM-соединение. Так же в статье приведены статистические данные замеров производительности 1) и проведен сравнительный анализ различных методов работы с COM-объектами.
Охота на COMов
Часть первая. Жажда скорости
Глава первая. Широко закрытые глаза
Чтение
Начнем с очевидных вещей. Первым шагом для существенного повышения скорости работы чтения через Внешнее Соединение считаю полный отказ от использования выборок/ссылок внешней базы.
Про конструкции вида:
Выборка=V8.Справочники.Склады.Выбрать();
Пока Выборка.Следующий() Цикл
//...
КонецЦикла;
рекомендуется вспоминать только в страшных ночных кошмарах. Ибо при обращении к любому реквизиту, не принадлежащему к основным данным объекта произойдет чтение всех реквизитов объекта. Если мы попытаемся получить реквизит основных данных - то произойдет чтение только основных данных, что тоже не хорошо. Использование же запроса позволит читать только то, что вам действительно нужно.
Вот такой код:
Запрос=V8.NewObject("Запрос");
Запрос.Текст=
"ВЫБРАТЬ
| Склады.Код,
| Склады.Наименование,
| Склады.ВидСклада.Порядок
|ИЗ
| Справочник.Склады КАК Склады";
Выборка=Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КонецЦикла;
позволит прочесть из базы только то, что действительно нужно.
Также необходимо обратить внимание на то, что поле Ссылка в запросах, подобных приведенному ниже, в большинстве случаев бесполезна и даже вредна:
Запрос=Бухгалтерия.NewObject("Запрос");
Запрос.Текст=
"ВЫБРАТЬ
| Склады.Ссылка, //Вот здесь оно, это поле
| Склады.Код,
| Склады.Наименование,
| Склады.ВидСклада.Порядок
|ИЗ
| Справочник.Склады КАК Склады";
Выборка=Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КонецЦикла;
Ссылка бесполезна потому, что это ссылка на объект внешней базы, которая в локальной базе БЕСПОЛЕЗНА 2). А вредна потому, что ее наличие может спровоцировать появление такого кода:
Пока Выборка.Следующий() Цикл
НашОбъект=ПоискПоКоду(Выборка.Ссылка.Код);
КонецЦикла;
которая циклично читает объект, а значит сводит на нет весь смысл использование запроса 3).
Также не рекомендуется использовать конструкцию вида:
Пока Выборка.Следующий() Цикл
Сообщить("Производится выгрузка склада "+V8.String(Выборка.Ссылка));
КонецЦикла;
Хотя данный код и менее пагубно сказывается на быстродействии — считываются только основные данные объекта — считывание лишних данных через COM-соединение все же происходит. Лучше использовать функции ПРЕДСТАВЛЕНИЕ() иПРЕДСТАВЛЕНИЕССЫЛКИ() в запросе, или на крайняк Наименование вытащить в поля запроса 4).
Следует отметить, что указанные особенности справедливы не только для внешнего соединения, но и для локальных баз. Не пренебрегайте приведенными советами и рекомендациями.
В Таблице 1 приведены результаты тестов на скорость получения данных в зависимости от типа соединения и способа получения данных.
Таблица 1. Время выборки 10000 элементов в секундах
Тип соединения | Выборка | Запрос |
---|---|---|
ComConnector | 0,583412 | 0,192428 |
Application | 1,178783 | 0,782243 |
Запись
Приведенные выше методики оптимизации подходят только для чтения данных, поскольку платформа 1С не предоставляет ни каких возможностей для записи данных при помощи языка запросов. По этому управлять количеством и способом получения данных объектов при записи практически не возможно.
Эмпирически путем было установлено, что транзакции при записи через COM-соединение позволяют ускорить процесс. На текущий момент теоретического обоснования этого феномена найти не удалось.
Таблица 2 показывает, что конструкция вида:
НовыйВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
СправочникМенеджер=Бухгалтерия.Справочники.Склады;
Бухгалтерия.НачатьТранзакцию();
Для i=1 по 1000 Цикл
НовыйОбъект=СправочникМенеджер.СоздатьЭлемент();
НовыйОбъект.Наименование=Строка(Новый УникальныйИдентификатор());
НовыйОбъект.ВидСклада=НовыйВидСклада;
НовыйОбъект.Записать();
КонецЦикла;
Бухгалтерия.ЗафиксироватьТранзакцию();
отрабатывает быстрее, чем такая конструкция:
НовыйВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
СправочникМенеджер=Бухгалтерия.Справочники.Склады;
Для i=1 по 1000 Цикл
НовыйОбъект=СправочникМенеджер.СоздатьЭлемент();
НовыйОбъект.Наименование=Строка(Новый УникальныйИдентификатор());
НовыйОбъект.ВидСклада=НовыйВидСклада;
НовыйОбъект.Записать();
КонецЦикла;
Таблица 2. Время записи 1000 элементов в секундах в зависимости от использования транзакции
Тип соединения | Простая | Транзакция |
---|---|---|
ComConnector | 2,594816 | 1,558324 |
Application | 8,482852 | 7,455433 |
Глава два. Для сильных духом мужчин
Во второй главе речь пойдет про методику оптимизации работы с объектами удаленной базы. Следует отметить, что работа с методами/свойствами Com объектов в 1С осуществляется через позднее связывание и требует значительных затрат. Поэтому, чем меньше вы делаете вызовов методов/свойств Com объекта - тем более быстро все работает.
Например, конструкция вида:
Для i=1 по 1000 Цикл
НовыйОбъект=Бухгалтерия.Справочники.Склады.СоздатьЭлемент();
НовыйОбъект.Наименование=Строка(Новый УникальныйИдентификатор());
НовыйОбъект.ВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
НовыйОбъект.Записать();
КонецЦикла;
гораздо медленнее работает, чем вот такая:
НовыйВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
СправочникМенеджер=Бухгалтерия.Справочники.Склады;
Для i=1 по 1000 Цикл
НовыйОбъект=СправочникМенеджер.СоздатьЭлемент();
НовыйОбъект.Наименование=Строка(Новый УникальныйИдентификатор());
НовыйОбъект.ВидСклада=НовыйВидСклада;
НовыйОбъект.Записать();
КонецЦикла;
ибо во втором случае используется гораздо меньше вызовов свойств Com-объектов.
Ну и наконец решение для фанатов - основной код должен быть внутри конфигурации внешней базы, скажем в процедурах общего модуля, и вызываться из локальной базы в передачей параметров, например, как:
НовыйВидСклада=Бухгалтерия.Перечисления.ВидыСкладов.Оптовый;
Для i=1 по 1000 Цикл
Бухгалтерия.СоздатьСклад(Строка(Новый УникальныйИдентификатор()),НовыйВидСклада);
КонецЦикла;
// здесь СоздатьСклад() - это функция общего модуля внешней базы:
Функция СоздатьСклад(Наименование, ВидСклада) Экспорт
НовыйОбъект=Справочники.Склады.СоздатьЭлемент();
НовыйОбъект.Наименование=Наименование;
НовыйОбъект.ВидСклада=ВидСклада;
НовыйОбъект.Записать();
КонецФункции
Однако этот прием затруднительно использовать и отлаживать, кроме того он почти не имеет смысла для COMConnector-а. Таблица 3. Сводный отчет о производительности. Время записи 1000 элементов в секундах
Тип соединения | Простая | Предопредел.СправочникМенеджер | Внутренняя |
---|---|---|---|
ComConnector | 5,12778 | 2,49483 | 2,29044 |
Application | 57,52807 | 8,337808 | 2,395056 |
Часть два. Тонкости и трюки
Глава один. Работа со значениями перечисления
Перечисления придуманы для того, чтобы обращаться к их значениям из кода через точку. Однако, во второй главе части первой написано про позднее связывание и как оно отражается на быстродействии. Для того, чтобы ускорить работу с перечислениями через COM, можно использовать следующий трюк.
СоответствиеВидаСклада=Новый Соответствие;
СоответствиеВидаСклада.Вставить(0,Перечисления.ВидыСкладов.Оптовый);
СоответствиеВидаСклада.Вставить(1,Перечисления.ВидыСкладов.Розничный);
СоответствиеВидаСклада.Вставить(2,Перечисления.ВидыСкладов.НеавтоматизированнаяТорговаяТочка);
Запрос=Бухгалтерия.NewObject("Запрос");
Запрос.Текст=
"ВЫБРАТЬ
| Склады.Ссылка,
| Склады.Наименование,
| Склады.ВидСклада.Порядок как ПорядокВидаСклада
|ИЗ
| Справочник.Склады КАК Склады";
Выборка=Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НовыйОбъект=Справочники.Склады.СоздатьЭлемент();
НовыйОбъект.Наименование=Выборка.Наименование;
НовыйОбъект.ВидСклада=СоответствиеВидаСклада.Получить(Выборка.ПорядокВидаСклада);
НовыйОбъект.Записать();
КонецЦикла;
То есть из удаленной базы выбирается НЕ ССЫЛКА на элемент перечисления, которая возвратится как COM-объект, а его ПОРЯДКОВЫЙ НОМЕР в конфигурации, который также позволяет однозначно идентифицировать значение перечисления. Ну а конструкция:
СоответствиеВидаСклада=Новый Соответствие;
СоответствиеВидаСклада.Вставить(0,Перечисления.ВидыСкладов.Оптовый);
СоответствиеВидаСклада.Вставить(1,Перечисления.ВидыСкладов.Розничный);
СоответствиеВидаСклада.Вставить(2,Перечисления.ВидыСкладов.НеавтоматизированнаяТорговаяТочка);
устанавливает соответстивие между порядковым номером перечисления в удаленной базе и его значением в локальной базе. Сделана эта конструкция для удобства кода.
Глава два. Если что-то хочется, но нельзя, то немного можно.. Но не со мной.
Вот есть у вас задача сделать обмен через COM. И будут там обмениваться ну скажем… Поступления. Что я делаю в первую очередь? А в первую очередь я в модуле обработки прописываю функции
ПолучитьНоменклатуру()
ПолучитьСклад()
ПолучитьКонтрагента()
вот так.
Разберем теперь код следующего вида:
Перем СоответствиеВидовСкладов Экспорт;
Перем ЗапросВнешнейБазы Экспорт;
Перем ВнешняяБаза Экспорт;
Процедура Подключиться() Экспорт
ВнешняяБаза=Новый COMОбъект("V81.COMConnector");
Попытка
ВнешняяБаза=ВнешняяБаза.Connect(База.СтрокаПодключения);
Исключение
Предупреждение("Ошибка открытия базы!!!");
Сообщить(ОписаниеОшибки());
ВнешняяБаза=Неопределено;
Возврат;
КонецПопытки;
ЗапросВнешнейБазы=ВнешняяБаза.NewObject("Запрос");
СоответствиеВидовСкладов=Новый Соответствие;
СоответствиеВидовСкладов.Вставить(0,Перечисления.ВидыСкладов.Оптовый);
СоответствиеВидовСкладов.Вставить(1,Перечисления.ВидыСкладов.Розничный);
СоответствиеВидовСкладов.Вставить(2,Перечисления.ВидыСкладов.НеавтоматизированнаяТорговаяТочка);
КонецПроцедуры
Функция ПолучитьСклад(КодВнешнейБазы,ПолучатьГруппу=Ложь)Экспорт
Если (КодВнешнейБазы=Неопределено)ИЛИ(КодВнешнейБазы=NULL)ИЛИ(КодВнешнейБазы="") Тогда
Возврат Справочники.Склады.ПустаяСсылка();
КонецЕсли;
Склад=Справочники.Склады.НайтиПоРеквизиту("КодВнешнейБазы",КодВнешнейБазы);
Если Не Склад.Пустая() Тогда
Возврат Склад; //Он создан уже раньше, вернем ссылку на него
Иначе
Если ПолучатьГруппу Тогда
ЗапросВнешнейБазы.Текст=
"ВЫБРАТЬ
| Склады.Наименование,
| Склады.Родитель.Код
|ИЗ
| Справочник.Склады КАК Склады
|ГДЕ
| Склады.Код = &Код";
ЗапросВнешнейБазы.УстановитьПараметр("Код",КодВнешнейБазы);
Выборка=ЗапросВнешнейБазы.Выполнить().Выбрать();
Выборка.Следующий();
Склад=Справочники.Склады.СоздатьГруппу();
Склад.Наименование=Выборка.Наименование;
Склад.КодВнешнейБазы=КодВнешнейБазы;
Склад.Родитель=ПолучитьСклад(Выборка.РодительКод,Истина);
Склад.Записать();
Иначе
ЗапросВнешнейБазы.Текст=
"ВЫБРАТЬ
| Склады.Наименование,
| Склады.ВидСклада.Порядок,
| Склады.Родитель.Код
|ИЗ
| Справочник.Склады КАК Склады
|ГДЕ
| Склады.Код = &Код";
ЗапросВнешнейБазы.УстановитьПараметр("Код",КодВнешнейБазы);
Выборка=ЗапросВнешнейБазы.Выполнить().Выбрать();
Выборка.Следующий();
Склад=Справочники.Склады.СоздатьЭлемент();
Склад.Наименование=Выборка.Наименование;
Склад.ВидСклада=СоответствиеВидовСкладов.Получить(Выборка.ВидСкладаПорядок);
Склад.КодВнешнейБазы=КодВнешнейБазы;
Склад.Комментарий="Создан "+ТекущаяДата()+" во время выгрузки";
Склад.Родитель=ПолучитьСклад(Выборка.РодительКод,Истина);
Склад.Записать();
КонецЕсли;
Возврат Склад.Ссылка;
КонецЕсли;
КонецФункции
Первым делом мы подключаемся к удаленной базе и сразу создаем объект запроса в удаленной базе (“Для сильных духом”). Затем создаем соответствие для перечисления по порядковому номеру. Все это храним в экспортных переменных модуля объекта.
Допускаем, что синхронизация осуществляется по коду объекта удаленной базы, который хранится в реквизите объекта локальной базы (не забывайте про длину реквизита, иначе будут дубли!).
Ну а функция ПолучитьСклад() – примитивна, разбирайтесь товарищи сами.
Плюсы:
- Быстродействие
- Формирование полной иерархии
- Возможность быстро получить элемент, например, при перегрузке документа.