![]() |
|
Эффективный способ конкатенации строк в 1С | ☑ | ||
---|---|---|---|---|
0
dimzon
10.07.07
✎
14:28
|
Корни данного поста растут отсюда: Простенькая задачка по производительности
На самом деле конкатенация строк является достаточно дорогой с точки зрения производительности операцией. Всем сомневающимся рекомендую замерить время выполнения данного кода: строка = ""; Для сч = 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
END
10.07.07
✎
14:44
|
Познавательно, спасиб.
|
|||
2
selenat
10.07.07
✎
14:49
|
(0) Респект!
|
|||
3
Злопчинский
12.07.07
✎
03:58
|
+1!
|
|||
4
dimzon
12.07.07
✎
04:08
|
+(0) если лень мерить я приведу свои цифры, можете им верить
в первом варианте (конкатенация в цикле) - 2500 секунд во втором варианте (vbscript, использование join) - 0.5 секунды в третьем варианте (использование ADODB.Stream) - 1.5 секунды первый вариант проверялся и в 1C v8.1 и в 1C v7.7 и в VBScript и даже в .NET, результаты практически одинаковые - около 2500 секунд ;) |
|||
5
dimzon
12.07.07
✎
04:24
|
+(0) ещё хочу добавить что с увеличением числа итераций время растёт нелинейно.
посудите сами - на каждой итерации размер строки которая копируется на шаге b) увеличивается, соответсвенно увеличивается и время необходимое на выполнение шага b) |
|||
6
Андрюха
12.07.07
✎
05:04
|
(0) Познавательно, интересно, однако малоприменимо на практике, ибо ни разу не приходилось пользоваться такой странной конкатенацией да еще в цикле от 0 до 333333 :)
|
|||
7
VladZ
12.07.07
✎
05:06
|
(6) +1
|
|||
8
orefkov
12.07.07
✎
08:53
|
(0)
Ты проводил исследования, насколько эта проблемя является "бутылочным горлышком" уже существующих решений? У тебя есть масса трасс замера производительности при реальной работе, указывающей на затык из-за этой проблемы? Решить высосаную из пальца проблему и рекомендовать ее в БЗ - феерично! |
|||
9
АЛьФ
12.07.07
✎
09:14
|
2(8) +1
От себя: 1С - это в первую очередь база данных и основные тормоза при обращении именно к базе. Именно это место и стоит оптимизировать. Остальное от лукавого, т.к. любой вычислительный алгоритм можно вынести в ВК и там хоть обоптимизироваться. Автору рекомендую заглянуть сюда, чтобы побольше узнать о том как работает движок 1С: http://www.1cpp.ru/wiki/?wakka=HomePage |
|||
10
dimzon
12.07.07
✎
13:31
|
(6)(7)
Ну насчёт "На практике" - данный цикл это реальный код: v8: У кого нибудь есть готовый вариант кодирования/декодирования BASE64? кроме того возможны несколько другие варианты - например цикл более короткий да строки длинные... вот например в этой ветке во всех "тормозах" Выгрузить ТЗ в текстовый файл. скорее всего виновата именно конкатенация... основная проблема в том что многие её не осознают и пишут код не думая... вот например ещё код: v8: Чем заменен метод ВСписокСРазделителями? вопрос на засыпку - что случится если коллекция будет достаточно большая... (8)(9) - уважаемые, то что я не 1С-нег не означает что я "тупица"... да и 1С тут не причём, работа со практически везде одинакова. естественно я знаю что говорю, естественно я НЕОДНОКРАТНО находил и расшивал подобные bottleneck-и... |
|||
11
Господин ПЖ
12.07.07
✎
13:42
|
(10) По поводу Выгрузить ТЗ в текстовый файл..
1. Сама постановка задачи убогая. 2. Проблема не в конкатенации, а в работе 1С с файлами, что давно решается через fso. |
|||
12
dimzon
12.07.07
✎
13:49
|
(11) На самом деле не факт. Ибо возможны 2 различных способа реализации
1. Сначала набирается строка которая потом одним махом пишется в файл. Тогда проблема как раз та 2. Пишется в файл по кусочкам. Тогда проблема с буферизацией НО, косвенно судя по "отъедаемой" памяти Выгрузить ТЗ в текстовый файл. рискну предположить что реализация именно 1 |
|||
13
Nordok
12.07.07
✎
13:52
|
(10) Я тебе писал цикл, который выполняется за 2 сек., чем он плох ?
|
|||
14
Андрюха
12.07.07
✎
13:55
|
(10) Никто и не говорит, что ты тупица, просто долбишь немножко не в том направлении.
|
|||
15
dimzon
12.07.07
✎
14:23
|
(13) Он неплох { Простенькая задачка по производительности } , но, видишь ли, дело даже не совсем в цикле...
как бы объяснить попонятнее... вообще на практике частенько встречается когда объявляется строковая переменная которая потом по ссылке передаётся в различные процедуры и там к ней прибавляется... кроме того в твоём методе ускорение заключается не в уменьшении количества ReAlloc-ов а в уменьшении объёмов, копируемых в b) Не знаю как в 1С но в наших проектах я неоднократно встечал подобные проблемы при формировании CSV, XML, HTML. Ещё помнится был код который делал SELECT а потом из результатов генерировал SQL-Batch c INSERT-ами, там тоже было... |
|||
16
Nordok
12.07.07
✎
14:51
|
(15) Я просто все не осилил, написано оптимизировать код, я и оптимизировал :)
|
|||
17
dimzon
12.07.07
✎
15:25
|
кто-нить может на 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-Х)); есть подозрение что реализация ВСтрокуСРазделителями не далеко ушла от { v8: Чем заменен метод ВСписокСРазделителями? } Заранее спасибо! |
|||
18
asady
12.07.07
✎
15:51
|
(17)
тД1=ТекущаяДата(); т = Новый СписокЗначений; Для А=1 По 333333 Цикл т.Добавить("abcd"); КонецЦикла; Стр = Строка(т); тД2=ТекущаяДата(); Сообщить(" Время вып. = "+(тД2-тД1)); время выыполнения на нашем перегруженном терминале 9 сек. |
|||
19
asady
12.07.07
✎
16:00
|
(17)
вариант нордока с1=""; с=""; Для сч = 0 по 340 цикл Для Сч1 = 0 По 1000 Цикл с1 = с1 + "abcd"; КонецЦикла; с=с+с1; с1=""; КонецЦикла; тД2=ТекущаяДата(); Сообщить(" Время вып. = "+(тД2-тД1)); дает у меня 6 секунд. |
|||
20
asady
12.07.07
✎
16:04
|
(17)
Вариант через 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
dimzon
12.07.07
✎
16:12
|
(18) результирующая строка неправильная (посмотри длину, должна быть больше мегабайта)
|
|||
22
asady
12.07.07
✎
16:23
|
(21) да (18) работает некорректно длина всего 10
зато (19) и (20) работают правильно длина больше мегабайта. |
|||
23
dimzon
12.07.07
✎
16:23
|
(19)(20)
похоже на правду (у меня подобные пропорции для VBScript-а получились) |
|||
24
Nordok
12.07.07
✎
16:26
|
думаю (19) можно заставить работать еще быстрее
|
|||
25
dimzon
12.07.07
✎
16:29
|
(24) Возможно, если поиграться с верхними границами массивов...
|
|||
26
dimzon
12.07.07
✎
16:46
|
(24)(25)
вот такая зависимость: 2000*170 ~ 61 170*2000 ~ 15 340*1000 ~ 15 34*10000 ~ 20 17*20000 ~ 20 10*33333 ~ 54 8*40000 ~ 98 к сожалению данный подход не всегда поможет... например при экспорте таблички в CSV или при построении html-отчёта - во внешнем цикле будут строки, во внутреннем - столбцы... |
|||
27
Nordok
12.07.07
✎
17:03
|
например при экспорте таблички в CSV или при построении html-отчёта - во внешнем цикле будут строки, во внутреннем - столбцы... На вскидку не совсем поянл, почему не походят, циклом мы всего лишь регулируем порции конкатенации.
И вот он лидер, у меня 1 сек. (может ошибся?) Для к = 0 По 70 цикл Для к1 = 0 По 70 цикл Для сч = 0 По 70 цикл с = с + "abcd"; КонецЦикла; с1 = с1 + с; С=""; КонецЦикла; с2 = с2 + с1; с1 = ""; КонецЦикла; |
|||
28
asady
12.07.07
✎
17:05
|
(0) ИМХО автору стоит переименовать ветку.
|
|||
29
dimzon
12.07.07
✎
17:13
|
(28) и как переименовать?
|
|||
30
dimzon
12.07.07
✎
17:15
|
(27) Ну вот смотри, есть табличка в ней nRow строк и nCol столбцов... Напиши эффективный код формирования CSV ;)
|
|||
31
Nordok
12.07.07
✎
17:52
|
(30)
Процедура Сформировать() Строк = 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
dimzon
12.07.07
✎
18:00
|
(31) у меня 1С нет, можешь то-же самое на Stream померять?
спасибо ! |
|||
33
dimzon
12.07.07
✎
18:03
|
кстати, а кто-нить знает как можно дополнить и подредактировать уже отправленное сообщение на этом форумном движке? или никак?
|
|||
34
dimzon
12.07.07
✎
19:53
|
(32) вопрос снят, похоже действительно быстрее чем Stream даже при увеличенном в 10 раз объёме, поздравляю ;)
блин, жалко в классик обернуть этот код нельзя... хотя можно наверно сделать структурку содержащую в качестве полей Буфер1, ЦСВ и Сч плюс процедуру добавления ДобавитьСтроку(структурка, строка) плюс функцию получения строки ПолучитьРезультат(структурка)... |
|||
35
PR
12.07.07
✎
20:47
|
(8) +1
LOL |
|||
36
dimzon
12.07.07
✎
20:53
|
(35)
читай (10) и (15) |
|||
37
Torquader
13.07.07
✎
17:26
|
Вопрос мной просматривался при сборке TXT файла.
Выяснилось, что t=CreateObject("Text"); t.AddLine("abcd"); работает прекрасно. При этом для решения кодирования base 64 вполне подойдёт. Также есть мнение, что строки меньше 256 символов работают без выделения памяти, но копирования всё равно не избежать. |
|||
38
Эльниньо
13.07.07
✎
18:15
|
Процедура Сформировать()
Сп = СоздатьОбъект("СписокЗначений"); Б = ""; А = "Аждлэ лдэждэ жд эж дэждэждэждэ ждэжд эжд эждоьлдьт лщтжлдо тджл от джло т джло т ждлотждлотджлотдлотдлотлдЯ"; Н = _GetPerformanceCounter(); Для х = 1 По 10000 Цикл //Б = Б + А; Сп.ДобавитьЗначение(А); КонецЦикла; Б = ЗначениеВСтроку(Сп); Форма.Обновить(); Б = СтрЗаменить(Б, СокрП(СтрЗам1), ""); Б = СтрЗаменить(Б, СокрП(СтрЗам2), ""); Сообщить("Секунд=" + ((_GetPerformanceCounter() - Н) / 1000)); КонецПроцедуры Результат: 7 сек против 19 |
|||
39
Эльниньо
13.07.07
✎
18:18
|
+(38) Про значения СтрЗам1 и СтрЗам2 не говорю, несложно догадаться.
|
|||
40
Torquader
13.07.07
✎
20:33
|
Вообще, проблема намного глубже 1С.
Я давно говорил, что, например, в Си++ есть операторы new и delete, но нет оператора redim, который позволял бы увеличить длину массива. Поэтому и получаем - как в глубине, так и наруже. |
|||
41
Ковычки
14.07.07
✎
09:42
|
Фигня какая-то...(с)
... Есть метод быстрее всех этих... |
|||
42
dimzon
14.07.07
✎
13:17
|
(37) а что, для строк меньше 256 символов память не нужна? ;)
(40) уважаемый, в С есть функция realloc это и есть redim... работает именно так как описано в (0) (38) задача не просто получить большую строку из abcd (это только пример)... для примеров из реальной жизни, например (10) и (15) данный способ не годится... (37) по поводу объекта "Text" - посмотрю подробнее (первый раз такой объект вижу) |
|||
43
Torquader
16.07.07
✎
19:03
|
В справке 1С есть работа с текстом, который можно прочитать из текстового файла. (По русски вроде бы Текст).
Замечено, что добавление строки в текст работает быстрее. Причём, анализ показал, что строка s=s+a работает медленно из-за того, что сначала создаётся строка в которую копируется s, потом копируется a, а потом результат снова копируется в s, то есть проблема не в выделении памяти, а в выполнении операции memcpy несколько лишних раз. Для команды s+=a выполняется только одно копирование строки а, когда строка маленькая, и два копирования s и а, когда идёт выделение памяти (и блок в памяти переезжает с места на место). Вота. |
Форум | Правила | Описание | Объявления | Секции | Поиск | Книга знаний | Вики-миста |