Скорость сложения строк большой длины в 1С разными методами (конкатенация строк)

02.01.22

Разработка - Механизмы платформы 1С

Как известно, в 1С со строковыми переменными часто приходится работать в режиме добавления строк в одну переменную. Когда строка небольшой длины, все происходит достаточно быстро и можно работать вот так: Строка1 = "Привет"; Строка2 = "мир!"; Результат = Строка1 + Строка2; И все замечательно ровно до того момента, когда эти строки не становятся большими... Тогда скорость работы значительно падает. Я провел небольшие замеры производительности и выношу их на суд общественности.

Скачать файлы

Наименование Файл Версия Размер
Обработка тестирования конкатенации строк разными способами
.epf 8,57Kb
2
.epf 8,57Kb 2 Скачать

Итак, смоделируем сначала условие задачи.

Задача

Необходимо написать функцию, которая возвращает в строковую переменную текст вида:

Это тестовая строка 1
Это тестовая строка 2
Это тестовая строка 3
...
Это тестовая строка N

Где N - число строк.
Разобрать несколько способов получения такой переменной.
Так же сделать график скорости работы в зависимости от N и сравнить получившиеся функции по производительности.

Решение

На самом деле, несмотря на вымышленность задания подобная задача в реальной жизни, в том или ином виде встречается сплошь и рядом. Есть большой цикл в котором мы результат складываем в текстовую переменную суммой нескольких строк. Так вот, было замечено, что для большого цикла и текста, время выполнения операции сложения строк растет нелинейно и для большого количества итераций не позволительно долго.

Где по оси Х у нас идет количество итераций, а по Y - время в миллисекундах.

1. ТекстовыйДокумент. Как мы видим, этот способ работает медленнее всего:

Результат = Новый ТекстовыйДокумент;
Для Индекс = 1 По КоличествоИтераций Цикл
    Результат.ДобавитьСтроку("Это тестовая строка " + Строка(Индекс));
КонецЦикла;
Строка = Результат.ПолучитьТекст();

2. Строка. Затем у нас идет обычная работа со строкой:

Результат = "";
Для Индекс = 1 По КоличествоИтераций Цикл
    Результат = Результат + "Это тестовая строка " + Строка(Индекс) + Символы.ПС;
КонецЦикла;

3. Шаблон. Интересный предложение по реализации от coollerinc. Отнесся к этому методу скептически изначально, т.к. работа со строками никуда не девается, но ради чистоты эксперимента, добавил тест.

Результат = "";
Шаблон = "%1 %2";
Для Индекс = 1 По КоличествоИтераций Цикл
	Результат = СтрШаблон(Шаблон, Результат, "Это тестовая строка " + Строка(Индекс) + Символы.ПС);
КонецЦикла;

Как я и ожидал, результат не очень... После 10000 итераций способ становится самым медленным (на скриншоте этого не видно, но по факту это так). 

4. Далее по скорости идет работа с ПотокВПамяти + ЗаписьДанных:

Результат = Новый ПотокВПамяти;
ЗаписьДанных = Новый ЗаписьДанных(Результат);
Для Индекс = 1 По КоличествоИтераций Цикл
    ЗаписьДанных.ЗаписатьСтроку("Это тестовая строка " + Строка(Индекс));
КонецЦикла;
ЗаписьДанных.Закрыть();
Строка = ПолучитьСтрокуИзДвоичныхДанных(Результат.ЗакрытьИПолучитьДвоичныеДанные());

5. ПотокВПамяти + ЗаписьТекста. Как оказалось, со строками этот способ работает быстрее, чем ПотокВПамяти + ЗаписьДанных, хотя по идее, корни у них общие, в плане механизмов работы:

Результат = Новый ПотокВПамяти;
ЗаписьТекста = Новый ЗаписьТекста(Результат);
Для Индекс = 1 По КоличествоИтераций Цикл
    ЗаписьТекста.ЗаписатьСтроку("Это тестовая строка " + Строка(Индекс));
КонецЦикла;
ЗаписьТекста.Закрыть();
Строка = ПолучитьСтрокуИзДвоичныхДанных(Результат.ЗакрытьИПолучитьДвоичныеДанные());   

6. ЗаписьXML, тоже работает быстро. Выглядит как какая-то магия:

Результат = Новый ЗаписьXML;
Результат.УстановитьСтроку();
Для Индекс = 1 По КоличествоИтераций Цикл
    Результат.ЗаписатьБезОбработки("Это тестовая строка " + Строка(Индекс) + Символы.ПС);
КонецЦикла;
Строка = Результат.Закрыть();

7. Массив. В массив можно складывать строки и в конце использовать функцию СтрСоединить:

Результат = Новый Массив;
Для Индекс = 1 По КоличествоИтераций Цикл
    Результат.Добавить("Это тестовая строка " + Строка(Индекс));
КонецЦикла;
Строка = СтрСоединить(Результат, Символы.ПС);

8. ЗаписьJSON, работает быстро. Спасибо Rustig, который подсказал добавить и такой вариант для тестирования.

Результат = Новый ЗаписьJSON;
Результат.УстановитьСтроку();
Для Индекс = 1 По КоличествоИтераций Цикл
	Результат.ЗаписатьБезОбработки("Это тестовая строка " + Строка(Индекс) + Символы.ПС);
КонецЦикла;	
Строка = Результат.Закрыть();

Изначально было подозрение, что это будет работать быстрее чем ЗаписьXML, но это оказалось не так. Тесты показывают примерно одинаковое время работы с вариантом 6. Видимо все дело в том, что есть общие механизмы в реализации этих объектов платформы и, видимо, реализация метода ЗаписатьБезОбработки одинаковая.

9. ПотокВПамяти + ЗаписьТекста + начальное выделение памяти под поток. Думалось, что это хороший вариант для тех случаев, когда изначально известен размер данных. Спасибо Perfolenta.

Результат = Новый ПотокВПамяти(КоличествоИтераций * 16 * (СтрДлина("Это тестовая строка ") + 4));
ЗаписьТекста = Новый ЗаписьТекста(Результат);
Для Индекс = 1 По КоличествоИтераций Цикл
	ЗаписьТекста.ЗаписатьСтроку("Это тестовая строка " + Строка(Индекс));
КонецЦикла;
ЗаписьТекста.Закрыть();
Строка = ПолучитьСтрокуИзДвоичныхДанных(Результат.ЗакрытьИПолучитьДвоичныеДанные());	

Длину строки умножаем на 16 накинул на кодировку в UTF-8 и на количество итераций. Получаем примерный размер для потока. Именно примерный. В таки у нас эксперимент и выделенного размера должно быть достаточно. Но оказалось, что по скорости, что с выделением начального объема памяти, что без выделения, все работает примерно одинаково. В комментариях к статье есть мои размышления на эту тему в сторону Capacity.

Замечания и оговорки

При тестировании наткнулся на интересное. На рабочем компьютере медленнее всего работал ТекстовыйДокумент, а потом Строки, а в форуме, на скриншотах, уже самые медленные Строки, а потом ТекстовыйДокумент вместе с Шаблоном. Вопрос. Почему на разных машинах не одинаковая тенденция? Речь именно о тенденции графиков, а не времени выполнения. Т.е. если ТекстовыйДокумент самый медленный на одном компьютере по сравнению с другими, то по идее он должен и на другом быть самым медленным в сравнении, ведь механизм один и тот же. Но это не так. Вопрос остается открытым... Это хорошо видно в обсуждении.

Предварительные выводы

Если у вас строки не большой длины, то можно обойтись и простой суммой двух строковых переменных, вида: Строка1 + Строка2. Если строк достаточно, много то ни в коем случае не использовать работу с ТекстовымДокументом. Лучше выбрать что-то другое. Например, ПотокВПамяти + ЗаписьТекста. Это способ, который позволит работать с длинными строками как в памяти, так и из файла.

Удивило так же, что ПотокВПамяти с начальным выделением памяти не дает прибавки в скорости. Причем не только ощутимой, а вообще нет никакой прибавки.
Почему при работе со строками разными методами, такой большой разброс по времени выполнения - вопрос и, на мой взгляд, самое удивительное в этом, что конкатенация обычных строк - это не самый быстрый способ сложения строк...
Ну а какой из способов использовать - выбирать вам.
Прилагаю обработку, в которой можно поэкспериментировать введя свои значения итераций.

Тестировалось на платформе 8.3.19.1264

PS: Приготовьтесь, что ждать результата теста придется долго :)

PPS: В процессе написания обработки были использованы публикации:

Формирование строки большой длины

Варианты конкатенации строк в 1С и замеры производительности

обработка замер производительности график производительность конкатенация строки сложение

См. также

Поинтегрируем: сервисы интеграции – новый стандарт или просто коннектор?

Обмен между базами 1C Администрирование СУБД Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

В платформе 8.3.17 появился замечательный механизм «Сервисы интеграции». Многие считают, что это просто коннектор 1С:Шины. Так ли это?

11.03.2024    4488    dsdred    53    

71

Как готовить и есть массивы

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Все мы используем массивы в своем коде. Это один из первых объектов, который дают ученикам при прохождении обучения программированию. Но умеем ли мы ими пользоваться? В этой статье я хочу показать все методы массива, а также некоторые фишки в работе с массивами.

24.01.2024    5286    YA_418728146    25    

63

Планы обмена VS История данных

Обмен между базами 1C Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Вы все еще регистрируете изменения только на Планах обмена и Регистрах сведений?

11.12.2023    6401    dsdred    36    

111

1С-ная магия

Механизмы платформы 1С Бесплатно (free)

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    18467    SeiOkami    46    

118

Дефрагментация и реиндексация после перехода на платформу 8.3.22

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Начиная с версии платформы 8.3.22 1С снимает стандартные блокировки БД на уровне страниц. Делаем рабочий скрипт, как раньше.

14.09.2023    12086    human_new    27    

74

Валидация JSON через XDTO (включая массивы)

WEB-интеграция Универсальные функции Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    8807    YA_418728146    6    

141

Внешние компоненты Native API на языке Rust - Просто!

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Внешние компоненты для 1С можно разработывать очень просто, пользуясь всеми преимуществами языка Rust - от безопасности и кроссплатформенности до удобного менеджера библиотек.

20.08.2023    6274    sebekerga    54    

94

Все скопируем и вставим! (Буфер обмена в 1С 8.3.24)

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Рассмотрим новую возможность 8.3.24 и как её можно эффективно использовать

27.06.2023    15976    SeiOkami    31    

103
Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. coollerinc 185 02.01.22 20:42 Сейчас в теме
Можно еще вариант проверить:

Результат = "";
Шаблон = "1% 2% 
|";
Для Индекс = 1 По КоличествоИтераций Цикл
 
Результат = СтрШаблон(Шаблон, Результат ,  "Это тестовая строка " + Строка(Индекс) );

КонецЦикла;



Обычную работу со строкой(Результат = Результат + "Это тестовая строка " + Строка(Индекс) + Символы.ПС;), когда большие строки и несколько слогаемых, лучше не использовать. Фактически это интерпретируется примерно так:

