Эффективный способ конкатенации строк в 1С #283358


#0 by dimzon
Корни данного поста растут отсюда: На самом деле конкатенация строк является достаточно дорогой с точки зрения производительности операцией. Всем сомневающимся рекомендую замерить время выполнения данного кода:   строка = "";   Для сч = 0 по 333333 цикл       строка = строка + "abcd";   КонецЦикла; Причина столь медленного выполнения заключается в особенности поведения строк. Конкатенация строк в цикле ЕСТЬ ЗЛО! Итак, что происходит при выполнении операции S=S+"Some Text" Происходит т.н. ReAlloc (перевыделение памяти):  a) Выделяется область памяти размером = размер(S) + размер("Some Text")  b) В выделенную область копируется S  c) В выделенную область дописывается "Some Text"  d) Адрес дескриптора строки S устанавливается на новую область  e) Старая область памяти, использовавшаяся S освобождается На небольших строках/циклах потери производительности незаметны, но на больших циклах (десятки тысяч) и/или на больших объёмах (десятки мегабайт) подобный код может служить серьёзным bottleneck-ом... Для ускорения данного кода необходимо уменьшить количество realloc-ов. В "нормальных" языках для решения подобной проблемы есть специальные механизмы навроде StringBuilder-а в .NET, StringBuffer-а в Java... В С++ стандартный stl::string также имеет специальный метод append, реализующий логику аналогичную StringBuilder-у из .NET В других языках можно придумать специальные трюки для минимизации ReAlloc-ов и ускорения данного кода. Например в VBScript/VB/JavaScript можно использовать конструкцию Join:   Option Explicit   Dim i,arr, s: s=""   ReDim arr(333333)   For i=0 to 333333      arr(i)="abcd"   Next   s = Join(arr,"") К сожалению для 1С подобных трюков с использованием "чистого 1С" найти  не удалось... Тем не менее ускорить конкатенацию можно используя COM-объект ADODB.Stream. Вот пример кода:   str = Новый COMОбъект("ADODB.Stream");   str.Open;   Для i=0 По 333333 Цикл        str.WriteText("abcd",0);   КонецЦикла;   str.Position = 0;   строка = str.readText; Как можно убедиться разница во времени выполнения между первым вариантом (сложение в цикле) и последним (с использованием Stream) просто ошеломляющая...
#1 by END
Познавательно, спасиб.
#2 by selenat
Респект!
#3 by Злопчинский
+1!
#4 by dimzon
+ если лень мерить я приведу свои цифры, можете им верить в первом варианте (конкатенация в цикле) - 2500 секунд во втором варианте (vbscript, использование join) - 0.5 секунды в третьем варианте (использование ADODB.Stream) - 1.5 секунды первый вариант проверялся и в 1C v8.1 и в 1C v7.7 и в VBScript и даже в .NET, результаты практически одинаковые - около 2500 секунд ;)
#5 by dimzon
+ ещё хочу добавить что с увеличением числа итераций время растёт нелинейно. посудите сами - на каждой итерации размер строки которая копируется на шаге b) увеличивается, соответсвенно увеличивается и время необходимое на выполнение шага b)
#6 by Андрюха
Познавательно, интересно, однако малоприменимо на практике, ибо ни разу не приходилось пользоваться такой странной конкатенацией да еще в цикле от 0 до 333333 :)
#7 by VladZ
+1
#8 by orefkov
Ты проводил исследования, насколько эта проблемя является "бутылочным горлышком" уже существующих решений? У тебя есть масса трасс замера производительности при реальной работе, указывающей на затык из-за этой проблемы? Решить высосаную из пальца проблему и рекомендовать ее в БЗ - феерично!
#9 by АЛьФ
2 +1 От себя: 1С - это в первую очередь база данных и основные тормоза при обращении именно к базе. Именно это место и стоит оптимизировать. Остальное от лукавого, т.к. любой вычислительный алгоритм можно вынести в ВК и там хоть обоптимизироваться. Автору рекомендую заглянуть сюда, чтобы побольше узнать о том как работает движок 1С:
#10 by dimzon
Ну насчёт "На практике" - данный цикл это реальный код: кроме того возможны несколько другие варианты - например цикл более короткий да строки длинные... вот например в этой ветке во всех "тормозах" скорее всего виновата именно конкатенация... основная проблема в том что многие её не осознают и пишут код не думая... вот например ещё код: вопрос на засыпку - что случится если коллекция будет достаточно большая... - уважаемые, то что я не 1С-нег не означает что я "тупица"... да и 1С тут не причём, работа со практически везде одинакова. естественно я знаю что говорю, естественно я НЕОДНОКРАТНО находил и расшивал подобные bottleneck-и...
#11 by Господин ПЖ
По поводу . 1. Сама постановка задачи убогая. 2. Проблема не в конкатенации, а в работе 1С с файлами, что давно решается через fso.
#12 by dimzon
На самом деле не факт. Ибо возможны 2 различных способа реализации 1. Сначала набирается строка которая потом одним махом пишется в файл. Тогда проблема как раз та 2. Пишется в файл по кусочкам. Тогда проблема с буферизацией НО, косвенно судя по "отъедаемой" памяти рискну предположить что реализация именно 1
#13 by Nordok
Я тебе писал цикл, который выполняется за 2 сек., чем он плох ?
#14 by Андрюха
Никто и не говорит, что ты тупица, просто долбишь немножко не в том направлении.
#15 by dimzon
Он неплох { } , но, видишь ли, дело даже не совсем в цикле... как бы объяснить попонятнее... вообще на практике частенько встречается когда объявляется строковая переменная которая потом по ссылке передаётся в различные процедуры и там к ней прибавляется... кроме того в твоём методе ускорение заключается не в уменьшении количества ReAlloc-ов а в уменьшении объёмов, копируемых в b) Не знаю как в 1С но в наших проектах я неоднократно встечал подобные проблемы при формировании CSV, XML, HTML. Ещё помнится был код который делал SELECT а потом из результатов генерировал SQL-Batch c INSERT-ами, там тоже было...
#16 by Nordok
Я просто все не осилил, написано оптимизировать код, я и оптимизировал :)
#17 by dimzon
кто-нить может на 7.7 замерить время выполнения такого кода:     Ч = 0;     М = 0;     С = 0;     ТекущееВремя(Ч,М,С);     Сп = СоздатьОбъект("СписокЗначений");     Для А=1 По 333333 Цикл          Сп.ДобавитьЗначение("abcd");     КонецЦикла;         Стр = Сп.ВСтрокуСРазделителями;     Ч1 = 0;     М1 = 0;     С1 = 0;     ТекущееВремя(Ч1,М1,С1);     Х = (Ч*60+М)*60+С;     Х1 = (Ч1*60+М1)*60+С1;     Сообщить(" Время вып. = "+Строка(Х1-Х)); есть подозрение что реализация ВСтрокуСРазделителями не далеко ушла от { } Заранее спасибо!
#18 by asady
тД1=ТекущаяДата;    т = Новый  СписокЗначений;    Для А=1 По 333333 Цикл         т.Добавить("abcd");    КонецЦикла;        Стр = Строка(т);        тД2=ТекущаяДата;    Сообщить(" Время вып. = "+(тД2-тД1)); время выыполнения на нашем перегруженном терминале 9 сек.
#19 by asady
вариант нордока    с1="";    с=""; Для сч = 0 по 340 цикл    Для Сч1 = 0 По 1000 Цикл         с1 = с1 + "abcd";      КонецЦикла;   с=с+с1; с1="";     КонецЦикла;    тД2=ТекущаяДата;    Сообщить(" Время вып. = "+(тД2-тД1)); дает у меня 6 секунд.
#20 by asady
Вариант через ADO стрим    тД1=ТекущаяДата; str = Новый COMОбъект("ADODB.Stream");  str.Open;  Для i=0 По 333333 Цикл       str.WriteText("abcd",0);  КонецЦикла;  str.Position = 0;  строка = str.readText;    тД2=ТекущаяДата;    Сообщить(" Время вып. = "+(тД2-тД1)); дает у меня 9 секунд!!!!
#21 by dimzon
результирующая строка неправильная (посмотри длину, должна быть больше мегабайта)
#22 by asady
да работает некорректно длина всего 10 зато и работают правильно длина больше мегабайта.
#23 by dimzon
похоже на правду (у меня подобные пропорции для VBScript-а получились)
#24 by Nordok
думаю можно заставить работать еще быстрее
#25 by dimzon
Возможно, если поиграться с верхними границами массивов...
#26 by dimzon
вот такая зависимость: 2000*170 ~ 61 170*2000 ~ 15 340*1000 ~ 15 34*10000 ~ 20 17*20000 ~ 20 10*33333 ~ 54 8*40000 ~ 98 к сожалению данный подход не всегда поможет... например при экспорте таблички в CSV или при построении html-отчёта - во внешнем цикле будут строки, во внутреннем - столбцы...
#27 by Nordok
например при экспорте таблички в CSV или при построении html-отчёта - во внешнем цикле будут строки, во внутреннем - столбцы... На вскидку не совсем поянл, почему не походят, циклом мы всего лишь регулируем порции конкатенации. И вот он лидер, у меня 1 сек. (может ошибся?) Для к = 0 По 70 цикл      Для к1 = 0 По 70 цикл          Для сч = 0 По 70 цикл с = с + "abcd";    КонецЦикла;    с1 = с1 + с;    С="";    КонецЦикла; с2 = с2 + с1; с1 = ""; КонецЦикла;
#28 by asady
ИМХО автору стоит переименовать ветку.
#29 by dimzon
и как переименовать?
#30 by dimzon
Ну вот  смотри, есть табличка в ней nRow строк и nCol столбцов... Напиши эффективный код формирования CSV ;)
#31 by Nordok
Процедура Сформировать Строк = 500; Столбцов = 500; Тз = СоздатьОбъект("ТаблицаЗначений"); Сообщить("Генерация колонок..."); Для Ном = 1 По Столбцов Цикл    Тз.НоваяКолонка("Кол"+Ном);     КонецЦикла; Сообщить("Заполнение..."); Для Ном = 1 По Строк Цикл    Тз.НоваяСтрока;        Для Ном1 = 1 По Столбцов Цикл        Тз.УстановитьЗначение(Ном, Ном1,"ф");        Состояние(""+Ном+":"+Ном1);    КонецЦикла; КонецЦикла; Сообщить("Формирование CSV"); Буфер1=""; Буфер2=""; ЦСВ=""; Сообщить(ТекущееВремя); Сч=0; Для Ном = 1 По Строк Цикл    Тз.НоваяСтрока;        Для Ном1 = 1 По Столбцов Цикл        Зн=Тз.ПолучитьЗначение(Ном, Ном1);        Буфер1=Буфер1+Зн+", ";        Если Сч=1000 Тогда            Сч=0;            ЦСВ=ЦСВ+Буфер1;            Буфер1="";        КонецЕсли;                Сч=Сч+1;        Состояние(""+Ном+":"+Ном1);    КонецЦикла; КонецЦикла; Сообщить(СтрДлина(ЦСВ)); Сообщить(ТекущееВремя); Сообщить(Лев(ЦСВ,30)); КонецПроцедуры                         //Процедура Базовый //2м46сек //Сообщить(ТекущееВремя); //Для Ном = 1 По Строк Цикл //    Тз.НоваяСтрока;     //    Для Ном1 = 1 По Столбцов Цикл //        Зн=Тз.ПолучитьЗначение(Ном, Ном1); //        ЦСВ=ЦСВ+Зн; //        Состояние(""+Ном+":"+Ном1); //    КонецЦикла; //КонецЦикла; //Сообщить(СтрДлина(ЦСВ)); //Сообщить(ТекущееВремя); //КонецПроцедуры Базовый вариант: 2 минуты 46 сек. Улучшенный: 17 сек. Улучшенный правда немного косячит, в конце, но это мелочь... И конечно же чем больше данных, тем больше разница, и тем больше понадобится разбивки для поддержания быстродействия.
#32 by dimzon
у меня 1С нет, можешь то-же самое на Stream померять? спасибо !
#33 by dimzon
кстати, а кто-нить знает как можно дополнить и подредактировать уже отправленное сообщение на этом форумном движке? или никак?
#34 by dimzon
вопрос снят, похоже действительно быстрее чем Stream даже при увеличенном в 10 раз объёме, поздравляю ;) блин, жалко в классик обернуть этот код нельзя... хотя можно наверно сделать структурку содержащую в качестве полей Буфер1, ЦСВ и Сч плюс процедуру добавления ДобавитьСтроку(структурка, строка) плюс функцию получения строки ПолучитьРезультат(структурка)...
#35 by PR
+1 LOL
#36 by dimzon
читай и
#37 by Torquader
Вопрос мной просматривался при сборке TXT файла. Выяснилось, что t=CreateObject("Text"); t.AddLine("abcd"); работает прекрасно. При этом для решения кодирования base 64 вполне подойдёт. Также есть мнение, что строки меньше 256 символов работают без выделения памяти, но копирования всё равно не избежать.
#38 by Эльниньо
Процедура Сформировать    Сп = СоздатьОбъект("СписокЗначений");    Б = "";    А = "Аждлэ лдэждэ жд эж дэждэждэждэ ждэжд эжд эждоьлдьт лщтжлдо тджл от джло т джло т ждлотждлотджлотдлотдлотлдЯ";    Н = _GetPerformanceCounter;    Для х = 1 По 10000 Цикл        //Б = Б + А;        Сп.ДобавитьЗначение(А);    КонецЦикла;    Б = ЗначениеВСтроку(Сп);    Форма.Обновить;    Б = СтрЗаменить(Б, СокрП(СтрЗам1), "");    Б = СтрЗаменить(Б, СокрП(СтрЗам2), "");    Сообщить("Секунд=" + ((_GetPerformanceCounter - Н) / 1000)); КонецПроцедуры Результат: 7 сек против 19
#39 by Эльниньо
+ Про значения СтрЗам1 и СтрЗам2 не говорю, несложно догадаться.
#40 by Torquader
Вообще, проблема намного глубже 1С. Я давно говорил, что, например, в Си++ есть операторы new и delete, но нет оператора redim, который позволял бы увеличить длину массива. Поэтому и получаем - как в глубине, так и наруже.
#41 by Ковычки
Фигня какая-то...(с) ... Есть метод быстрее всех этих...
#42 by dimzon
а что, для строк меньше 256 символов память не нужна? ;) уважаемый, в С есть функция realloc это и есть redim... работает именно так как описано в задача не просто получить большую строку из abcd (это только пример)... для примеров из реальной жизни, например и данный способ не годится... по поводу объекта "Text" - посмотрю подробнее (первый раз такой объект вижу)
#43 by Torquader
В справке 1С есть работа с текстом, который можно прочитать из текстового файла. (По русски вроде бы Текст). Замечено, что добавление строки в текст работает быстрее. Причём, анализ показал, что строка s=s+a работает медленно из-за того, что сначала создаётся строка в которую копируется s, потом копируется a, а потом результат снова копируется в s, то есть проблема не в выделении памяти, а в выполнении операции memcpy несколько лишних раз. Для команды s+=a выполняется только одно копирование строки а, когда строка маленькая, и два копирования s и а, когда идёт выделение памяти (и блок в памяти переезжает с места на место). Вота.
Тэги: Математика и алгоритмы
Ответить:
Комментарии доступны только авторизированным пользователям

В этой группе 1С