Pull to refresh

Comments 38

На .NET 2.0 beta 2 результаты несколько лучше, но не так уж и сильно.

К теме статьи не относится, но .NET пора обновить)
да, статья писалась автором довольно давно, но, полагаю, актуальности ещё не утратила
Дочитав до этой строки сразу бросил читать.
ЗЫ: проблему можно решить по хард-кору через указатели (unsafe code) отключив интернирование чтобы не получить неожиданные результаты :-) (кто воспримет это серьёзно — тот зануда).
А ещё нет полезного совета:
string left = «2+2=»;
int right=4;
string exp = left + right;
в последней строке лучше сделать
string exp = left + right.ToString();
чтобы избежать лишней потери производительности на боксинг переменной right
UFO just landed and posted this here
Это перевод старющей статьи Скита. Глаза разувайте перед тем как ляпать что-то.
немного огорчает в StringBuilder отсутствие перегрузок, например sb += «append»;
Да, и, кстати, об этом (о недостаточности операторов и методов StringBuilder по сравнению со «стандартными» строками) писал Джеффри Рихтер в CLR via C#; по его мнению, типы String и StringBuilder должны иметь почти одинаковый функционал.
Если скорость критична, то надо под конкретные цели писать что то свое. Вот отличная статья на эту тематику.
Часто можно еще делать примерно так:

int[] someArray = {1,2,3}; string s = string.Concat(someArray.Select(i => i.ToString()));

Понятно что массив может быть чего-то поинтереснее int-ов, и вместо ToString() что-то посложнее. Также можно использовать string.Join если нужно разделить значения, например, запятыми.

Удобно для простых сценариев, типа пройтись по коллекции и как-нибудь слепить из элементов строку.

По производительности не проверял, но по логике должно работать примерно как StringBuilder.
Имхо, это даже быстрее чем если сделать:
foreach (var item in someArray) stringBuilder.Append(item.ToString());
т.к. string.Concat сразу создаст строку нужной длины через FastAllocateString и FillStringChecked-ами её заполнит.
Там все-таки надо еще массив сделать и переложить все в него. Как на практике будет — сложно сказать, надо мерить. Впрочем, тут уже речь процентах, так что можно смело применять.
Да, но если речь идёт о сферических тестах, то вот такой paste.org.ru/?tllr43 даёт интересные результаты: быстрее всего string.Join, потом String.Concat, a потом уже StringBuilder.
String.Join использует хитрый UnSafeCharBuffer.
А если у вас IEnumerable с неизвестным заранее количеством?

Достаточно семантично, и крайне быстро (а при желании можно состряпать «сахарное» расширение):

var result = someArray.Aggregate(new StringBuilder(), (a,x) => a.Append(x)).ToString();
А я знаю способ еще лучше. Дать всем в этой ветке комментов сначала премию за нестандартное мышление, а потом по ушам за извращения, ведущие к нечитаемому и несопровождаемому коду.
А если окажется что выгода от извращений < 0.1%, то лишить премии.
Итого: премии ноль, уши горят.
ну покажите нам пример генерации md5 hash с результатом в string hex _без извращений_
Тут согласен. Хотя время на любой способ сборки строки по сравнению с вычислением MD5 ничтожно, но если вычислять хеш от хеша в цикле, то становится интересно.
Может знаете, раньше было такое кунг-фу при проверке пароля на локальном компьютере, много раз хеш от хеша — специально, чтобы проверка пароля занимала минимум секунду — это делало простой брутфорс перебором нереальным.
Насчет удобочитаемости кода с билдером.
string name = new StringBuilder(firstName).Append(" ").Append(lastName).ToString();

А насчет того, что стоит ли вообще пытаться оптимизировать такое место, уже столько копий сломано…
Такое — точно не стоит, т.к. перед
string name = string.Concat(firstName, " ", lastName);
он не имеет никаких преимуществ в затратах.
Много раз натыкался на подобные статьи/обсуждения.

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

Из своего опыта скажу, разительно помогло «не создавать новых строк», т.е. везде где можно использовалась ссылка на оригинальный текст и передавались индексы начала и конца фрагмента.