ПромежуточныйРезультат1 = Результат + "Это тестовая строка ";
ПромежуточныйРезультат2 = ПромежуточныйРезультат1 + Строка(Индекс) ;
Результат = ПромежуточныйРезультат2 + Символы.ПС;

Это одновременно в нескольких переменных хранится почти одно и тоже, что отжирает память.
DrAku1a; bulpi; Созинов; tormozit; +4 Ответить
6. Diversus 2306 03.01.22 13:29 Сейчас в теме
(1)
Обычную работу со строкой(Результат = Результат + "Это тестовая строка " + Строка(Индекс) + Символы.ПС;), когда большие строки и несколько слогаемых, лучше не использовать. Фактически это интерпретируется примерно так:

ПромежуточныйРезультат1 = Результат + "Это тестовая строка ";
ПромежуточныйРезультат2 = ПромежуточныйРезультат1 + Строка(Индекс) ;
Результат = ПромежуточныйРезультат2 + Символы.ПС;

Это одновременно в нескольких переменных хранится почти одно и тоже, что отжирает память.


Проверил ваш вариант. Он на графике фиолетовый.
Уступает вообще всем вариантам на больших значениях итераций. Чуть-чуть его видоизменил:

Результат = "";
Шаблон = "%1 %2";
Для Индекс = 1 По КоличествоИтераций Цикл
	Результат = СтрШаблон(Шаблон, Результат, "Это тестовая строка " + Строка(Индекс) + Символы.ПС);
КонецЦикла;
Прикрепленные файлы:
ixijixi; mrChOP93; coollerinc; +3 Ответить
2. RustIG 1351 03.01.22 09:59 Сейчас в теме
1. Для истории обсуждения оставлю ссылку на последовательное чтение и запись файлов через Потоки https://wonderland.v8.1c.ru/blog/novye-instrumenty-dlya-raboty-s-dvoichnymi-dannymi-obespechivayut-kak-posledovatelnyy-dostup-k-danny/?sphrase_id=268058

Вот здесь было обсуждение - продолжение темы потоков ЧтениеДанных и ЗаписьДанных. Работа со строками

2. Запись в json насколько быстрее записи в xml? Мне кажется, запись в джейсон должна быть быстрее.

3. Использование смешанных технологий - частями собираем тексты, абзацы, параграфы, блоки в цикле, затем последовательно их соединяем в промежуточные строки (или сразу в итоговую строку), затем промежуточные строки собираем в итоговую строку.

4. Работу со строками можно также увидеть и проанализировать в технологии обменов - для транспорта собирается не один большой файл, а несколько небольших файлов - стоит проверка на количество отправляемых данных - один файл формируется , например, только для не более 1000 объектов. То есть все понимают, что собрать и отправить файл большего размера - это значит столкнуться с другими проблемами на каждом шаге - сборка большого файла, отправка по интернету, далее чтение большого файла....На каждом шаге есть свои ограничения... Поэтому возможно лучше в цикле собрать несколько файлов, а не один итоговый....И будет быстрее.

5. Как прочитать и обойти весь справочник Номенклатура? Можно через запрос, и получить "нехватку памяти", а можно через объектную модель Выборка = Справочники.Номенклатура.Выбрать(); Пока Выборка.Следующий() Цикл....
Собственно ограничения заложены изначально, поэтому обход цикла и сборка файла должны проходить с пониманием ограничений - на твоем компе какое железо? тот же самый алгоритм на моем ноуте будет работать в два раза медленнее и вылетит в ошибку нехватки памяти...

6. Современные технологии позволяют распараллелить вычисления Параллельные вычисления
Задача собрать строку - хорошо распараллеливается: ИтоговаяСтр = Стр1 + Стр2 + Стр3
- полагаю, выигрыш может быть положительный.
CratosX; rusmil; +2 Ответить
3. Cmapnep 18 03.01.22 10:07 Сейчас в теме
Плюсанул, но раз уж речь о больших данных, то напрашивается еще и сравнение по используемой памяти!
dhurricane; +1 Ответить
4. Diversus 2306 03.01.22 13:20 Сейчас в теме
(2) При чем здесь запись в JSON и XML? Или выборка из справочника Номенклатура? Я не совсем понимаю при чем здесь это?
В статье, я пытаюсь решить классическую задачу с которой сталкивается каждый, а именно: работа со строками большой длины, в цикле с формированием большого текста. Это может быть большой HTML-документ (я в курсе про DOM-модель и про его формирование другим способом), большой какой-то текстовый файл и его обработка, или в большом цикле работа со строками и т.д. и т.п. Мое "микро-исследование" про влияние размера строк на скорость работы.
Понятно, что от компьютера к компьютеру скорость работы будет отличаться, но я думаю, что график работы как раз для этого случая. Он позволяет увидеть общую тенденцию. Как пример: на больших строках самый медленный - это ТекстовыйДокумент. Это будет так хоть на мощном компьютере, хоть на "дохлом" ноуте.
Теперь о конструктиве. По п.6 надо подумать как это втиснуть в текущий тест.
(3)
Плюсанул, но раз уж речь о больших данных, то напрашивается еще и сравнение по используемой памяти!

Замер используемой памяти? Хм. Когда в 1С появился профилировщик памяти? ТЖ, насколько я знаю, позволяет включить мониторинг утечек памяти, но здесь то утечек памяти нет, да и моментом очистки памяти разработчик в явном виде управлять не может. Включить и выключить мониторинг потребляемой памяти для конкретной процедуры в 1С, опять же, на сколько я знаю, такой возможности нет. Если ошибаюсь и это возможно, то поправьте меня пожалуйста. Был бы признателен. Спасибо.
5. RustIG 1351 03.01.22 13:29 Сейчас в теме
(4)
При чем здесь запись в JSON и XML?


в твоем микро-исследовании ты написал:

2. ЗаписьXML, тоже работает быстро. Выглядит как какая-то магия:

Результат = Новый ЗаписьXML;
Результат.УстановитьСтроку();
Для Индекс = 1 По КоличествоИтераций Цикл
Результат.ЗаписатьБезОбработки("Это тестовая строка " + Строка(Индекс) + Символы.ПС);
КонецЦикла;
Строка = Результат.Закрыть();


Хотелось бы увидеть разницу с записью в джейсон. Есть предположение, что магия будет интересней.
Особенно при записи текста для html-страницы.
8. Diversus 2306 03.01.22 13:44 Сейчас в теме
(5)
Хотелось бы увидеть разницу с записью в джейсон. Есть предположение, что магия будет интересней.

Замысел понял.
Проверил, примерно одинаково работают. Причем где-то дольше XML, где-то JSON на 1-2 единицы. Так что примерно равны по скорости. Как я понимаю, равенство потому, что используется метод ЗаписатьБезОбработки и там и там. А т.к. скорее всего внутри ЗаписьXML и ЗаписьJSON имеют в реализации общего класса-предка, который с записанными строками без обработки работает одинаково.

Проверяемый блок кода:
Результат = Новый ЗаписьJSON;
Результат.УстановитьСтроку();
Для Индекс = 1 По КоличествоИтераций Цикл
	Результат.ЗаписатьБезОбработки("Это тестовая строка " + Строка(Индекс) + Символы.ПС);
КонецЦикла;	
Строка = Результат.Закрыть();
Прикрепленные файлы:
starik-2005; CratosX; ixijixi; +3 Ответить
7. RustIG 1351 03.01.22 13:42 Сейчас в теме
(4)
выборка из справочника Номенклатура?

В платформе 1с имеются известные способы обхода нехватки памяти и ускорения обхода цикла для большого массива данных. Простой и показательный пример со справочником Номенклатура - об этом также на ИТС написано - не держать в памяти весь массив данных, а последовательно его считывать - это обычная практика работы с большими массивами данных.
Задача записи длинной строки (которая формируется в цикле) - делится собственно на две -
1) откуда и как берутся сведения (данные) - как раз я привел пример как можно ускорить в целом алгоритм
2) как записывать в строку - последовательно или кучей - как раз это и есть та классическая задача, с которой ты начал.

Еще раз, скорость прохождения алгоритма для Записи строки (любой вариант возьми) по справочнику Номенклатура будет разным для случая обхода через объектную модель и через Запрос по всему справочнику и дальнейшему обходу по результату выполнения запроса....

В общем разная скорость сначала считывания данных, и скорость записи в строку - для определенного варианта постоянна. Итоговая скорость разная...
Diversus; +1 Ответить
9. Diversus 2306 03.01.22 13:54 Сейчас в теме
(7) Мысль понял. Спасибо.
И кстати, если обратить внимание на скриншоты в публикации и в форуме, то можно заметить разницу между скоростью работы со строками и текстовым документом.
В публикации самый медленный ТекстовыйДокумент, а потом Строки, а в форуме уже самые медленные Строки, а потом ТекстовыйДокумент.
Дело в том, что публикацию я писал на работе, а там стоит компьютер на AMD Ryzen 3700 + 32 Гб ОЗУ, а в форуме я отвечаю на компьютере Intel Core-i5 9600К + 32 Гб. Отсюда такая разница. Интересно, почему такой разброс на машинах с разной архитектурой...
10. gzharkoj 502 04.01.22 11:18 Сейчас в теме
(9) Может потому, что память на разной частоте работает.
63. starik-2005 3033 01.02.22 17:54 Сейчас в теме
(10) предположу, что дома память должна быть лучше, чем на работе )))
Также предположу, что на работе может стоять ECC-память, которую дома вообще никак не поставить (ибо там 9600К)
62. starik-2005 3033 01.02.22 17:52 Сейчас в теме
(9)
Дело в том, что публикацию я писал на работе, а там стоит компьютер на AMD Ryzen 3700 + 32 Гб ОЗУ, а в форуме я отвечаю на компьютере Intel Core-i5 9600К + 32 Гб. Отсюда такая разница. Интересно, почему такой разброс на машинах с разной архитектурой...
Интел до 11-й серии практически не менял IPS (инструкций за время) для целочисленной арифметики, а АМД с ядра ZEN повысила этот IPS достаточно ощутимо. Также стоит иметь ввиду, что у АМД сильно больше кеша 3-го уровня, чем у Интела. Поэтому разные архитектуры могут дать разные результаты, связанные с конкретным кодом, отвечающим за реализацию того или иного метода (в данном случае сложения строк). Если я не ошибся, глядя на картинки, то Интел тут проигрывает АМД весьма существенно. Могу протестировать на 5600X если хотите - увидим, что остается от 1С на Интеле, если убрать СУБД.
64. starik-2005 3033 01.02.22 18:32 Сейчас в теме
(9) Вот что в итоге получилось на Linux (ядро 5.15.0-16.2-liquorix-amd64) + 5600X (частота памяти будь здоров!) Выиграл массив как по мне так...
Прикрепленные файлы:
11. Perfolenta 204 04.01.22 11:36 Сейчас в теме
В целом результаты соответствуют обычной житейской логике... что бы сложить две строки надо выделить кусок памяти равный сумме длин строк и скопировать в него обе строки... по мере роста одной из них, время её копирования растет, что делает процесс не линейным, а так же приводит к фрагментации памяти...
Соответственно, предварительно накапливая строки, мы получаем возможность выделить память один раз и скопировать все строки в итоговую за один проход... общее выделение памяти будет больше, а фрагментация меньше...
Можно попробовать ещё вариант через ПотокВПамяти, но не так как у автора, а с предварительным выделением памяти под суммарную длину строк, если длина всех строк заранее известна... дело в том, что если заранее не указать необходимый размер потока, то в игру вступает внутренняя логика автоматического увеличения размера буфера при добавлении данных, что ведет к дополнительным расходам...
С текстовым документом особая история, т.к. внутри себя, при добавлении строки он скорее всего сканирует её на предмет наличия разделителей строки...
12. Diversus 2306 04.01.22 14:24 Сейчас в теме
(11)
Можно попробовать ещё вариант через ПотокВПамяти, но не так как у автора, а с предварительным выделением памяти под суммарную длину строк, если длина всех строк заранее известна... дело в том, что если заранее не указать необходимый размер потока, то в игру вступает внутренняя логика автоматического увеличения размера буфера при добавлении данных, что ведет к дополнительным расходам...

