При работе с файлами в 1С, особенно если это выгрузки почтовых сообщений или логов, разработчики часто сталкиваются с проблемой катастрофического падения производительности. В этой статье мы разберем реальный кейс, когда чтение небольшого файла размером 6 МБ занимало более 3 минут, и выясним, как сократить это время до долей секунды. Мы проанализируем типичные ошибки и рассмотрим несколько профессиональных подходов к решению задачи.
Представим ситуацию: у нас есть текстовый файл (структура MIME/EML), содержащий вложение в формате PDF, закодированное в Base64. Нам необходимо программно найти начало блока данных, собрать строку Base64 и преобразовать её в объект ДвоичныеДанные. Рассмотрим исходный код, который привел к задержкам:
ЧтениеТекста = Новый ЧтениеТекста(Файл.ПолноеИмя, КодировкаТекста.ANSI);
ТекущаяСтрока = ЧтениеТекста.ПрочитатьСтроку();
Пока ТекущаяСтрока <> Неопределено Цикл
ТекущаяСтрока = ЧтениеТекста.ПрочитатьСтроку();
Если СтрНачинаетсяС(ТекущаяСтрока, "Content-Transfer-Encoding: base64") Тогда
СтрокаБазе64 = "";
ТекущаяСтрокаДД = ЧтениеТекста.ПрочитатьСтроку();
Пока ТекущаяСтрокаДД <> Неопределено Цикл
ТекущаяСтрокаДД = ЧтениеТекста.ПрочитатьСтроку();
Если СтрНачинаетсяС(ТекущаяСтрокаДД, "--") Тогда
Прервать;
КонецЕсли;
// ГЛАВНАЯ ОШИБКА ЗДЕСЬ:
СтрокаБазе64 = СтрокаБазе64 + ТекущаяСтрокаДД;
КонецЦикла;
МассиДД.Добавить(Base64Значение(СтрокаБазе64));
КонецЕсли;
КонецЦикла;
В чем причина низкой скорости? Проблема заключается в строке СтрокаБазе64 = СтрокаБазе64 + ТекущаяСтрокаДД. В платформе 1С (как и во многих других языках программирования) строки являются неизменяемыми (immutable) объектами. Это означает, что при каждой операции сложения система не просто добавляет символы в конец, а создает в оперативной памяти новую строку, копируя в неё содержимое старой и добавляя новую порцию данных.
Для файла в 6 МБ, который читается построчно (например, по 76 символов в строке Base64), выполняются десятки тысяч операций перевыделения памяти и копирования все возрастающих объемов данных. К концу процесса каждое добавление новой строки заставляет систему копировать мегабайты данных в новое место памяти, что и вызывает "зависание" на несколько минут.
Проанализируем самый простой способ оптимизации, не меняющий логику последовательного чтения. Вместо постоянного сложения строк мы будем накапливать части строки в массиве, а затем один раз объединим их.
Разберем этот метод по шагам:
Массив для временного хранения строк.Добавить().СтрСоединить(МассивРезультат, "").Этот подход работает значительно быстрее, так как 1С при выполнении СтрСоединить заранее вычисляет необходимый объем памяти для итоговой строки и выполняет копирование данных всего один раз.
Если мы хотим добиться максимальной производительности, нам следует отказаться от высокоуровневого объекта ЧтениеТекста в пользу работы с двоичными данными. Начиная с версии платформы 8.3.9, доступны объекты ЧтениеДанных и БуферДвоичныхДанных.
Рассмотрим, почему это эффективнее:
ЧтениеТекста всегда пытается интерпретировать байты как символы в определенной кодировке, что требует ресурсов процессора.БуферДвоичныхДанных позволяет выделять фиксированные объемы памяти и работать с ними напрямую.Пример логики работы с буфером:
ФайлПоток = Новый ФайловыйПоток(ПутьКФайлу, РежимОткрытияФайла.Открыть);
Чтение = Новый ЧтениеДанных(ФайлПоток);
// Ищем маркеры начала и конца блока через методы поиска в буфере
// Используем ПолучитьБуферДвоичныхДанныхИзBase64БуфераДвоичныхДанных()
Такой подход позволяет избежать создания гигантских строк в памяти и работает на уровне скорости дисковой подсистемы.
Рассмотрим решение, к которому пришел автор темы на форуме. Если данных очень много и мы не хотим перегружать оперативную память хранением промежуточных строк (даже в массиве), можно использовать временный файл.
Алгоритм действий следующий:
ПолучитьИмяВременногоФайла().ЗаписьТекста.ДвоичныеДанные.Этот метод эффективно разгружает оперативную память, перенося нагрузку на кэш диска, что в операционных системах Windows/Linux работает крайне быстро для файлов среднего размера.
Проанализируем структуру файла из примера. Мы видим заголовки Content-Type: application/pdf и Content-Transfer-Encoding: base64. Это стандартный формат электронного письма (MIME). В 1С есть мощный встроенный инструмент для работы с такими файлами — объект ИнтернетПочтовоеСообщение.
Вместо того чтобы изобретать собственный парсер, мы можем сделать следующее:
Сообщение = Новый ИнтернетПочтовоеСообщение;
Сообщение.Прочитать(ПутьКФайлу);
// Все вложения уже извлечены и находятся в коллекции Сообщение.Вложения
Для Каждого Вложение Из Сообщение.Вложения Цикл
ДвоичныеДанныеФайла = Вложение.Данные;
КонецЦикла;
Преимущества этого способа: 1. Надежность: Платформа сама корректно обработает сложные случаи (разные кодировки заголовков, вложенные части сообщения, разные типы разделителей). 2. Скорость: Разбор MIME-структуры реализован на уровне ядра платформы (C++), что всегда быстрее кода на встроенном языке. 3. Чистота кода: Вам не нужно писать циклы поиска строк и следить за памятью.
Выясним, какой метод выбрать в зависимости от ситуации:
ИнтернетПочтовоеСообщение.Массив и СтрСоединить. Это самый простой способ исправить тормоза конкатенации.ЧтениеДанных и БуферДвоичныхДанных для потоковой обработки без загрузки всего файла в память.Строка = Строка + НоваяСтрока в циклах, где количество итераций заранее не ограничено малым числом.Применяя эти правила, вы сможете ускорить обработку файлов в десятки и сотни раз, обеспечивая комфортную работу пользователей с системой 1С.