При работе в 1С:Предприятии 7.7 разработчики часто сталкиваются с необходимостью кэширования промежуточных расчетов. Представьте ситуацию: вы строите сложный аналитический отчет (своеобразный мини-OLAP куб), который собирает данные из сотен тысяч документов за несколько лет. Сбор такой таблицы значений (ТЗ) может занимать значительное время, а пользователю требуется крутить эти данные в разных разрезах, менять группировки и перестраивать дерево отчета «на лету».
Для оптимизации работы логично один раз собрать данные, быстро выгрузить их во внешний файл, а при последующих действиях или в новых сессиях мгновенно загрузить их обратно в ТЗ. Однако стандартные механизмы платформы 7.7 при работе с сотнями тысяч строк начинают катастрофически тормозить. Выясним причину этой проблемы и подробно разберем самые скоростные способы сохранения и загрузки больших объемов данных.
Казалось бы, первое, что приходит на ум, — использовать встроенные процедуры ЗначениеВФайл() и ЗначениеИзФайла() (или работу через внутренние строки ЗначениеВСтрокуВнутр()). Однако на больших ТЗ (от 50 000 до 100 000 строк и более) эти функции демонстрируют крайне нелинейный рост времени обработки. Проанализируем причину: это связано с особенностями выделения памяти и внутренними алгоритмами сериализации платформы 7.7, из-за которых на больших объемах данных производительность падает по экспоненте. Таблица из 100 000 строк может сохраняться более 70 секунд, что полностью перечеркивает смысл кэширования.
Также встроенный объект Текст при добавлении строк через метод ДобавитьСтроку() страдает от аналогичной проблемы перераспределения памяти на больших объемах: 100 000 строк записываются за несколько секунд, а 200 000 строк могут занять уже несколько минут.
Рассмотрим четыре альтернативных метода, которые позволяют решить эту задачу за считанные секунды.
---
Если у вас есть возможность использовать внешнюю компоненту 1С++, этот вариант является абсолютным фаворитом по соотношению простоты реализации и скорости работы. Индексированная Таблица значений (ИТЗ) имеет собственные высокооптимизированные методы сериализации, написанные на чистом C++, которые полностью избавлены от платформенного бага нелинейного роста времени.
Давайте разберем, как преобразовать обычную ТЗ в индексированную, быстро сохранить ее в файл, а затем прочитать обратно:
// Пример сохранения ТЗ на диск через ИТЗ
Процедура СохранитьТЗ_1СПП(ИсходнаяТЗ, ПутьКФайлу)
// Создаем объект Индексированная Таблица
ИТЗ = СоздатьОбъект("ИндексированнаяТаблица");
// Загружаем данные из обычной ТЗ в ИТЗ (происходит мгновенно в памяти)
ИТЗ.Загрузить(ИсходнаяТЗ);
// Сохраняем во внутреннем формате.
// Формат 1 сохраняет внутреннее представление объектов 1С (ссылки на справочники, документы)
ИТЗ.ВФайл(ПутьКФайлу, 1);
КонецПроцедуры
Для восстановления данных из файла обратно в стандартную ТЗ используем следующий код:
// Пример загрузки ТЗ с диска через ИТЗ
Функция ЗагрузитьТЗ_1СПП(ПутьКФайлу)
ИТЗ = СоздатьОбъект("ИндексированнаяТаблица");
// Загружаем данные из файла
ИТЗ.ИзФайла(ПутьКФайлу, 1);
// Выгружаем данные обратно в стандартную ТЗ
ВыходнаяТЗ = СоздатьОбъект("ТаблицаЗначений");
ИТЗ.Выгрузить(ВыходнаяТЗ);
Возврат ВыходнаяТЗ;
КонецФункции
Результаты замера производительности: Таблица значений объемом в 100 000 строк сохраняется через ИТЗ всего за 2 секунды вместо 70 секунд стандартным методом! Обратное чтение выполняется столь же мгновенно.
---
Если вы хотите обойтись штатными возможностями операционной системы без подключения сторонних компонент (таких как 1С++), наилучшим решением будет использование COM-объекта Windows для работы с файловой системой — Scripting.FileSystemObject.
Этот объект обходит внутренние механизмы буферизации текста 1С и производит прямую потоковую запись на диск, работая на уровне операционной системы.
Проанализируем пример быстрой выгрузки плоской ТЗ (содержащей строки и числа) в текстовый файл с разделителем:
// Быстрое сохранение через FSO
Процедура СохранитьТЗ_FSO(лТЗ, ИмяФайла)
fso = СоздатьОбъект("Scripting.FileSystemObject");
// Параметры метода CreateTextFile: Имя файла, Перезаписывать (Истина), Unicode (Ложь, пишем в ANSI)
file = fso.CreateTextFile(ИмяФайла, -1, 0);
лТЗ.ВыбратьСтроки();
Пока лТЗ.ПолучитьСтроку() = 1 Цикл
// Формируем строку с разделителем "|"
лСтрока = "" + лТЗ.Строка + "|" + лТЗ.Число;
file.WriteLine(лСтрока);
КонецЦикла;
file.Close();
КонецПроцедуры
Для загрузки такого файла обратно в 1С 7.7 нам потребуется написать функцию разбора строк (парсинга). Посмотрим на пример кода загрузки:
// Быстрая загрузка через FSO
Процедура ЗагрузитьТЗ_FSO(лТЗ, ИмяФайла)
лТЗ.Очистить();
лТЗ.НоваяКолонка("Строка");
лТЗ.НоваяКолонка("Число");
fso = СоздатьОбъект("Scripting.FileSystemObject");
Если fso.FileExists(ИмяФайла) = 0 Тогда
Предупреждение("Файл кэша не найден!");
Возврат;
КонецЕсли;
// Открываем файл для чтения (параметр 1)
file = fso.OpenTextFile(ИмяФайла, 1);
Пока file.AtEndOfStream = 0 Цикл
лСтрока = file.ReadLine();
// Разбираем строку по разделителю "|"
Поз = Найти(лСтрока, "|");
Если Поз > 0 Тогда
лТЗ.НоваяСтрока();
лТЗ.Строка = Лев(лСтрока, Поз - 1);
лТЗ.Число = Число(Сред(лСтрока, Поз + 1));
КонецЕсли;
КонецЦикла;
file.Close();
КонецПроцедуры
Результаты замера производительности: Запись 100 000 строк через FSO занимает всего 2 секунды. Запись гигантского объема в 1 000 000 (один миллион!) строк занимает около 25 секунд, что является выдающимся результатом для среды 1С 7.7.
---
Объект XBase встроен в платформу 1С 7.7 изначально и предоставляет прямой доступ к механизму DBF-файлов. Этот способ хорош тем, что нам не нужно заботиться о ручном парсинге разделителей строк, типизация данных (строка, число, дата) сохраняется автоматически на уровне структуры файла DBF.
Для достижения максимальной скорости записи в DBF следуйте важному правилу: никогда не создавайте индексные файлы (CDX) при сохранении временного кэша. Запись в индексируемую таблицу происходит в разы медленнее, так как при добавлении каждой строки перестраивается индексное дерево.
Посмотрим на пример реализации сохранения:
// Сохранение ТЗ в формат DBF
Процедура СохранитьТЗ_DBF(лТЗ, ИмяФайлаDBF)
// Если файл уже существует, удаляем его
Если ФС.СуществуетФайл(ИмяФайлаDBF) = 1 Тогда
ФС.УдалитьФайл(ИмяФайлаDBF);
КонецЕсли;
ДБФ = СоздатьОбъект("XBase");
// Определяем поля, соответствующие колонкам ТЗ
ДБФ.ДобавитьПоле("STR_COL", 2, 100, 0); // Тип 2 - Строка, длина 100
ДБФ.ДобавитьПоле("NUM_COL", 1, 15, 2); // Тип 1 - Число, длина 15, точность 2
// Создаем файл структуры без CDX индекса
ДБФ.СоздатьФайл(ИмяФайлаDBF);
лТЗ.ВыбратьСтроки();
Пока лТЗ.ПолучитьСтроку() = 1 Цикл
ДБФ.Добавить();
ДБФ.STR_COL = лТЗ.Строка;
ДБФ.NUM_COL = лТЗ.Число;
ДБФ.Записать();
КонецЦикла;
ДБФ.ЗакрытьФайл();
КонецПроцедуры
А теперь восстановим данные из DBF-файла обратно в ТЗ:
// Загрузка ТЗ из DBF
Процедура ЗагрузитьТЗ_DBF(лТЗ, ИмяФайлаDBF)
лТЗ.Очистить();
лТЗ.НоваяКолонка("Строка");
лТЗ.НоваяКолонка("Число");
ДБФ = СоздатьОбъект("XBase");
ДБФ.ОткрытьФайл(ИмяФайлаDBF);
Если ДБФ.Открыта() = 1 Тогда
ДБФ.Первая();
Пока ДБФ.ВКонце() = 0 Цикл
лТЗ.НоваяСтрока();
лТЗ.Строка = ДБФ.STR_COL;
лТЗ.Число = ДБФ.NUM_COL;
ДБФ.Следующая();
КонецЦикла;
ДБФ.ЗакрытьФайл();
КонецЕсли;
КонецПроцедуры
---
Когда объемы данных становятся по-настоящему огромными, а логика отбора требует сложных фильтраций, лучшим решением становится использование базы данных SQLite в оперативной памяти или на диске с помощью компоненты 1sqlite.dll.
Специализированный метод УложитьТЗ() (или его аналог PutVT) выполняет экспорт всей ТЗ на уровне скомпилированного кода базы данных за доли секунды, после чего вы можете сохранить файл базы на диск:
// Схематичный пример укладки ТЗ в локальную базу SQLite
База = СоздатьОбъект("SQLiteDatabase");
База.Открыть("C:\temp\cache.db");
// Укладываем ТЗ во временную или постоянную таблицу
База.УложитьТЗ(лТЗ, "CacheTable", 1);
Загрузка данных обратно осуществляется стандартным SQL-запросом SELECT * FROM CacheTable, результат которого моментально возвращается платформе в виде готовой таблицы значений.
---
Мы подробно разобрали работу с простыми типами (числа и строки). Но что делать, если в ТЗ содержатся ссылки на реальные объекты базы данных — элементы справочников (например, «Номенклатура», «Контрагенты») или документы?
При выгрузке таких данных напрямую через строковые представления (например, FSO) мы потеряем связь с базой данных, получив лишь текстовые наименования. Если же мы будем использовать стандартную сериализацию ЗначениеВСтрокуВнутр() для каждого объекта внутри цикла записи, скорость резко упадет.
Рекомендуем применить следующие подходы для оптимизации:
ВФайл(ИмяФайла, 1) автоматически сохраняет внутренние идентификаторы объектов. При загрузке через ИзФайла() все объектные связи восстанавливаются встроенными механизмами без потери скорости. Это делает связку 1С++ и ИТЗ лучшим выбором для работы со сложными ТЗ.ЗначениеВСтрокуВнутр(), вы можете получать уникальный идентификатор объекта в базе данных 1С (его внутренний ID — 9-значную строку вида " 12345 ") и записывать только его. При чтении файла вы восстанавливаете ссылку по известному типу данных и ID.Проанализировав различные подходы к оптимизации обмена данными ТЗ с диском, мы можем дать следующие рекомендации:
Текст и функцию ЗначениеВФайл при работе с действительно большими массивами информации, чтобы пользователи вашей системы не страдали от длительного ожидания.