Провел тест и ничего особо не меняется при назначении сразу нужного объема.
Скорее всего там все обстоит точно так же, как и в других ЯП с подобными структурами. Насколько я знаю есть несколько стратегий работы с памятью с использованием Capacity (емкость), которую не надо путать с размером.
Грубо говоря, Capacity - это объем сколько занимает память под объект, когда реальный размер занимаемых данных полностью заполнен этот объем увеличивается. Причем не на 1, а сразу в 2 раза, или как-то еще.
Таким образом, ПотокВПамяти не перераспределяет память каждый раз, когда что-то вставляется, а только тогда, когда дополнительная память исчерпана.
Пример как оно работает в других ЯП (схематично): Пусть начальное количество элементов в массиве - 0, начальное значение Capacity массива - 8, и размер одного элемента 1 байт (для простоты).
Теперь в цикле мы добавляем по 1 элементу в массив. Как только количество элементов станет равным 8, Capacity (емкость) увеличится до 16, как только количество элементов станет 16, Capacity увеличится до 32, когда дойдет до 32, произойдет увеличение и выделение памяти сразу под 64 элемента и т.д.
Такой подход позволяет достаточно быстро увеличить емкость массива и на больших массивах перераспределение памяти будет происходить не часто. При этом память будет использоваться достаточно экономно.
Понятно, это всего лишь пример. В реальной жизни увеличение Capacity может быть не на 2, а на 1.5 или еще как-то, как решат разработчики. Да и вообще там все несколько сложнее, на самом деле :)

Использовал код:
Результат = Новый ПотокВПамяти(КоличествоИтераций * 16 * (СтрДлина("Это тестовая строка ") + 4)); // UTF
ЗаписьТекста = Новый ЗаписьТекста(Результат);
Для Индекс = 1 По КоличествоИтераций Цикл
	ЗаписьТекста.ЗаписатьСтроку("Это тестовая строка " + Строка(Индекс));
КонецЦикла;
ЗаписьТекста.Закрыть();
Строка = ПолучитьСтрокуИзДвоичныхДанных(Результат.ЗакрытьИПолучитьДвоичныеДанные());
Показать
Прикрепленные файлы:
13. Perfolenta 204 04.01.22 19:23 Сейчас в теме
(12) дело в том, что используемый код сам слишком медленный, что бы заметить эффекты от изменения метода выделения памяти...
например, если избавиться от лишних итераций цикла, то сразу можно получить большой выигрыш:
КоличествоИтераций=100000;	

Стр="";
Для Индекс = 1 По 1000 Цикл
    Стр=Стр+"Ф";
КонецЦикла;

т=ТекущаяУниверсальнаяДатаВМиллисекундах();
Результат = Новый ПотокВПамяти(КоличествоИтераций * 16 * (СтрДлина(Стр) + 4)); // UTF
ЗаписьТекста = Новый ЗаписьТекста(Результат);
Для Индекс = 1 По КоличествоИтераций/10 Цикл
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
    ЗаписьТекста.ЗаписатьСтроку(Стр);
КонецЦикла;
ЗаписьТекста.Закрыть();
Строка = ПолучитьСтрокуИзДвоичныхДанных(Результат.ЗакрытьИПолучитьДвоичныеДанные());	
Сообщить(ТекущаяУниверсальнаяДатаВМиллисекундах()-т);


т=ТекущаяУниверсальнаяДатаВМиллисекундах();
Результат = Новый ПотокВПамяти;
ЗаписьТекста = Новый ЗаписьТекста(Результат);
Для Индекс = 1 По КоличествоИтераций Цикл
    ЗаписьТекста.ЗаписатьСтроку(Стр);
КонецЦикла;
ЗаписьТекста.Закрыть();
Строка = ПолучитьСтрокуИзДвоичныхДанных(Результат.ЗакрытьИПолучитьДвоичныеДанные());  
Сообщить(ТекущаяУниверсальнаяДатаВМиллисекундах()-т);

Показать
15. Perfolenta 204 05.01.22 05:37 Сейчас в теме
(12) Кстати, интересно, когда я запускаю этот код на 1С, то версия с предварительным выделением памяти почему-то работает чуть-чуть медленнее, чем с автоматическим выделением... примерно на 7%... это странно, ведь казалось бы, должно быть наоборот...
Когда же я запускаю этот же код на своей Перфоленте, то версия с предварительным выделением памяти работает быстрее в среднем на 11%, как и предполагалось...
Ещё одна странность, я просто копипастил Ваш код, но у меня в 1С ТекстовыйДокумент работает значительно быстрее, чем простая конкатенация строк, Не так уж и много отставая от потоков... возможно это зависит от версии платформы...
И ещё, в Вашем коде для простой конкатенации можно сделать одно маленькое улучшение, значительно увеличивающее производительность, надо всего-лишь взять часть выражения в скобки:
Результат = "";
Для Индекс = 1 По КоличествоИтераций Цикл
    Результат = Результат + ("Это тестовая строка " + Строка(Индекс) + Символы.ПС);
КонецЦикла;
Diversus; +1 Ответить
16. Diversus 2306 05.01.22 12:53 Сейчас в теме
(15)
И ещё, в Вашем коде для простой конкатенации можно сделать одно маленькое улучшение, значительно увеличивающее производительность, надо всего-лишь взять часть выражения в скобки

Да! По правилам то, что в скобках вычисляется первым, а потом добавить к большой строке одной строкой, а если без скобок, то все подстроки добавляются к большой строке по очереди, что при работе с большими строками будет медленней. Отличное предложение.
29. Darklight 32 10.01.22 13:08 Сейчас в теме
(16)Ну, для чистоты эксперимента - наверное во всех тестах надо было бы сначала сформировать добавляемую строку в отдельной переменной - а уже потом ей конкатенировать в общую.
Или вообще остановиться на конкатенации константной строки - чтобы исключить затраты времени на её формирование!
Но замечание, безусловно, архиполезное - но существенное только для прямой конкатенации строк!
14. ivanov660 4330 04.01.22 23:09 Сейчас в теме
Можно было бы табличку с позициями нарисовать, по картинкам некоторые цвета сливаются и тяжело воспринимается.
17. Идальго 226 05.01.22 23:16 Сейчас в теме
Может дело в особенностях строкового типа и его хранения в памяти(в куче)? Былож что-то такое, ну, типа оно в куче каждый раз создает новую переменную (равную предыдущей плюс приконкатенированная часть) - и так, для каждой итерации цикла. В результате, получается неэффективное использование памяти.
18. DrAku1a 1679 07.01.22 11:46 Сейчас в теме
Интересная тема. После статьи "Самый быстрый FizzBuzz на 1С" считал, что через массив - самый быстрый вариант, и уж точно быстрее, чем сложение строк.
Для чистоты эксперимента, стоило бы проверить такой вариант:
Переменная1 = "Это тестовая строка 1";
Переменная2 = "Это тестовая строка 2";
...
Переменная1000 = "Это тестовая строка 1000";

//***начать отсчет времени ***

МассивСтрок = новый Массив();
МассивСтрок.Добавить(Переменная1);
МассивСтрок.Добавить(Переменная2);
...
МассивСтрок.Добавить(Переменная1000);
Строка = СтрСоединить(МассивСтрок, Символы.ПС);

//*** Закончить замер времени ***

Сообщить(Строка);
Показать
ardn; RustIG; +2 Ответить
19. RustIG 1351 09.01.22 15:57 Сейчас в теме
(0)
7. Массив. В массив можно складывать строки и в конце использовать функцию СтрСоединить:

Результат = Новый Массив;
Для Индекс = 1 По КоличествоИтераций Цикл
Результат.Добавить("Это тестовая строка " + Строка(Индекс));
КонецЦикла;
Строка = СтрСоединить(Результат, Символы.ПС);

А что если рассмотреть строку как небольшую "книгу"?
Книга - это двумерный массив (n,m) - где n - количество страниц, m - количество строк на странице
сначала собираем книгу цикл в цикле, затем цикл в цикле обращаемся к СтрСоединить страница за страницей...
Кол-во строк = 1000 ? тогда книгу можно представить как 20-ть страниц по 50 строк....

Если подобная задача касалась бы разметки html-страницы, я бы еще попробовал использовать Соответствие, ключами которого были бы условно говоря <заголовок1><тело1><что-то еще>, а значениями были бы как раз-таки строки... это для простой html-страницы...

для сложной html-страницы попробовал бы использовать структуру + массивы - как раз по подобию древовидной структуры json https://infostart.ru/public/1573208/
21. Darklight 32 10.01.22 09:47 Сейчас в теме
(19)Данному варианту явно не хватает подварианта с заранее выделенной памятью под массив - что важно для очень больших массивов:
Результат = Новый Массив(КоличествоИтераций);
Для Индекс = 0 По КоличествоИтераций-1 Цикл
Результат[Индекс] = "Это тестовая строка " + Строка(Индекс+1));
КонецЦикла;
Строка = СтрСоединить(Результат, Символы.ПС);
simgo83; RustIG; +2 Ответить
33. RustIG 1351 10.01.22 22:41 Сейчас в теме
(21)
вот код 4-х примеров (сценариев алгоритма)
Старт = ТекущаяДата();
	
	//первый пример
	Результат = "";
	Для Индекс = 1 По КоличествоИтераций Цикл
		Результат = Результат + "Это тестовая строка " + Строка(Индекс) + Символы.ПС;
	КонецЦикла;
	
	Стоп = ТекущаяДата();
	Сообщить(Стоп - Старт);
	
	//второй пример
	Старт = ТекущаяДата();
	
	КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок); 	
	Книга = Новый Массив; 	
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + "Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС; 			
		КонецЦикла;
		
		Книга.Добавить(Страница);  		
	КонецЦикла;
	
	//Результат = СтрСоединить(Книга, Символы.ПС);
	Результат = "";
	Для Индекс = 0 По КолСтраниц-1 Цикл
		Результат = Результат + Книга.Получить(Индекс);		
	КонецЦикла;	
	
	Стоп = ТекущаяДата();
	Сообщить(Стоп - Старт);
	
	//третий пример
	Старт = ТекущаяДата();
	
	Книга = Новый Массив(КолСтраниц);
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + "Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС; 			
		КонецЦикла;
		
		Книга.Добавить(Страница); 		
	КонецЦикла;
	
	//Результат = СтрСоединить(Книга, Символы.ПС);
	Результат = "";
	Для Индекс = 0 По КолСтраниц-1 Цикл
		Результат = Результат + Книга.Получить(Индекс);		
	КонецЦикла;
	
	Стоп = ТекущаяДата();
	Сообщить(Стоп - Старт);
	
	//четвертый пример
	Старт = ТекущаяДата();

	Книга = "";
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + "Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС; 			
		КонецЦикла;
		
		Книга = Книга + Страница; 		
	КонецЦикла;
	
	Стоп = ТекущаяДата();
	Сообщить(Стоп - Старт);

