Ситуация, когда программа «съедает» всю доступную оперативную память и завершается с ошибкой «Недостаточно памяти», часто встречается при обработке внешних файлов (Excel, CSV) или больших табличных частей. Рассмотрим конкретный пример: у нас есть табличная часть на 300 строк и прайс-лист в формате Excel на 2000 строк. При попытке сопоставить данные во вложенном цикле потребление памяти вырастает до критических 3.5 Гб, и система зависает. Проанализируем причины этой проблемы и разберем способы оптимизации кода.
Прежде всего, важно понимать архитектурные ограничения. Если вы используете 32-битный клиент 1С, то максимальный объем адресного пространства, доступный приложению, ограничен примерно 3.2–3.8 Гб. Как только процесс достигает этого порога, платформа выбрасывает исключение. Переход на 64-битный клиент отодвигает этот порог, но не решает проблему «плохого» кода — программа просто упадет позже, поглотив десятки гигабайт памяти. Поэтому наша задача — сделать код эффективным.
В ходе обсуждения выяснилось, что основной причиной «утечки» стало программное добавление элементов условного оформления внутри цикла. Рассмотрим фрагмент кода, который приводит к катастрофе:
процедура пИзменитьЦветКолонки(знач парЭлемент, парЦвет)
// ОШИБКА: Добавление нового элемента оформления на каждой итерации цикла!
пЭлементУсловногоОформления = УсловноеОформление.Элементы.Добавить();
пЭлементУсловногоОформления.Использование = Истина;
пОформляемоеПоле = пЭлементУсловногоОформления.Поля.Элементы.Добавить();
пОформляемоеПоле.Поле = Новый ПолеКомпоновкиДанных("ТЧНоменклатура");
пЭлементОтбора = пЭлементУсловногоОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
пЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Объект.ТЧ.Номенклатура");
пЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
пЭлементОтбора.ПравоеЗначение = парЭлемент.Номенклатура;
пЭлементУсловногоОформления.Оформление.УстановитьЗначениеПараметра("ЦветФона", парЦвет);
КонецПроцедуры
Разберем по шагам: когда этот метод вызывается внутри вложенного цикла (например, 300 строк ТЧ * 2000 строк прайса = до 600 000 потенциальных вызовов), коллекция УсловноеОформление.Элементы разрастается до гигантских размеров. Каждый элемент оформления — это сложный объект в памяти. Платформа не очищает эти объекты до закрытия формы, что и приводит к переполнению.
Как правильно: Условное оформление нужно настроить один раз (в конфигураторе или при создании формы). Создадим правило: «Если колонка СтатусЦвета равна 1 — красить в зеленый, если 2 — в белый». В цикле же мы будем просто менять число в скрытой колонке СтатусЦвета нашей таблицы.
Обращение к ячейкам ТабличныйДокумент через метод Область() внутри циклов крайне ресурсозатратно. Каждый вызов рТДПрайс.Область("R" + НомерСтроки + "C" + НомерКолонки).Текст порождает временный объект области. При сотнях тысяч итераций диспетчер памяти просто не успевает их утилизировать.
Решение: выгрузим данные из табличного документа в индексированную ТаблицаЗначений перед началом обработки. Это в десятки раз ускорит поиск и снизит нагрузку на память.
Рассмотрим еще одну распространенную ошибку оптимизации — получение реквизитов через навигационную ссылку в цикле:
Если ТипЗнч(парСтрока) = Тип("СправочникСсылка.Номенклатура") Тогда
парСтрока = парСтрока.Наименование; // ОШИБКА: обращение через точку в цикле
КонецЕсли;
Проанализируем ситуацию: при каждом таком обращении 1С не просто считывает одно поле, а полностью загружает объект со всеми его реквизитами и табличными частями в локальный кэш. Кэш живет около 20 минут. Если у вас 300 разных товаров в списке, вы загрузите в память 300 тяжелых объектов СправочникОбъект.
Рекомендация: получайте данные запросом или используйте метод ОбщегоНазначения.ЗначениеРеквизитаОбъекта(), если работаете в типовых конфигурациях. Еще лучше — подготовить структуру или соответствие (Map) со всеми нужными данными до начала цикла.
Рассмотрим, как должен выглядеть оптимизированный процесс сопоставления цен:
ТаблицаЗначений. Оставим только нужные колонки (Артикул, Цена).Найти().Пример оптимизированного кода:
// 1. Подготовка таблицы прайса
ТЗПрайс = Новый ТаблицаЗначений;
ТЗПрайс.Колонки.Добавить("КлючПоиска", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(100)));
ТЗПрайс.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
// Заполняем ТЗ из табличного документа (один проход)
Для пСтр = 1 По рТДПрайс.ВысотаТаблицы Цикл
НоваяСтр = ТЗПрайс.Добавить();
// Сразу нормализуем строку поиска (убираем пробелы и т.д.)
НоваяСтр.КлючПоиска = фСтрокаБезПробелов(рТДПрайс.Область(пСтр, НомерКолонкиКода).Текст);
НоваяСтр.Цена = Число(фСтрокаБезПробелов(рТДПрайс.Область(пСтр, НомерКолонкиЦены).Текст));
КонецЦикла;
// 2. Индексация для мгновенного поиска
ТЗПрайс.Индексы.Добавить("КлючПоиска");
// 3. Основной цикл обработки
Для Каждого СтрТЧ Из Объект.ТЧ Цикл
КлючПоискаТЧ = фСтрокаБезПробелов(СтрТЧ.Артикул);
// Вместо вложенного цикла используем метод Найти() по индексу
НайденнаяСтрока = ТЗПрайс.Найти(КлючПоискаТЧ, "КлючПоиска");
Если НайденнаяСтрока <> Неопределено Тогда
// Обработка данных
СтрТЧ.ЦенаЗакупки = НайденнаяСтрока.Цена;
// Вместо вызова пИзменитьЦветКолонки просто ставим признак
СтрТЧ.СтатусОформления = 1;
КонецЕсли;
КонецЦикла;
В функции очистки пробелов фСтрокаБезПробелов часто используют конструкцию Пока СтрНайти(...) Цикл СтрЗаменить(...). Помните, что в 1С строки неизменяемы. Каждая операция СтрЗаменить создает в памяти новую копию строки. Для больших текстов это создает колоссальную нагрузку. Используйте СтрЗаменить() один раз — он по умолчанию заменяет сразу все вхождения искомого символа. Также эффективно работает связка СтрСоединить(СтрРазложить(ВходнаяСтрока, " "), "").
Подведем итог: для предотвращения переполнения памяти всегда минимизируйте количество создаваемых объектов внутри циклов. Используйте ТаблицаЗначений с индексами вместо ТабличныйДокумент, избегайте обращения к реквизитам через точку и никогда не добавляйте элементы УсловноеОформление динамически в массовых итерациях.