Автор статьи: 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)ИЛИ(КодВнешнейБазы="") Тогда
        Возврат Справочники.Склады.ПустаяСсылка();
    КонецЕсли;
    Склад=Справочники.Склады.НайтиПоРеквизиту("КодВнешнейБазы",КодВнешнейБазы);
    Если Не Склад.Пустая() Тогда
        Возврат Склад; //Он создан уже раньше, вернем ссылку на него
    Иначе
        Если ПолучатьГруппу Тогда
            ЗапросВнешнейБазы.Текст=
            "ВЫБРАТЬ
            |    Склады.Наименование,
            |    Склады.Родитель.Код
            |ИЗ
            |    Справочник.Склады КАК Склады
            |ГДЕ
            |    Склады.Код = &Код";
            ЗапросВнешнейБазы.УстановитьПараметр("Код",КодВнешнейБазы);
            Выборка=ЗапросВнешнейБазы.Выполнить().Выбрать();
            Выборка.Следующий();
            Склад=Справочники.Склады.СоздатьГруппу();
            Склад.Наименование=Выборка.Наименование;
            Склад.КодВнешнейБазы=КодВнешнейБазы;
            Склад.Родитель=ПолучитьСклад(Выборка.РодительКод,Истина);
            Склад.Записать();
        Иначе
            ЗапросВнешнейБазы.Текст=
            "ВЫБРАТЬ
            |    Склады.Наименование,
            |    Склады.ВидСклада.Порядок,
            |    Склады.Родитель.Код
            |ИЗ
            |    Справочник.Склады КАК Склады
            |ГДЕ
            |    Склады.Код = &Код";
            ЗапросВнешнейБазы.УстановитьПараметр("Код",КодВнешнейБазы);
            Выборка=ЗапросВнешнейБазы.Выполнить().Выбрать();
            Выборка.Следующий();
            Склад=Справочники.Склады.СоздатьЭлемент();
            Склад.Наименование=Выборка.Наименование;
            Склад.ВидСклада=СоответствиеВидовСкладов.Получить(Выборка.ВидСкладаПорядок);
            Склад.КодВнешнейБазы=КодВнешнейБазы;
            Склад.Комментарий="Создан "+ТекущаяДата()+" во время выгрузки";
            Склад.Родитель=ПолучитьСклад(Выборка.РодительКод,Истина);
            Склад.Записать();
        КонецЕсли;
        Возврат Склад.Ссылка;
    КонецЕсли;
КонецФункции

Первым делом мы подключаемся к удаленной базе и сразу создаем объект запроса в удаленной базе (“Для сильных духом”). Затем создаем соответствие для перечисления по порядковому номеру. Все это храним в экспортных переменных модуля объекта.
Допускаем, что синхронизация осуществляется по коду объекта удаленной базы, который хранится в реквизите объекта локальной базы (не забывайте про длину реквизита, иначе будут дубли!).
Ну а функция ПолучитьСклад() – примитивна, разбирайтесь товарищи сами.

Плюсы:

  1. Быстродействие
  2. Формирование полной иерархии
  3. Возможность быстро получить элемент, например, при перегрузке документа.
1) Тесты делал правильно, базу очищал, перезапускал. База файловая. H A D G E H O G s
2) в большинстве случаев
3) Поверьте моему опыту, вы этого и не заметите, пока вам бухгалтер не скажет: «Что же так долго выгрузка ползет?..» H A D G E H O G s
4) Есть и еще одна причина, но о ней чуть позже