Показать


вот результат:
65
1
0
2

у меня нет функции СтрСоединить() - я ее заменил на обход массива
Прикрепленные файлы:
37. Darklight 32 11.01.22 09:20 Сейчас в теме
(33)Кто же через "ТекущаяДата()" замеры устраивает. Нужно использовать "ТекущаяУниверсальнаяДатаВМиллисекундах()"
С прямой конкатенацией строк уже выше (15) упомянули, что крайне важна скобочная оптимизация! В идеале (для чистоты замеров) - везде (во всех ваиантах) вынести это вычисление в отдельной переменной, которую уже конкатенировать разными способами (ну или просто использовать константную строку - для данного теста этого достаточно). А вы в последующих тестах вообще работали с готовыми строками!
И не понятно, зачем так всё усложнять?
38. RustIG 1351 11.01.22 09:41 Сейчас в теме
(37)
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
	
	//первый пример
	Результат = "";
	Для Индекс = 1 По КоличествоИтераций Цикл
		Результат = Результат + ("Это тестовая строка " + Строка(Индекс) + Символы.ПС);
	КонецЦикла;
	
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);
	
	//второй пример
	Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
	
	КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок); 	
	Книга = Новый Массив; 	
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + ("Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС); 			
		КонецЦикла;
		
		Книга.Добавить(Страница);  		
	КонецЦикла;
	
	//Результат = СтрСоединить(Книга, Символы.ПС);
	Результат = "";
	Для Индекс = 0 По КолСтраниц-1 Цикл
		Результат = Результат + Книга.Получить(Индекс);		
	КонецЦикла;	
	
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);
	
	//третий пример
	Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
	
	Книга = Новый Массив(КолСтраниц);
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + ("Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС); 			
		КонецЦикла;
		
		Книга.Добавить(Страница); 		
	КонецЦикла;
	
	//Результат = СтрСоединить(Книга, Символы.ПС);
	Результат = "";
	Для Индекс = 0 По КолСтраниц-1 Цикл
		Результат = Результат + Книга.Получить(Индекс);		
	КонецЦикла;
	
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);
	
	//четвертый пример
	Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();

	Книга = "";
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + ("Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС); 			
		КонецЦикла;
		
		Книга = Книга + Страница; 		
	КонецЦикла;
	
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);

Показать


Результат для 30 000 строк и 60 строк на страницу:
15 581
2 985
2 711
2 963
Прикрепленные файлы:
40. RustIG 1351 11.01.22 09:46 Сейчас в теме
(37)
Кто же через "ТекущаяДата()" замеры устраивает. Нужно использовать "ТекущаяУниверсальнаяДатаВМиллисекундах()"


для первого приближения и больших данных всегда можно использовать ТекущаяДата() - она покажет сравнительно и относительно разницу между алгоритмами.

(37)
С прямой конкатенацией строк уже выше (15) упомянули, что крайне важна скобочная оптимизация!

переписал алгоритм со скобочной оптимизацией - в итоге время прохождения алгоритма увеличилось в 5 раз! в моем случае оптимизации не произошло!!! давайте предметно говорить - как нужно скобки расставить чтобы был выигрыш в алгоритме?


(37)
И не понятно, зачем так всё усложнять?

не понял к чему? у меня продемонстрирован пример 3 - за счет алгоритма через "книги и страницы "- смотрите пример 3 - алгоритм ускорился в 60 раз !
41. RustIG 1351 11.01.22 09:52 Сейчас в теме
(40) а так в принципе я через алгоритм и тесты проверил и увидел, что работу со строками всегда можно оптимизировать за счет только алгоритмов (а не выбора объекта-типа-переменной) - алгоритм книг и страниц можно использовать для любого объекта - строка, текстовый документ, джейсон - далее посмотреть какой объект быстрее собирает итоговую строку - ну это уже не в моих интересах, поэтому дальше тестировать не буду с xml, памятью в потоке и т.д.
мне интересен был только алгоритмический подход... а не синтаксический....
34. RustIG 1351 10.01.22 22:43 Сейчас в теме
(21) вот результат для 100 000 строк и 50 строк на страницу- первые три примера:
539
17
12
Прикрепленные файлы:
35. RustIG 1351 10.01.22 22:46 Сейчас в теме
(21) 1) выходит, что задать массив с фиксированным кол-вом элементом - ускоряет выполнение кода на несколько секунд !!!
2) использование аналогии с книгой и страницами ускоряет алгоритм в 30-60 раз (в несколько раз) !!!!
36. RustIG 1351 10.01.22 22:52 Сейчас в теме
(21)
для замера другой функцией:
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
	
	//первый пример
	Результат = "";
	Для Индекс = 1 По КоличествоИтераций Цикл
		Результат = Результат + "Это тестовая строка " + Строка(Индекс) + Символы.ПС;
	КонецЦикла;
	
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);
	
	//второй пример
	Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
	
	КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок); 	
	Книга = Новый Массив; 	
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + "Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС; 			
		КонецЦикла;
		
		Книга.Добавить(Страница);  		
	КонецЦикла;
	
	//Результат = СтрСоединить(Книга, Символы.ПС);
	Результат = "";
	Для Индекс = 0 По КолСтраниц-1 Цикл
		Результат = Результат + Книга.Получить(Индекс);		
	КонецЦикла;	
	
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);
	
	//третий пример
	Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
	
	Книга = Новый Массив(КолСтраниц);
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + "Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС; 			
		КонецЦикла;
		
		Книга.Добавить(Страница); 		
	КонецЦикла;
	
	//Результат = СтрСоединить(Книга, Символы.ПС);
	Результат = "";
	Для Индекс = 0 По КолСтраниц-1 Цикл
		Результат = Результат + Книга.Получить(Индекс);		
	КонецЦикла;
	
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);
	
	//четвертый пример
	Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();

	Книга = "";
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + "Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС; 			
		КонецЦикла;
		
		Книга = Книга + Страница; 		
	КонецЦикла;
	
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);

Показать


результат для 30 000 строк и 60 строк на странице:
39 682
766
524
719
Прикрепленные файлы:
20. Pryanishnikov_Vladimir 10.01.22 08:41 Сейчас в теме
Для полной картины не хватает метода
ТекстовыйФайл = Новый ЗаписьТекста();
ТекстовыйФайл.ЗаписатьСтроку()
. В моей практике метод в разы быстрее текстового документа...
42. Darklight 32 11.01.22 12:13 Сейчас в теме
(20)"ЗаписьXML" (вариант 6) у меня более чем в 1.5 раза быстрее чем "ЗаписьТекста" (вариант 5 из статьи) - всё дело в том, что у варианта с "ЗаписьXML" меньше промежуточных объектов и операций - это очень старый классический вариант быстрой записи в строку - и он актуален и по сей день!
Но "ЗаписьJSON" ещё немного быстрее - при том же количестве кода (хотя при присединении более длинных строк происходит незначительное снижение производительности относительно "ЗаписьXML")!
Но быстрее всего - это варианты с "Массив+СтрСтоединить()" - рекомендуются для постоянного использования в современных конфигурациях и версиях платформы!
Эти варианты тяготеют к методике применения "StringBuilder" в C#
Причём вариант с предварительным выделением памяти под массив не даёт сколько-нибудь заметного прироста производительности (всё и без него работает очень быстро; начинаешь задумываться - может 1С оптимизировала работу с коллекцией "Массив" и ввела-таки экстенсивное выделением блоков памяти в резерв для следующих элементов?) - выполнение на 100K и 1М итераций (кол строк) дало даже обратный результат - выделение памяти заранее даже оказалось медленнее (а оба варианта с массивом были даже медленнее чем "ЗаписьJSON") - но это скорее всего артефакт чистоты эксперимента - всё-таки 1М cтрок заполняются достаточно долго! Да и в реальной практике такое количество - очень большая редкость! Тут скорее даже размер строки важнее - чем количество итераций.
Если провести анализ:
То инструкции алгоритма вида
ЗаписьТекста.ЗаписатьСтроку(Стр);
ЗаписьXML.ЗаписатьБезОбработки(Стр);
Массив.Добавить(Стр);
Массив[Индекс] = Стр;
Относительно друг друга занимают примерно одно и то же время (в % от выполнения всего алгоритма - причём примерно в 2 раза меньше времени - чем уходит на инструкции цикла "Для Индекс = 1 По КоличествоИтераций Цикл КонецЦикла" - что меня сильно удивило) - скорее всего почти все затраты времени - это передача значения переменной (скорее всего просто ссылки, без копирования всей области памяти - так как от размера Стр соотношение не меняется) и вызов метода обхекта с переключением контекста на его модуль реализации).
Финальное же формирование строки - методами
ПолучитьСтрокуИзДвоичныхДанных(ДвоичныеДанныеИзПотокаВПамяти)
Закрыть()
СтрСоединить()
Почти не оказывают влияния (чем больше итераций - тем меньше их влияние, при сохранение итоговой длины строки; ну разве что СтрСоединить() чуть-чуть медленнее, чем Закрыть() записей XML/JSON и чуть-чуть быстрее чем ПолучитьСтрокуИзДвоичныхДанных() ).
Тем самым - основные затраты - это цикл - весь блок это более 99% всего времени во всех вариантах!
Тогда вывод такой - во всех указанных вариантах собственно обработка конкатенации строк идёт очень быстро - побочные затраты на цикл и вызовы методов объектов (в т.ч. скрытые - при присвоении индексированной ячейке массива значения) - куда более значительны и составляют почти всё время выполнения!
Работа с Массивом и функцией СтрСоединить - чуть быстрее, но она есть только с некоторого релиза 8.3 совместимости конфигурации и платформы. Работа с ЗаписьXML - является классической, старой, проверенной и почти такой же производительной!
Предварительное выделение памяти для Массива - как ни странно - играет даже обратную роль - может чуть снизить производительность (причём даже не за счёт затрат на конструкторе (хотя они тоже есть - но не значительны), а именно на присвоении значения ячейке массива по индексу (впрочем эти затраты тоже не значительны - но они находятся в цикле)).
Применение же классической конкатенации строк сильно проседает по производительности при числе строк свыше 1000. До 100 строк можно использовать и рядовую конкатенацию строк - друге способы не будут иметь значительного превосходства. А до 1000 строк - затраты времени по-прежнему будут фактически не велики (с учётом, что внутри цикла будет ещё какой-то продуктивный алгоритм) даже для рядовой конкатенации - но тут, конечно, ещё многое зависит от объёма строк - как быстро будет расти итоговая строка.
При числе итераций свыше 1000 всё зависит от затрат времени на продуктивном алгоритме внутри цикла - если он более 1 секунды на итерацию - тоже можно не заморачиваться на затраты времени на конкатенацию строк. В циклах свыше 10000 тыс строк уже настоятельно рекомендую выбрать любую оптимизацию конкатенации строк - даже если продуктивная часть будет ещё дольше - уж больно много времени будет занимать рядовая конкатенация строк.

