При разработке функционала на управляемых формах 1С разработчики нередко сталкиваются с ситуацией, когда вызов серверной процедуры с передачей большой коллекции данных (например, массива или списка из сотен тысяч или миллиона элементов) занимает десятки секунд, даже если клиент и сервер физически работают на одном компьютере.
Рассмотрим, почему стандартная передача параметров работает медленно, как устроен механизм сериализации платформы 1С:Предприятие и какие существуют практические способы многократного ускорения таких вызовов.
Посмотрим на классический тестовый пример, демонстрирующий проблему передачи миллиона чисел:
&НаКлиенте
Процедура Тест(Команда)
к = 1000000;
м = Новый Массив;
р = Новый ГенераторСлучайныхЧисел;
Для _ = 1 По к Цикл
м.Добавить(р.СлучайноеЧисло(0, 65535));
КонецЦикла;
т = ТекущаяУниверсальнаяДатаВМиллисекундах();
ТестНаСервере(м);
Сообщить(ТекущаяУниверсальнаяДатаВМиллисекундах() - т);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ТестНаСервере(м)
// Пустая процедура
КонецПроцедуры
При выполнении этого кода время вызова может достигать 40–60 секунд. Выясним основные причины таких задержек:
xs:decimal). Коллекция из 1 000 000 элементов порождает миллионы XML-тегов, из-за чего исходные 4 Мб чисел превращаются в десятки и сотни мегабайт текста.Знач, платформа передает коллекцию на сервер, а затем целиком возвращает обратно на клиент, удваивая время сериализации.ТаблицаЗначений с явно заданным типом колонки через ОписательТипов) не отключает генерацию служебных XML-тегов для каждой строки, поэтому типизация не решает проблему скорости передачи.Разберем базовую оптимизацию, которая исключает обратную передачу данных на клиент. Если серверная процедура не должна изменять саму переданную коллекцию для клиента, всегда указываем ключевое слово Знач в заголовке процедуры:
&НаСервереБезКонтекста
Процедура ТестНаСервере(Знач м)
// Параметр не будет сериализоваться обратно на клиент
КонецПроцедуры
Важно: Это сократит время вызова примерно вдвое, однако прямая сериализация миллиона элементов все еще займет существенное время.
Наиболее быстрый способ передачи больших объемов однотипных примитивных данных (чисел, байтов) — использование низкоуровневых потоков. Преобразуем массив в непрерывный бинарный буфер:
&НаКлиенте
Процедура ПередатьЧерезПоток()
Поток = Новый ПотокВПамяти;
Запись = Новый ЗаписьДанных(Поток);
р = Новый ГенераторСлучайныхЧисел;
Для Сч = 1 По 1000000 Цикл
// Записываем 32-битное целое число напрямую в поток
Запись.ЗаписатьЦелое32(р.СлучайноеЧисло(0, 65535));
КонецЦикла;
Запись.Закрыть();
ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
// Передаем компактные двоичные данные (всего ~4 Мб)
ПринятьДанныеНаСервере(ДвоичныеДанные);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ПринятьДанныеНаСервере(Знач ДвоичныеДанные)
Поток = ДвоичныеДанные.ОткрытьПотокДляЧтения();
Чтение = Новый ЧтениеДанных(Поток);
Пока Не Чтение.ИсходныйПотокЗавершен Цикл
Число = Чтение.ПрочитатьЦелое32();
// Обработка полученного числа на сервере
КонецЦикла;
Чтение.Закрыть();
КонецПроцедуры
Этот подход передает 1 миллион чисел меньше чем за 1 секунду, так как полностью исключает накладные расходы XML-тегов.
Если требуется передавать не просто числа, а структуры или разнородные списки, эффективной альтернативой является формат JSON. Объекты ЗаписьJSON и ЧтениеJSON формируют компактный текст без дублирования типов у каждого элемента:
&НаКлиенте
Процедура ПередатьЧерезJSON(МассивДанных)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, МассивДанных);
СтрокаJSON = ЗаписьJSON.Закрыть();
ПринятьJSONНаСервере(СтрокаJSON);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ПринятьJSONНаСервере(Знач СтрокаJSON)
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
МассивНаСервере = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
КонецПроцедуры
Передача одной сплошной строки JSON выполняется платформой на порядки быстрее, чем прямая сериализация сложного объекта Массив.
При работе через медленные каналы связи (веб-клиент, удаленные подключения) бинарные данные или строковые пакеты JSON можно сжать с помощью встроенного объекта СжатиеДанных:
&НаКлиенте
Функция СжатьСтроку(СтрокаДанных)
Двоичные = ПолучитьДвоичныеДанныеИзСтроки(СтрокаДанных);
СжатыеДанные = СжатиеДанных(Двоичные, ФорматСжатия.Deflate);
Возврат СжатыеДанные;
КонецФункции
&НаСервереБезКонтекста
Процедура ОбработатьСжатыеДанные(Знач СжатыеДанные)
РаспакованныеДанные = РаспаковкаДанных(СжатыеДанные, ФорматСжатия.Deflate);
СтрокаДанных = ПолучитьСтрокуИзДвоичныхДанных(РаспакованныеДанные);
// Дальнейшая обработка
КонецПроцедуры
Проанализируем архитектуру вызова: если передаваемый массив формируется программно (например, генерацией случайных данных, расчетом или выборкой из базы), наилучшим решением будет перенести логику формирования данных целиком на сервер в контекст &НаСервереБезКонтекста или выполнить её в фоновом задании через ФоновыеЗадания.Выполнить.
В таком случае клиент вообще освобождается от работы с тяжелыми массивами и получает только итоговый результат выполнения операции.