При разработке интеграционных модулей, особенно когда речь идет о выгрузке сотен тысяч строк, программисты часто сталкиваются с резким падением производительности. В процессе замера выясняется, что «узким местом» становятся стандартные строковые функции, в частности СтрЗаменить. Если выполнять замену символов в цикле для каждой из 300 000 строк и более, время выполнения задачи начинает расти нелинейно. В этой статье мы подробно разберем, почему это происходит и какими способами можно кратно ускорить обработку текста в 1С.
Для начала проанализируем ситуацию: строки в платформе 1С являются неизменяемыми (immutable). Это означает, что при каждом вызове функции СтрЗаменить система не правит существующую строку в памяти, а создает её полную копию с внесенными изменениями. Рассмотрим пример: если у вас есть строка из 1000 символов и вы вызываете СтрЗаменить пять раз, система пять раз выделит память под новую строку и скопирует туда данные.
Когда количество таких операций достигает сотен тысяч, менеджер памяти 1С начинает тратить больше времени на выделение новых блоков и сборку мусора, чем на саму полезную работу. Ситуация усугубляется, если замены производятся внутри цикла, который формирует итоговый огромный текст путем конкатенации (склеивания через +). В итоге мы получаем «эффект снежного кома».
Как выяснилось в обсуждении, основной задачей автора было формирование файла в формате JSON. Часто программисты пытаются собирать JSON «руками», используя СтрЗаменить для экранирования спецсимволов (кавычек, обратных слешей и переносов строк). Это самая большая ошибка в плане производительности.
Для работы с этим форматом в 1С 8.3 предусмотрены встроенные объекты ЗаписьJSON и ПрочитатьJSON. Они реализованы на уровне ядра платформы (на языке C++) и выполняют все необходимые преобразования и замены символов в десятки раз быстрее, чем любой код на языке 1С. Разберем правильный подход:
// Вместо ручной сборки строки и СтрЗаменить
Запись = Новый ЗаписьJSON;
ПараметрыЗаписиJSON = Новый ПараметрыЗаписиJSON(, Символы.Таб);
Запись.УстановитьСтроку(ПараметрыЗаписиJSON);
Запись.ЗаписатьНачалоОбъекта();
Запись.ЗаписатьИмяСвойства("Данные");
Запись.ЗаписатьЗначение(МассивОбъектовДляВыгрузки); // Массив структур или соответствий
Запись.ЗаписатьКонецОбъекта();
Результат = Запись.Закрыть();
Используя ЗаписьJSON, вы полностью делегируете платформе задачу по корректной замене символов, что исключает необходимость вызова СтрЗаменить вручную.
Если специфика задачи все же требует ручной обработки текста, проанализируем способ записи данных. Вместо того чтобы накапливать гигантскую строку в оперативной памяти и выполнять замены в ней, следует использовать потоковую запись в файл через объект ЗаписьТекста.
Рассмотрим разницу: при работе с одной огромной строкой СтрЗаменить вынуждена «просматривать» мегабайты данных за один вызов. Если же мы пишем данные порциями, мы выполняем замены только в маленьком фрагменте (буфере) перед его записью на диск. Это позволяет держать нагрузку на память на одном (низком) уровне независимо от итогового размера файла.
Запись = Новый ЗаписьТекста("C:\Temp\Export.txt", КодировкаТекста.UTF8);
Для Каждого СтрокаДанных Из ИсточникДанных Цикл
// Выполняем замену только в короткой строке
ОбработаннаяСтрока = СтрЗаменить(СтрокаДанных, "СтароеЗначение", "НовоеЗначение");
Запись.ЗаписатьСтроку(ОбработаннаяСтрока);
КонецЦикла;
Запись.Закрыть();
Выясним причину медленной работы при большом количестве мелких замен. Каждое обращение к функции СтрЗаменить — это накладные расходы на вызов программного интерфейса. Если вам нужно заменить 5 разных символов в каждой строке, попробуйте изменить логику: сначала соберите массив строк, объедините их в один большой блок текста и выполните замену один раз для всего блока.
Посмотрим на пример оптимизации, предложенный участниками обсуждения. Вместо замены в каждой из 300 000 итераций:
МассивСтрок.Добавить(Текст).СтрСоединить(МассивСтрок).СтрЗаменить над итоговым результатом всего один или несколько раз.Такой подход может дать колоссальный прирост, так как платформа оптимизирована для поиска и замены в больших массивах данных «за один проход», что гораздо эффективнее миллиона мелких вызовов.
Проанализируем ситуацию, когда СтрЗаменить используется для формирования итогового сообщения. Никогда не используйте конструкцию ИтоговаяСтрока = ИтоговаяСтрока + НоваяЧасть в больших циклах. Это классическая анти-паттерн производительности. Правильный способ имитации StringBuilder в 1С выглядит так:
МассивЧастей = Новый Массив;
Для Счетчик = 1 По 300000 Цикл
// Вместо: ИтоговаяСтрока = ИтоговаяСтрока + СтрЗаменить(Данные, " ", "_")
// Используем:
МассивЧастей.Добавить(СтрЗаменить(Данные, " ", "_"));
КонецЦикла;
ИтоговыйТекст = СтрСоединить(МассивЧастей, "");
Добавление элемента в массив — это очень быстрая операция, так как она не требует копирования содержимого строк. Финальный метод СтрСоединить работает на уровне платформы крайне эффективно, заранее вычисляя необходимый объем памяти для итогового результата.
Важно отметить, что сама по себе функция СтрЗаменить в 1С работает достаточно быстро. В ходе обсуждения были приведены результаты теста: замена пробела на подчеркивание в текстовом файле объемом 500 Мб в 1С заняла около 945 мс, в то время как аналогичный код на Delphi выполнил ту же задачу за 828 мс. Разница составляет менее 15%.
Это доказывает, что проблема не в медлительности самой функции, а в том, как она используется. Если вы наблюдаете «нелинейное падение производительности», ищите ошибку в алгоритме:
СтрЗаменить там, где можно применить штатные объекты (JSON, XML).Подводя итог, можно сказать: чтобы ускорить СтрЗаменить, нужно перестать её использовать в циклах над огромными объемами данных. Переходите на ЗаписьJSON, используйте массивы для накопления данных и применяйте потоковую запись в файлы.