Варианты с постраничным разбиением на массивы, предложенные в (33)-(38) - оказались не стабильны (всё зависит от размеров строк и их количества - разные размеры страниц бывает по-разному эффективны) - но все они, как минимум незначительно, проигрывают описанным выше вариантам - поэтому заворачиваться с постраничным разбиением смысла нет!

Некоторые мои замеры (мс):


Код:
Процедура Тест(КоличествоИтераций, Стр)
	//Стр2 = Стр + Символы.ПС;
	//т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	//Результат = "";
	//Для Индекс = 1 По КоличествоИтераций Цикл
	//    Результат = Результат + Стр2;
	//КонецЦикла;
	//Сообщить("Строка: "+(ТекущаяУниверсальнаяДатаВМиллисекундах()-т));
		
	т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	Результат = Новый ПотокВПамяти;
	ЗаписьТекста = Новый ЗаписьТекста(Результат);
	Для Индекс = 1 По КоличествоИтераций Цикл
	    ЗаписьТекста.ЗаписатьСтроку(Стр);
	КонецЦикла;
	ЗаписьТекста.Закрыть();   
	ДД = Результат.ЗакрытьИПолучитьДвоичныеДанные();
	Строка = ПолучитьСтрокуИзДвоичныхДанных(ДД);  
	Сообщить("ЗаписьТекста: "+(ТекущаяУниверсальнаяДатаВМиллисекундах()-т));
	
	т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	Результат = Новый ЗаписьXML;
	Результат.УстановитьСтроку();
	Для Индекс = 1 По КоличествоИтераций Цикл
	    Результат.ЗаписатьБезОбработки(Стр);
	КонецЦикла;
	Строка = Результат.Закрыть();
	Сообщить("ЗаписьXML: "+(ТекущаяУниверсальнаяДатаВМиллисекундах()-т));
	
	т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	Результат = Новый ЗаписьJSON;
	Результат.УстановитьСтроку();
	Для Индекс = 1 По КоличествоИтераций Цикл
	    Результат.ЗаписатьБезОбработки(Стр);
	КонецЦикла;
	Строка = Результат.Закрыть();
	Сообщить("ЗаписьJSON: "+(ТекущаяУниверсальнаяДатаВМиллисекундах()-т));
	
	т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	Результат = Новый Массив;
	Для Индекс = 1 По КоличествоИтераций Цикл
	    Результат.Добавить(Стр);
	КонецЦикла;
	Строка = СтрСоединить(Результат, Символы.ПС);	
	Сообщить("Массив: "+(ТекущаяУниверсальнаяДатаВМиллисекундах()-т));
	
	т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	Результат = Новый Массив(КоличествоИтераций);
	Для Индекс = 0 По КоличествоИтераций-1 Цикл
	    Результат[Индекс] = Стр;
	КонецЦикла;
	Строка = СтрСоединить(Результат, Символы.ПС);	
	Сообщить("Массив(Кол): "+(ТекущаяУниверсальнаяДатаВМиллисекундах()-т));
		
	//СтраницаКолСтрок = 40;
	//т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	//КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок);  
	//Книга = Новый Массив(КолСтраниц);
	//Для Индекс1 = 0 По КолСтраниц-1 Цикл
	//	
	//	Страница = "";
	//	Для Индекс2 = 1 По СтраницаКолСтрок Цикл             
	//		Страница = Страница + Стр2;             
	//	КонецЦикла;
	//	
	//	Книга.Добавить(Страница);         
	//КонецЦикла;
	//
	//Результат = "";
	//Для Индекс = 0 По КолСтраниц-1 Цикл
	//	Результат = Результат + Книга.Получить(Индекс);        
	//КонецЦикла;
	//c = ТекущаяУниверсальнаяДатаВМиллисекундах();
	//Сообщить("Массив страницы("+СтраницаКолСтрок+"): "+(c-т));  
	//
	//СтраницаКолСтрок = 60;
	//т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	//КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок);  
	//Книга = Новый Массив(КолСтраниц);
	//Для Индекс1 = 0 По КолСтраниц-1 Цикл
	//	
	//	Страница = "";
	//	Для Индекс2 = 1 По СтраницаКолСтрок Цикл             
	//		Страница = Страница + Стр2;             
	//	КонецЦикла;
	//	
	//	Книга.Добавить(Страница);         
	//КонецЦикла;
	//
	//Результат = "";
	//Для Индекс = 0 По КолСтраниц-1 Цикл
	//	Результат = Результат + Книга.Получить(Индекс);        
	//КонецЦикла;
	//c = ТекущаяУниверсальнаяДатаВМиллисекундах();
	//Сообщить("Массив страницы("+СтраницаКолСтрок+"): "+(c-т));  
	//
	//СтраницаКолСтрок = 100;
	//т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	//КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок);  
	//Книга = Новый Массив(КолСтраниц);
	//Для Индекс1 = 0 По КолСтраниц-1 Цикл
	//	
	//	Страница = "";
	//	Для Индекс2 = 1 По СтраницаКолСтрок Цикл             
	//		Страница = Страница + Стр2;             
	//	КонецЦикла;
	//	
	//	Книга.Добавить(Страница);         
	//КонецЦикла;
	//
	//Результат = "";
	//Для Индекс = 0 По КолСтраниц-1 Цикл
	//	Результат = Результат + Книга.Получить(Индекс);        
	//КонецЦикла;
	//c = ТекущаяУниверсальнаяДатаВМиллисекундах();
	//Сообщить("Массив страницы("+СтраницаКолСтрок+"): "+(c-т));
	//
	//
	//СтраницаКолСтрок = 1000;
	//т=ТекущаяУниверсальнаяДатаВМиллисекундах();
	//КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок);  
	//Книга = Новый Массив(КолСтраниц);
	//Для Индекс1 = 0 По КолСтраниц-1 Цикл
	//	
	//	Страница = "";
	//	Для Индекс2 = 1 По СтраницаКолСтрок Цикл             
	//		Страница = Страница + Стр2;             
	//	КонецЦикла;
	//	
	//	Книга.Добавить(Страница);         
	//КонецЦикла;
	//
	//Результат = "";
	//Для Индекс = 0 По КолСтраниц-1 Цикл
	//	Результат = Результат + Книга.Получить(Индекс);        
	//КонецЦикла;
	//c = ТекущаяУниверсальнаяДатаВМиллисекундах();
	//Сообщить("Массив страницы("+СтраницаКолСтрок+"): "+(c-т));

КонецПроцедуры
Показать
Прикрепленные файлы:
43. Diversus 2306 11.01.22 12:36 Сейчас в теме
(42) Почитайте комментарии, где я выше отписывался. Многое из того, что вы рассказали я уже в той или иной мере проговорил.
Про Capacity мое предположение почему выделение памяти заранее под Массив не ведет к тотальному увеличению скорости.
По поводу того, что ваши результаты не соответствуют моим, тоже есть. На разных машинах все может быть по разному. Общие тренды сохраняются плюс-минус, но позиции могут отличаться. Например, между компьютерами с процессорами Intel и AMD. Объемом ОЗУ и т.д. Все это в той или иной мере влияет на графики.
49. Darklight 32 11.01.22 13:31 Сейчас в теме
(43)
Про Capacity мое предположение почему выделение памяти заранее под Массив не ведет к тотальному увеличению скорости.

Насколько я знаю - ранее (в других алгоритмах работы с массивом) на Инфорстарт уже обсуждалось - что массивы проседают по производительности если их наполнять - без предварительного определения размера - а сейчас вижу обратную картину.

По поводу того, что ваши результаты не соответствуют моим, тоже есть. На разных машинах все может быть по разному.

Само собой - у всех по-разному - время зависит от аппаратной составляющей - а вот соотношения - по идеи от нюанса паттерна конкатенируемой строки (у меня везде фиксированная строка - разной длинны) и вероятно особенностей того или иного релиза платформы (интересно влияет ли режим совместимости - по идеи не должен, а вот версия платформы, под которой фактически всё запущено - должна влиять, как и режим упр или не упр приложения).
Суть основновнога вывода в том - что все способы оптимизации примерно одинаково производительны и общие накладные расходы в них берут верх - так что можно брать любой - и использовать, когда число строк в итерациях цикла будет явно больше 100 а то и больше 1000 (включая все циклы верхнего уровня - если они есть) - иначе можно не заморачиваться - оригинальная прямая конкатенация строка будет не особо проседать по производительности!

Забыл упомнить, что тестировал на релизе 8.3.19.1264 64Bit (на клиенте: Core i3 3.5Ghz 16Gb RAM; на сервере по сути ничего не меняется - но у меня тут виртуализация и невозможен чистый эксперимент серверного выполнения), но в 1С Бухгалтерия 3.0 в режиме совместимости конфигурации Версия 8.3.14 в управляемом режиме во внешней обработке). Надо бы ещё другие релизы попробовать - у меня есть 8.3.20.1674 (и конфа для него без установленной совместимости) - но что-то сегодня запускаться не стал (хотя до НГ 100% работал) - позже буду разбираться. Ну и меньшие релизы платформы надо глянуть (именно фактические, а не по режиму совместимости)!
44. RustIG 1351 11.01.22 12:52 Сейчас в теме
(42) ваши мысли, изыскания и алгоритмы интересны. спасибо, что нашли время написать так подробно.
45. RustIG 1351 11.01.22 13:01 Сейчас в теме
(42)
1Kx100

я не только не понял, что это за запись? что означает?
47. Darklight 32 11.01.22 13:10 Сейчас в теме
(45)1K=1000 итераций конкатенации порциями в 100 символов!
48. RustIG 1351 11.01.22 13:19 Сейчас в теме
(47) на одной строке не должно быть много символов - 100 символов это много - ограничимся 80 - 100 символов это уже не книга в моей концепции - придется учитывать длину строки, чтобы понять сколько строк на страницу влезет - а страниц в книге может быть много - как раз книга - это итоговый массив, состоящий из страниц....

