#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) просто ошеломляющая...
#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 :)
#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
#15
by dimzon
Он неплох { } , но, видишь ли, дело даже не совсем в цикле... как бы объяснить попонятнее... вообще на практике частенько встречается когда объявляется строковая переменная которая потом по ссылке передаётся в различные процедуры и там к ней прибавляется... кроме того в твоём методе ускорение заключается не в уменьшении количества ReAlloc-ов а в уменьшении объёмов, копируемых в b) Не знаю как в 1С но в наших проектах я неоднократно встечал подобные проблемы при формировании CSV, XML, HTML. Ещё помнится был код который делал SELECT а потом из результатов генерировал SQL-Batch c INSERT-ами, там тоже было...
#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 секунд!!!!
#22
by asady
да работает некорректно длина всего 10 зато и работают правильно длина больше мегабайта.
#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 = ""; КонецЦикла;
#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 сек. Улучшенный правда немного косячит, в конце, но это мелочь... И конечно же чем больше данных, тем больше разница, и тем больше понадобится разбивки для поддержания быстродействия.
#33
by dimzon
кстати, а кто-нить знает как можно дополнить и подредактировать уже отправленное сообщение на этом форумном движке? или никак?
#34
by dimzon
вопрос снят, похоже действительно быстрее чем Stream даже при увеличенном в 10 раз объёме, поздравляю ;) блин, жалко в классик обернуть этот код нельзя... хотя можно наверно сделать структурку содержащую в качестве полей Буфер1, ЦСВ и Сч плюс процедуру добавления ДобавитьСтроку(структурка, строка) плюс функцию получения строки ПолучитьРезультат(структурка)...
#37
by Torquader
Вопрос мной просматривался при сборке TXT файла. Выяснилось, что t=CreateObject("Text"); t.AddLine("abcd"); работает прекрасно. При этом для решения кодирования base 64 вполне подойдёт. Также есть мнение, что строки меньше 256 символов работают без выделения памяти, но копирования всё равно не избежать.
#38
by Эльниньо
Процедура Сформировать Сп = СоздатьОбъект("СписокЗначений"); Б = ""; А = "Аждлэ лдэждэ жд эж дэждэждэждэ ждэжд эжд эждоьлдьт лщтжлдо тджл от джло т джло т ждлотждлотджлотдлотдлотлдЯ"; Н = _GetPerformanceCounter; Для х = 1 По 10000 Цикл //Б = Б + А; Сп.ДобавитьЗначение(А); КонецЦикла; Б = ЗначениеВСтроку(Сп); Форма.Обновить; Б = СтрЗаменить(Б, СокрП(СтрЗам1), ""); Б = СтрЗаменить(Б, СокрП(СтрЗам2), ""); Сообщить("Секунд=" + ((_GetPerformanceCounter - Н) / 1000)); КонецПроцедуры Результат: 7 сек против 19
#40
by Torquader
Вообще, проблема намного глубже 1С. Я давно говорил, что, например, в Си++ есть операторы new и delete, но нет оператора redim, который позволял бы увеличить длину массива. Поэтому и получаем - как в глубине, так и наруже.
#42
by dimzon
а что, для строк меньше 256 символов память не нужна? ;) уважаемый, в С есть функция realloc это и есть redim... работает именно так как описано в задача не просто получить большую строку из abcd (это только пример)... для примеров из реальной жизни, например и данный способ не годится... по поводу объекта "Text" - посмотрю подробнее (первый раз такой объект вижу)
#43
by Torquader
В справке 1С есть работа с текстом, который можно прочитать из текстового файла. (По русски вроде бы Текст). Замечено, что добавление строки в текст работает быстрее. Причём, анализ показал, что строка s=s+a работает медленно из-за того, что сначала создаётся строка в которую копируется s, потом копируется a, а потом результат снова копируется в s, то есть проблема не в выделении памяти, а в выполнении операции memcpy несколько лишних раз. Для команды s+=a выполняется только одно копирование строки а, когда строка маленькая, и два копирования s и а, когда идёт выделение памяти (и блок в памяти переезжает с места на место). Вота.
Тэги: Математика и алгоритмы
Ответить:
Комментарии доступны только авторизированным пользователям
В этой группе 1С
- А как очистить хранилище значения?
- v8: УПП Услуга увеличивающая сестоимость // сторно поступления
- ОткрытьФормуМодально подвешивает 1С. Как лечить?
- Хотелось бы услышать Ваши мысли по поводу :?
- почему в регистре сведений загладка "Регистратор" не активен?
- УТ. Детализация по проектам в ведомости движения денежных средств.
- v7: Ситуация с канцтоварами...
- Как перенести документ в другую базу?
- v7: Ошибка при создании узла РБД бухгалтерии 8.1
- v8: Напишите пожалуйста скрипт для реиндексации базы 1с в sql2005
- Отловить пометку в списке значений
- Как узнать ProgID класса COM Microsoft Office Document Imaging 12.0 Type Li
- Строку в список значений
- Добавить счета в Список значений
- Перестала отображаться картинка на форме (1С 8.1)
- 1С:Автомойка
- Как получить ссылку на объект в перечисление по его значению?
- как программно изменить время документа
- v7: ЗУП Основные начисления
- Как отлаживать RLS?