Благо в .NET есть порядочно методов, которые работают именно с подстроками.
UFO just landed and posted this here
UFO just landed and posted this here
Мне кажется у вас ошибка в тесте — вы забыли про строчку «string result = x.ToString();». Ведь именно в ней происходит конкатенация.
UFO just landed and posted this here
.net 4.0, тестим string.Format, string.Concat и конкатенацию плюсом:

            for (int i = 0; i < 10000000; i++)
            {
                var a = rnd.Next(10000);
                var b = rnd.Next(1000);

                //string s = string.Format("{0} {1}", a, b); // 9.9 seconds
                //string s = string.Concat(a, " ", b); // 6.5 seconds
                string s = a.ToString() + " " + b.ToString(); // 6.8 seconds
                builder.Append(s);
            }


Тестим конкатенацию билдером:
            for (int i = 0; i < 10000000; i++)
            {
                var a = rnd.Next(10000);
                var b = rnd.Next(1000);
                
                // 5.9 seconds
                builder.Append(a);
                builder.Append(" ");
                builder.Append(b);
            }

Так что похоже любой метод конкатенации приводит к практически одинаковым результатам. В данном случае, основное время было затрачено на a.ToString() и b.ToString() (явный или неявный).

Короче, используйте то, что вам удобно, и не парьтесь. Только не используйте string.Format() — он в полтора раза медленнее, что и понятно — парсинг строки формата и подстановка параметров занимает время.
Такой скачек производительности достигается благодаря устранению ненужной операции копирования — копируются только те данные, которые присоединяются к результирующей строке.


Сдается мне, что автор не прав. И скачек производительности достигается тем, что память выделяется гораздо реже.
Нет, Скит прав. Выделение памяти — это копеечная операция.
С каких это пор выделение памяти стало копеечной операцией? По опыту — это самая дорогостоящая операция из всех возможных. Тем более, что вы никогда не знаете сколько точно она займет времени.
Вот копирование — это действительно копеечная операция — как два байта переслать :)
Там используется какой то супербыстрый алгоритм выделения памяти fast memory allocation к тому же оптимизированный под строки небольшого назмера. Посмотрите рефлектором.

В дот нете выделение памяти вообще очень быстрая операция — там не нужно искать куда ее выделить благодаря работе дефрагментатора памяти. И вы точно знаете сколько времени это займет — почти ноль времени. В тоже время копирование остается копированием.

Но создание временных элементов в большом количестве быстро приведет к переполнению управляемой кучи.
А в случае больших строк они еще и попадают в конце концов Large Object Heap — в котором нет дефрагментации памяти при сборке мусора и последняя будет происходить скорее всего непрерывно, так как механизм выделения памяти остается прежним.

Поэтому выигрыш не за счет копирования может даже или выделения памяти — а просто за счет менее активной работой с управляемой кучей.

Но вообще то, если строк там тысячи конкатенируются — это актуально. Задача, которая встречается оооочень редко — к тому же решается обычно вообще избавлением от работы со строками.
Для практических задач обычных конкатенаций строк никакие стринг билдеры не нужны — все это детские шалости и экономия на спичках.
Ну для дотнета это справедливо с тех самых пор, как появился сам дотнет :).
В Java конкатенация строк оптимизируется на уровне компилятора. При этом в Java 2.0 компилятор подставляет StringBuffer, а в Java 5.0 компилятор оптимизирует конкатенацию строк с помощью StringBuilder.
UFO just landed and posted this here
Практически всегда, если явно не используются методы ручной оптимизации на основе того или иного варианта String.concat().
В C#.NET тоже есть оптимизация компилятором, например строка кода вида
string a = b + c + d;
заменится на string a = string.Concat(b,c,d) который не будет создавать лишних объектов.
Чтобы обогнать StringBuilder, нужно как-то сделать так, чтобы каждый символ копировался в среднем меньше 2.5 раз. Например, когда ограничение на длину итоговой строки заранее известна. Одно копирование — из исходных строк в char[], другое — при создании строки из фрагмента массива. Тогда у StringBuilder (с переданной конструктору той же Capacity, чтобы было честно) удается выиграть примерно 20% скорости (при благоприятных условиях, когда строки возникают в том же цикле, где они кладутся в массив). Стоит сделать хотя бы один Resize — и уже проигрыш.
На 10^8 строк средней длиной 5 символов: построение через StringBuilder — 2.6 сек, а через массив — 2.2 сек. (.NET 4.0)
Выиграть можно было бы, если как-нибудь захватить char[], не обнуляя его — так не дадут :(
Sign up to leave a comment.

Articles