Поэтому итоговую строку (итоговый результат) удобно получать через СтрСоединить(Книга, Символы.ПС)...

Пока что все на уровне интуиции...
50. Darklight 32 11.01.22 13:37 Сейчас в теме
(48)
на одной строке не должно быть много символов - 100 символов

Наверное мы о разных вещах - алгоритм не может влиять на то, какие строки ему поручают конкатенировать! Сами строки могут быть результатом работы других алгоритмов, и скажем, быть либо кусками запроса, либо кусками XML/JSON/HTML документов, или ещё чем....
И в этом закавыка для Вашего подхода - он очень чувствителен к размеру этих строк - меняется оптимум количества этих строк на одной странице от их размера!
51. RustIG 1351 11.01.22 14:10 Сейчас в теме
(50)чтобы оптимум найти, надо погрузиться в способ хранения данных в памяти, сколько байтов на каждую строковую переменную выделяет память, плюс понимать для какой операционной системы мы делаем замеры, плюс на каком ядре (с учётом его алгоритмов побитовых сложений)... Поэтому предлагаю на уровне интуиции договориться, что строка содержит не более 80 символов, на странице не более 60 строк, страниц может быть много... Мы же конкретную задачу решаем о строках - смотрите начало статьи.
52. Darklight 32 11.01.22 14:39 Сейчас в теме
(51)Алгоритм конкатенации не знает строки какой длины к нему поступать будут. И не может изменять их размер. И не может ни динамически ни статически оптимизироваться по этому критерию!
53. RustIG 1351 11.01.22 16:21 Сейчас в теме
(52) давайте не будем тестировать строки свыше 130 символов.
представим реальную задачу - у нас список из qr-кодов товаров легкой промышленности, около 30 000 марок.
какой алгоритм отработает быстрее?
ваш:
 т=ТекущаяУниверсальнаяДатаВМиллисекундах();
    Результат = Новый Массив(КоличествоИтераций);
    Для Индекс = 0 По КоличествоИтераций-1 Цикл
        Результат[Индекс] = Стр;
    КонецЦикла;
    Строка = СтрСоединить(Результат, Символы.ПС);    
    Сообщить("Массив(Кол): "+(ТекущаяУниверсальнаяДатаВМиллисекундах()-т));

и мой:
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
	КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок);
	Книга = Новый Массив(КолСтраниц);
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + Марка130символов + Символы.ПС; 			
		КонецЦикла;
		
		Книга.Добавить(Страница); 		
	КонецЦикла;
	
	Результат = СтрСоединить(Книга, Символы.ПС);
		
	Стоп = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Сообщить(Стоп - Старт);
Показать


зададим 60 строк на страницу - возьму с неба - не знаю оптимума

сравниваем только эти два алгоритма работы со строками и массивами.

До использования джейсона или xml-записи я бы не додумался....Поэтому пока сравниваем алгоритмы работы со строками...
55. Darklight 32 11.01.22 17:52 Сейчас в теме
(53)Как уже сказал - алгоритм не может диктовать какие строки ему обрабатывать!
Но допустим. Я возьму даже строки короче - 10 символов и от 1000 до 1миллиона строк
Более того - я решил добавить ещё несколько тестов ПЛАЦЕБО - "условно" пустые функции, которые не производят конкатенацию вовсе
&НаКлиентеНаСервереБезКонтекста
Функция Пустая(Строка, Стр) экспорт
	Строка = Строка;
КонецФункции

&НаКлиентеНаСервереБезКонтекста
Функция Пустая2(Знач Строка, Знач Стр) экспорт
	Строка = Строка;
КонецФункции
Показать

Если код из них вовсе убрать - будет примерно в 2 раза быстрее (причём все варианты плацебо примерно одинаково по скорости) - но для тестов решил оставить так - тупо просто присвоение значения переменной себе же - по сути 100% пустышка - максимум что тут должно происходить - копирования ссылки на строку в стек, а потом обратно в ячейку памяти. А ну да - первая функция должна делать это дважды - так как аргументы передаются по в стек ссылке, и нужно их обратно отправить их значения в кучу!
По идеи - первая функция должна работать медленнее - так как ей нужно вернуть результат обратно в кучу!
Но суть не в этом, а в том - что тут нет конкатенации вовсе! Строка не растёт!
Но для эффекта - я передают не пустые строки - а уже заполненные предыдущим тестом - т.е. достаточно длинные!
Так же использую два контекста вызова - текущий (модуль текущей формы) и внешний (модуль второй формы или модуль объекта для сервера - получаются отдельно в начале)

#ЕСЛИ Клиент ТОГДА
	Контекст = ПолучитьФорму("ВнешняяОбработка.КонкатенацияСтрок.Форма.ФормаДоп");
	#ИНАЧЕ
	Контекст = Объект;
	#КОНЕЦЕСЛИ


Итого 4 плацебных вызова функций вид "Пустая_1(Строка, Стр)" (с "_" вызывают внешний контекст, без - текущий).

Ваш алгоритм тоже включил в тест - тестирую страницы 40, 60, 100, 1000 - для наглядности зависимости от размера страниц
Кстати - в этом алгоритме есть ошибка вот тут "КолСтраниц = Цел(КоличествоИтераций/СтраницаКолСтрок);" - при неровном делении - количество страниц будет меньше на 1 - а далее заполнение ориентируется на это количество - в итоге конкатенируется меньше строк. Да и сам алгоритм с книгой, на практике, будет сложнее - ведь ему придётся подстраивать своё заполнение под внешний поток строк (внешний цикл) - вложенные циклы тут буду крайне не желательны - так как встройка такого алгоритма в продуктивную среду может оказаться весьма затруднительной! от того - накладные расходы алгоритма возрастают ещё больше!

Вот результаты (в мс):


Тесты плацебо просто отжигают - конкатенации нет - а затраты времени на вызовы "условно" пустых функций колоссальные (при этом нагрузки на процессор почти нет)!
Что - в очередной раз показывает - что в данной задаче не важен метод решения (кроме прямой конкатенации) - всё упирается в общие накладные расходы платформенных инструкций цикла и внутренних вызовов - тогда имеет смысл выбирать тот алгоритм, который больше нравится и который удобнее вставить. Для 8.3 платформы я бы выбрал Массив + СтрСоединить() причём нет смысла даже массив заранее создавать заданного размера!

На у оптимальное универсальное число страниц видимо действительно 60 :-D
Но страничный метод всё-равно медленнее или как минимум не быстрее просто единого Массива - так что ещё раз подчёркиваю смысла заморачиваться со страничным методом нет! Это усложнение на пустом месте!

Прилагается скриншот распределения времени по плацебо-функциям в сравнении с распределением времени на алгоритм конкатенации через ЗаписьJSON.ЗаписатьБезОбработки(Стр)
Для 1000 и 100 000 строк по 10 символов
В % затраты времени только на вызов функции (без затрат на её пустую работу)

Ещё добавлю - что при большом количестве строк вызов от вызова затраты времени могут заметно гулять и на разные алгоритмы могут быть на разных местах по эффективности. Как уже сказал ранее - особенно это касается страничных вариантов - они самые нестабильные!
Прикрепленные файлы:
56. RustIG 1351 11.01.22 17:58 Сейчас в теме
(55) все понятно. согласен. спасибо.
57. Perfolenta 204 12.01.22 03:12 Сейчас в теме
(55) просто к сведению, в экспериментах с Net Framework я выяснил, что там есть граничная длина строк, после которой скорость конкатенации резко падает, но после излома график всё равно растет линейно... падение скорости конкатенации за граничной точкой составляет от 3 до 5 раз и точнее измерить не удается, т.к. видимо влияют другие процессы работающие в этот момент на компе...
Эта граница для 64-битных процессов грубо находилась в районе 100 килобайт, а для 32-битных процессов 50 килобайт... если общая длина суммируемых строк короче, то скорость конкатенации значительно выше...
В 1С такой границы обнаружить не удается и в целом в 1С конкатенация строк превышающих границу отрабатывает примерно в те же 3-5 раз быстрее, чем в Net... правда, в Net есть StringBuilder, который просто вне конкуренции и работает в 100-200 раз быстрее, чем простая конкатенация...
58. Darklight 32 12.01.22 10:03 Сейчас в теме
(57)
после которой скорость конкатенации резко падает

При каком методе конкатенации? Прямым сложением?
Строки в .NET константные ссылки на область в памяти. Прямое сложение - это создание новой строки, путём копирования в новую область памяти складываемых частей. Если я правильно понимаю механизм CLR - то если такая итоговая строка уже есть в памяти - то копирования не происходит - и просто возвращается ссылка на новую строку (но это надо проверить, так как не совсем ясно как определяется наличие такой строки в памяти).
Чем больше строка - тем больше нужна область в памяти для хранения символов. А память обычно фрагментирована - то есть в ней попросту может не быть свободной области нужного размера - тогда запускается сборщик мусора по полному циклу сборки и реструктуризации памяти данного домена приложения (для .NET Framework, что там с .NET CORE и .NET 5 я не в курсах вовсе - там ведь нет домена приложения). Соответственно при следующей конкатенации - скорее всего придётся искать новую область ещё большего объёма (но последовательная сборка мусора будет уже идти куда быстрее и более предсказуемо - память то уже в целом упорядочена) - о того и падение скорости и сохранение линейного графика времени выполнения. Но 100 кб - это как-то уж очень мало - не такая уж большая область. Кстати - как мерились эти 100кб (по сути это где-то 50K символов в UTF16 - ведь так строки хранятся в .NET; + кроха памяти на пару внутренних полей объекта System.String)?

В 1С такой границы обнаружить не удается и в целом в 1С конкатенация строк превышающих границу отрабатывает примерно в те же 3-5 раз быстрее, чем в Net

Вот это реально удивило. Этож насколько падает скорость в .NET - что её 1С так лихо начинает обходить там, где 1С сама уже безбожно тормозит! Это на .NET Framework 4.7? Может другие фреймворки попробовать (не только 4 версию но и 5 и 6, можно ещё и .NET CORE 3.1 попробовать - хоть он и устарел, но там могут быть свои особенности, до слияния в 5 версии с .NET Framework 4).
То что в 1С нет такой просадки - скорее большая удача или попросту иной планировщик памяти в платформе 1С: Предприятие 8 - что не приходится каждый раз под новую строку решать проблемы её размещения!

правда, в Net есть StringBuilder, который просто вне конкуренции и работает в 100-200 раз быстрее, чем простая конкатенация

Всё-таки работу StringBuilder имеет смысл сравнивать с работой СтрСоединить() по массиву в 1С - принцип их работы очень схож (ну разве что у StringBuilder чуть помощнее API форматирования и некоторые общие сервисные функции).
Проблем с резким замедлением у StringBuilder нет - так как изначально строки хранятся в отдельных ячейках ссылок на разные области памяти, а когда доходит до сборки итоговой строки - он сразу знает какого объёма нужна область памяти - сразу её выделяет (если надо - один раз запускается сборщик мусора) и в один цикл копирует туда память из исходных областей. То же делает и СтрСоединить() в 1С.

Вот, если я бы проектировал управляемый ЯП - я бы при конкатенации строк не копировал бы их физически - а конкатенировал лишь список ссылок на фрагменты (в т.ч. на части фрагментов). Вот когда эти фрагменты требовалось бы изменить - это уже другое дело (но опять же - если изменяемый фрагмент входит только в одну строку - то его можно смело менять без копирования; или выделить в отельные фрагменты неизменяемую часть и изменённую - это уже решается в runtime - по фактическому анализу ситуации) - но для строк обычно это плохой дизайн (нужно использовать другие объекты, в т.ч. временно - для проведения массовых изменений) - строки обычно константы или списки констант. По сути - у меня бы строки были продвинутым StringBuilder.
Даже при выделении подстроки - это просто формирование новых границ фрагмента на ту же область памяти - поэтому эффективность была бы не только при конкатенации, но и при подстрочном разбиении строки.
Что-то типа Span коллекций в .NET (и, возможно, но вряд ли, БуферДвоичныхДанных в 1С 8.3)
59. Perfolenta 204 12.01.22 13:12 Сейчас в теме
(58)
При каком методе конкатенации? Прямым сложением?

да, это был эксперимент со String.Concate
если такая итоговая строка уже есть в памяти - то копирования не происходит - и просто возвращается ссылка на новую строку

Это касается только интернированных строк....
как мерились эти 100кб (по сути это где-то 50K символов

верно, я просто умножил на 2 длину строки после превышения которой скорость резко падала
Это не на .NET Framework 4.7?

Это на .NET Framework 4.8.. на других не пробовал... на других конечно могут быть свои особенности..
То что в 1С нет такой просадки - скорее большая удача или попросту иной планировщик памяти в платформе 1С: Предприятие 8

ну да, выходит, что в 1С хороший алгоритм используется... раньше 1С 8.2 была внутри на COM основана и там использовались строки BSTR, а сейчас не знаю...
Всё-таки работу StringBuilder имеет смысл сравнивать с работой СтрСоединить

только что сравнил... StringBuilder обгоняет 1С Массив+СтрСоединить примерно в 10 раз...
скорее всего из-за медленной работы самого цикла заполняющего массив...
Вот, если я бы проектировал управляемый ЯП - я бы при конкатенации строк не копировал бы их физически - а конкатенировал лишь список ссылок на фрагменты (в т.ч. на части фрагментов).

да, идея отличная, особенно с учетом принципа неизменности строк... но почему-то разработчики Net так не сделали... можно наверное это сделать и в своём компиляторе, но это уже сложнее, т.к. придется делать особую аналитику, где обычные строки, а где модерновые, и когда надо одни превращать в другие, ведь язык IL знает только про обычные... у меня в Перфоленте пока только есть анализ обычной последовательной конкатенации и простейшие оптимизации основанные на количестве конкатенируемых строк..
60. Darklight 32 13.01.22 13:29 Сейчас в теме
(59)
Это касается только интернированных строк....

Ооо... не знал - что для этого не литеральные строки явно надо интернировать - жаль... явно этого никто делать почти никогда не будет! А без этого теряется весь смысл их компактезации в памяти! И сохранение памяти, скажем, если я буду читать из файла потока много одинаковых строк (в т.ч. в разнобой)! А потом ещё и сравнивать их на равенство (обычное сравнение то пройдёт - но будет неоптимально долгим для длинных строк) - то эффективность будет куда ниже.
Кстати, как я понял - явное интернирование не убирает из памяти исходный дубль строки - и он будет её занимать (не имея ссылок на него) до очередной чистки мусора - что тоже не очень хорошо!

да, это был эксперимент со String.Concate

Не удержался - провёл свой эксперимент на C# и .NET Core 3.1
Хотел отследить моменты выполнения сборки мусора - но адаптированный пример с MSDN у меня не сработал. Но другим путём я, всё-таки, частично отследил моменты сборки мусора.
При посимвольной конкатенации 1М строки примерно происходит более 200K сборок мусора (после примерно 500 сборок новые конкатенированные строки создаются уже в куче поколения 2 - минуя 1 - видимо из-за больших размеров - как раз где-то около 50K символов). Переход на поколение 2 - это первое серьёзное замедление - прибавка одного символа занимает порой 27K тиков. У меня это произошло уже через 420 миллисекунд от начала теста.
Но замедления начинаются ещё до перехода на поколение 2. Первые просадка у меня возникают около 25K символов и потом было ещё несколько - но это разовые пики - скорее всего просто огрехи не чистого эксперимента, ну или при выделении памяти в этом момент были разовые сложности поиска свободного блока нужной длинны - хотя 50KB - это смехотворный блок.
В первом поколение среднее время добавления одного символа около 40тиков - но надо заметить - что оно растёт с ростом строки - оно и понятно - длинная строк - требуется больше время на её копирование при конкатенации.
Но и тут случаются пиковые периодические небольшие, но явно выраженные просадки до 3K до 10K тиков. Скорее всего - всё это связано с задержками выделения памяти. При этом, бывает и обратное - на создание новой конкатенированной строки длиной около 1.5K символов может потребоваться 1-3 тика - это ОООЧЕНЬ мало!
Мне кажется - CLR в .NET делает какую-то оптимизацию операции конкатенации строк - не всегда копируя исходную строку - а именно добавляя символ к концу массива байтов - причём заранее выделяя памяти побольше для таких операций - просадки - это как раз выделения новых блоков памяти!

С переходом на поколение 2 ситуация в начале немного меняется - вырастает среднее время добавления символа - где-то до 400тиков - но по-прежнему есть большой разброс. И пиков становится куда больше (в среднем ещё не большие - около 3К-8К) - на графике (прилагается) явно виден забор (показаны большие приросты за которыми сразу же следуют большие падения). Но в целом среднее время добавления тоже начинает расти. Но главное - резко начинает расти количество сборок мусора во втором поколении (где их как раз должно быть мало по архитектуре построения второго поколения управляемо кучи). И пики начинают совпадать с этими моментами сборки мусора.

После 200K символов среднее время вырастает уже до 700тиков. А пики в среднем уже 5К-10К тиков.

Следующий прирост уже около 500K символов. В среднем около 4K тиков требуется на добавление символа. И пиковые задержки становятся более значимыми - около 15K-30K тиков - видом уже и в зоне 2-го поколения кучи начинаются проблемы с выделением памяти под блоки такого большого размера. На отметке 600K с небольшим символов был большой разовый выброс 100K тиков на добавление символа.

Но самое интересное случилось под конец - с отметки 780K символов начались большие выбросы времени конкатенации очередного символа к строке от 123K до 220K тиков. А среднее добавление символа стало около 8K.
Прохождение этого этапа заняло более 60% времени всего заполнения (и около 40% всех сборок мусора)
На лицо проблемы сборки мусора - видимо часто приходилось активно расширять зону памяти поколения 2 - и перемещать туда исходную зону.

Ещё один интересный график - это график корреляции среднего времени добавления символа к удельному приросту времени добавления символа в значимых точках такого прироста, в распределении по шкале количества сборок мусора (удельный прирост - это отношение пиковой единицы прироста к количеству сборок мусора в периоде, где был этот пиковый прирост - т.е. Средний прирост за одну сборку мусора - ведь не каждая сборка мусора образовывала увеличивающийся пик)
На этом графике, на мой взгляд, достаточно чётко видна зависимость пиков среднего времени добавления символа, от пикового удельного прироста времени при очистке мусора в поколении 2. По шкале капительного количества очисток мусора. Линейный рост среднего тут неизбежен из-за удлинения строки.

С 1С такой тест не сравнить - посимвольно 1С Предприятие 8 бы конкатенировало бы строку длиной в 1M более месяца! Но у меня на .NET весь тест занял около 4.5 минут (чистого времени - строго по замерам только операции конкатенации, с высокой точность). Поэтому я не могу сказать - что конкатенация в .NET на длинных строках проседает сильнее, чем на 1C. Ну, конечно, можно попробовать большими порциями конкатенировать строку - и тогда уже сравнить с 1С
Прикрепленные файлы:
61. Perfolenta 204 13.01.22 19:28 Сейчас в теме
(60) спасибо, для меня это интересная информация...

С 1С такой тест не сравнить - посимвольно 1С Предприятие 8 бы конкатенировало бы строку длиной в 1M более месяца!

не знаю, может я не правильно Вас понял про 1М, но вот такой код 1С на моём ноутбуке выполнила за 291 секунду, т.е. чуть больше 4-х минут...
Строка = ""; 
т=ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Индекс = 1 По 1000000 Цикл
    Строка = Строка+"Ф";
КонецЦикла;
Сообщить(ТекущаяУниверсальнаяДатаВМиллисекундах()-т);

в то же время Net 4.8 потратил 345 секунд, т.е. почти 6 минут...
Надо учесть, что пустой цикл в 1М итераций на 1С выполняется в среднем 500 мс, а на Net от 0 мс до 7 мс (раз на раз не приходится, да и точность ТекущаяУниверсальнаяДатаВМиллисекундах меньше 15 мс не гарантирована)...
54. RustIG 1351 11.01.22 16:35 Сейчас в теме
ваш алгоритм отрабатывает быстрее - стабильно быстрее ровно на 450 миллисекунды - для разных количеств марок и кол-ств строк на странице.
марку нашел в инете на ИС, использовал ее 129 символов: Стр = "010290000024799421tw:C:VWHj*_FeØ918029Ø92mSgFXJhvOUzoXp9S1TpKlUqt5526jYj+haVvxTfSznl0cE6JBXTxBBls+d­EUFD9OBeQ6w5PH2w/j2G5FPQprSQ==";


ну что ж - и правда мудрить с дополнительными блоками в виде страниц книг - оказалось неоправданно
46. RustIG 1351 11.01.22 13:09 Сейчас в теме
(42)
т=ТекущаяУниверсальнаяДатаВМиллисекундах();
Результат = Новый Массив(КоличествоИтераций);
Для Индекс = 0 По КоличествоИтераций-1 Цикл
Результат[Индекс] = Стр;
КонецЦикла;
Строка = СтрСоединить(Результат, Символы.ПС);
Сообщить("Массив(Кол): "+(ТекущаяУниверсальнаяДатаВМиллисекундах()-т));


сравните только два алгоритма: вот этот ваш в цитате
и второй мой :
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
	
	Книга = Новый Массив(КолСтраниц);
	Для Индекс1 = 0 По КолСтраниц-1 Цикл
		
		Страница = "";
		Для Индекс2 = 1 По СтраницаКолСтрок Цикл 			
			Страница = Страница + "Это тестовая строка " + Строка(Индекс1*СтраницаКолСтрок + Индекс2) + Символы.ПС; 			
		КонецЦикла;
		
		Книга.Добавить(Страница); 		
	КонецЦикла;
	
	Результат = СтрСоединить(Книга, Символы.ПС);
Показать


для данных - любая книга всегда содержит такое кол-во строк на странице, чтобы можно было комфортно держать ее в руке и читать - то есть 40-50-60 строк на страницу... Не нужно 100 строк на страницу - в этом нет ничего научного, и все требует доказательств.... и все же книга как Война и мир содержит много страниц и мало строк на каждой странице - это нормально - это вписывается в мою концепцию....

Я бы конечно не придумал использовать для подобных задач ни xml, ни джейсон.... поскольку не вижу как внутри устроены эти объекты на уровне ООП (что инкапсулировано в их методах и свойствах)... Но я бы заморочился только на строках и оптимизации работы со строками - отсюда мой пристальный интерес к алгоритму обхода строк.... не более и не менее....

а так спасибо за ликбез вам и автору статьи - есть над чем подумать
22. Darklight 32 10.01.22 10:00 Сейчас в теме
Для полноты эксперимента можно было бы привести тесты (отдельный график) и для ОЧЕНЬ большого количества конкатенаций: то есть явно больше 10тыс строк - скажем от от 100 тыс до 1миллиарда (главное чтобы памяти хватило и не начался свопинг - но тут уже явно нужен сервер с большим количеством ОЗУ, чтобы исключить свопинг).
Пусть такой тест можно считать искусственным - но он может показать более тонкие нюансы в производительности некоторых способов, идущих на меньших объёмах почти на равных (при этом из такого теста можно сразу выкинуть явных аутсайдеров - чтобы оптимизировать масштаб графика - кстати не плохо было бы подписать значения цифр на осях координат).

И да - как было сказано выше - не хватает и графика замера по расходу памяти (как и указание тех характеристик хардварной платформы, на которой производилось тестирование); так же, для полноты картины, как мне кажется, не хватает тестов на разных версиях платформах 1С Предприятие 8. В т.ч. как под Windows так и под Linux.

Неплохо было бы и сравнить графики нагрузки на процессор!

Ну и ещё можно было бы продемонстрировать корреляцию разных методов в зависимости от длины конкатенируемых строк - скажем размерами порций 100, 1К, 10К, 100K, 1000K (естественно не на 1Млрд строк, а не более чем 10K строк).

P.S.
Считаю, что можно было бы сделать и отдельный график для малого количества конкатенируемых строк (до 1000 включительно) - тоже выкинув явных аутсайдеров, чтобы оптимизировать масштаб графика. Где наглядно показать какие методы эффективнее на малых конкатенациях, пусть и эта эффективность в таких операциях и окажется незначительной.
Конечной, такие малые тесты хорошо бы зациклить хотя бы на 1000 (а лучше на 10K-100K) повторов - для достижения статистической точности на малых временных отрезках измерений!
Ведь чаще всего их как раз не так уж и много конкатенируется - зато наглядно будет видно - стоит ли заморачиваться с оптимизацией - и если заморачиваться - то какой способ лучше всего выбрать для универсального применения!
starik-2005; +1 Ответить
25. Diversus 2306 10.01.22 11:14 Сейчас в теме
(22) Обработка прилагается к статье. Если хотите протестировать работу в разных средах или на разном количестве итераций, то можно скачать и попробовать самостоятельно.
65. starik-2005 3033 01.02.22 18:49 Сейчас в теме
(22) провел тесты на линухе на 5600Х и весьма быстрой памяти (3600, CL15 и ужатые тайминги). Графики в сообщении (64). Обработку юзал свою - тупо скопипастил код статьи и добавил генератор графика из ТЗ результата (пять строк кода). Получилось существенно быстрее, чем у автора на 3700Х.
66. Darklight 32 02.02.22 09:21 Сейчас в теме
(65)О чем это говорит - о том что ваша система существенно быстрее, чем система автора - вот только что это даёт?
То что Массив наиболее рекомендуем - я уже писал. Но не во всех тестах он лидер - тут многое зависит от длинны конкатенируемых строк. Ну и как я написал ранее - по сути прикладная разница между всем алгоритмами ничтожна (кроме текстового документа) - поэтому можно выбирать любой - это не существенно!
67. starik-2005 3033 02.02.22 13:24 Сейчас в теме
(66)
прикладная разница
В принципе, время конкатенации 40к строк относительно небольшой длины не превышает трех секунд (на моем чудо компутере, который "существенно быстрее" компьютера(ов) автора немного более старой архитектуре) при самом неэффективном алгоритме. Да, это вполне приемлемая цена для обработки подобного количества информации. Но лично встречался с задачами, где нужно получить очень большой файлик (миллиарды строк), где уже смысла в конкатенации нет - все пишется через запись текста или XML. И на 1С, например, выгрузка такого файлика занимает часов шесть (при хорошей оптимизации), в то время как выгрузка средствами SQL -сервера занимает минут 40. Так что как бы быстро 1С что-либо ни делала, есть другие инструменты, которые будут делать то же самое существенно быстрее.

Как бы мое ИМХО тут в том, что не нужно все это большое и требовательное к производительности тянуть в 1С. В 99% это какая-то отдельная задача, реализация которой на питоне/пыхе/даже С++ или ином чем будет занимать не сильно больше времени в части программирования, но сэкономит туеву хучу времени на последующей попытке заставить 1С делать это с приемлемой скоростью. Поэтому 1С-у 1С-ово, а Кесарю кесарево...
68. Darklight 32 02.02.22 14:01 Сейчас в теме
(67)Вот уберите с графиков 3 топовых по времени бесполезных варианта - и убедите аудиторию, что в прикладных решениях разница между оставшимися алгоритмами будет существенна. Провести замер на миллионе и даже на миллиарде 10 символьных строк на 64битной 1С не проблема.
Да, безусловно - в других платформах и ЯП это будет быстрее - но на много ли? Для прикладных задач - где конкатенация строк не является ключевой частью алгоритма - будет ли в этом реальный смысл? Тем более - если, скажем, хотя бы данные изначально находятся в СУБД
69. starik-2005 3033 02.02.22 14:07 Сейчас в теме
(68)
на миллиарде 10 символьных строк
Сам по себе смысл конкатенации строк в моей лично практике заканчивается на куда меньшем количестве. Дальше только генерация файла. В XBRL, например, файл генерируется не один, а два - контексты (которые не должны повторяться - в этом основная сложность, т.к. факты с идентичным контекстом могут быть в разных отчетных формах) и факты, связанные с контекстами идентификаторами. И финальное объединение этих двух файлов в инстанс не является конкатенацией - просто к первому файлу с контекстами дописывается второй файл с фактами. На выходе XML с миллиардами строк на 100 гигабайт. И вряд ли кому-то в здравом уме придет в голову сначала накопить эти строки в массиве, а потом собрать из массива текст и записать в файл - даже большой, красивый и очень дорогой сервер может такой подход не пережить.
70. Darklight 32 02.02.22 14:45 Сейчас в теме
(69)Про алгоритмы на миллиарды строк Вы сами написали. В моём посте это имеет значение только дли синтетического сравнительного теста.
Поэтому я и говорю, что любой алгоритм (кроме 3-х самых медленных) годится для любых прикладных задач.
И выше уже писал - что для большинства прикладных задач с конкатенацией строк менее 10000 сгодится и обычная конкатенация. А если основные затраты - это именно операция конкатенации - но строк не более 1000 (и средней длинны не более 1000) то и тут нет смысла оптимизироваться вообще - оно и так будет весьма быстро!
71. starik-2005 3033 02.02.22 14:58 Сейчас в теме
(70)
что любой алгоритм (кроме 3-х самых медленных) годится для любых прикладных задач
Ну а где я с этим спорил?
72. Darklight 32 02.02.22 15:05 Сейчас в теме
(71)Тогда с чем Вы спорите? Или вообще не спорите ни с чем
73. starik-2005 3033 02.02.22 18:56 Сейчас в теме
(72)
Или вообще не спорите ни с чем
А кто тут с чем-то спорит? Вы?
23. Азбука Морзе 104 10.01.22 11:11 Сейчас в теме
Совершенно не отражены приемы работы с СКД. В частности агрегатная функция СоединитьСтроки().
24. Diversus 2306 10.01.22 11:13 Сейчас в теме
(23) В моем примере совсем о другом речь. Речь о конкатенации обычных строк, а не работа с СКД.
26. Азбука Морзе 104 10.01.22 11:17 Сейчас в теме
(24) Массив обычных строк можно передать в макет компоновки, обработать и вывести в итоговую строку процессором вывода. Думаю результат удивит многих.
27. Diversus 2306 10.01.22 11:21 Сейчас в теме
(26) Способ с массивом уже есть. Львиная доля всего времени пойдет на формирование этого массива.
И вряд ли передача данных в макет компановки будет работать быстрее, чем СтрСоединить... Но это надо бы протестировать.
Для чистоты эксперимента, если будет время, попробую проверить позже.
28. Darklight 32 10.01.22 12:07 Сейчас в теме
(27)Если память под массив выделена заранее (что не было протестировано) - то львиная доля времени - это уже работа функции СтрСоединить () - ну или вызов СКД - другое дело - что этот вызов скорее всего будет тяжелее чем вызов СтрСоединить () - но попробовать можно. Конечно сравнивать надо в одном контексте - Серверном - так как все вызовы СКД выполняются на Сервере
30. Diversus 2306 10.01.22 13:15 Сейчас в теме
(28) Да - это будет интересно.
31. awk 741 10.01.22 13:46 Сейчас в теме
Как производился замер?
32. Diversus 2306 10.01.22 13:49 Сейчас в теме
(31)
ВремяНачальное = ТекущаяУниверсальнаяДатаВМиллисекундах();
...
// То, что замеряем
...
ЗатраченоВМиллисекундах = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачальное;
39. John_d 5277 11.01.22 09:42 Сейчас в теме
В итоге, чтобы не сильно заморачиваться надо использовать Массив и функцию, которую 1с специально сделала для этих целей СтрСоединить.
Sashares; Darklight; +2 Ответить
74. unichkin 1559 14.08.22 14:36 Сейчас в теме
Кажется, что с СтрШаблон тест некорректный.. Уж если пользуетесь СтрШаблон, то конкатенации встроенным языком вообще быть не должно.


Результат = "";
Шаблон = "%1 %2";
Для Индекс = 1 По КоличествоИтераций Цикл
        ТекстСтроки = СтрШаблон("%1%2%3", "Это тестовая строка " , Индекс, Символы.ПС);
	Результат = СтрШаблон(Шаблон, Результат, ТекстСтроки);
КонецЦикла;

Показать


А вообще самый быстрый способ по-идее должен быть вместе с СтрШаблон и СтрСоединить:


Строки = Новый Массив;
Для Индекс = 1 По КоличествоИтераций Цикл
        ТекстСтроки = СтрШаблон("%1%2", "Это тестовая строка " , Индекс);
         Строки.Добавить(ТекстСтроки);
КонецЦикла;
Результат = СтрСоединить(Строки, Символы.ПС);

Показать
Sashares; simgo83; +2 Ответить
Оставьте свое сообщение