Почему StringBuilder медленнее конкатенации строк?

Почему StringBuilder медленнее по сравнению с + конкатенацией? StringBuilder предназначался для предотвращения создания дополнительных объектов, но почему он наказывает производительность?

    static void Main(string[] args)
    {
        int max = 1000000;
        for (int times = 0; times < 5; times++)
        {
            Console.WriteLine("\ntime: {0}", (times+1).ToString());
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < max; i++)
            {
                string msg = "Your total is ";
                msg += "$500 ";
                msg += DateTime.Now;
            }
            sw.Stop();
            Console.WriteLine("String +\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

            sw = Stopwatch.StartNew();
            for (int j = 0; j < max; j++)
            {
                StringBuilder msg = new StringBuilder();
                msg.Append("Your total is ");
                msg.Append("$500 ");
                msg.Append(DateTime.Now);
            }
            sw.Stop();
            Console.WriteLine("StringBuilder\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
        }
        Console.Read();
    }

enter image description here

EDIT: Перемещение переменных области видимости:

enter image description here

Ответ 1

Измените так, чтобы StringBuilder не создавался все время, вместо этого .Clear() он:

time: 1
String +    :   3348ms
StringBuilder   :   3151ms

time: 2
String +    :   3346ms
StringBuilder   :   3050ms

и т.д..

Примечание, что это все еще проверяет точно такую ​​же функциональность, но пытается повторно использовать ресурсы немного умнее.

Код: (также живут на http://ideone.com/YuaqY)

using System;
using System.Text;
using System.Diagnostics;

public class Program
{
    static void Main(string[] args)
    {
        int max = 1000000;
        for (int times = 0; times < 5; times++)
        {
            {
                Console.WriteLine("\ntime: {0}", (times+1).ToString());
                Stopwatch sw = Stopwatch.StartNew();
                for (int i = 0; i < max; i++)
                {
                    string msg = "Your total is ";
                    msg += "$500 ";
                    msg += DateTime.Now;
                }
                sw.Stop();
                Console.WriteLine("String +\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
            }

            {
                Stopwatch sw = Stopwatch.StartNew();
                StringBuilder msg = new StringBuilder();
                for (int j = 0; j < max; j++)
                {
                    msg.Clear();
                    msg.Append("Your total is ");
                    msg.Append("$500 ");
                    msg.Append(DateTime.Now);
                }
                sw.Stop();
                Console.WriteLine("StringBuilder\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));
            }
        }
        Console.Read();
    }
}

Ответ 2

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

Более общее сравнение/использование StringBuilder выглядит примерно так:

string msg = "";
for (int i = 0; i < max; i++)
{
    msg += "Your total is ";
    msg += "$500 ";
    msg += DateTime.Now;
}

StringBuilder msg_sb = new StringBuilder();
for (int j = 0; j < max; j++)
{
    msg_sb.Append("Your total is ");
    msg_sb.Append("$500 ");
    msg_sb.Append(DateTime.Now);
}

При этом вы увидите значительное различие в производительности между StringBuilder и конкатенацией. И "значительным" я подразумеваю порядки величины, а не разницу в 10%, которую вы наблюдаете в своих примерах.

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

Ответ 3

Преимущества StringBuilder должны быть заметны при использовании более длинных строк.

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

Кроме того, создание многих временных объектов может отрицательно сказаться на производительности, которая не поддается измерению с помощью StopWatch, поскольку она "загрязняет" управляемую кучу временными объектами и может вызывать больше циклов сбора мусора.

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

Ответ 4

Обратите внимание, что

string msg = "Your total is ";
msg += "$500 ";
msg += DateTime.Now;

скомпилируется до

string msg = String.Concat("Your total is ", "$500 ");
msg = String.Concat(msg, DateTime.Now.ToString());

Это составляет два concats и один ToString для каждой итерации. Кроме того, один String.Concat работает очень быстро, потому что он знает, насколько велика результирующая строка, поэтому он только выделяет результирующую строку один раз, а затем быстро копирует исходные строки в нее. Это означает, что на практике

String.Concat(x, y);

всегда превосходит

StringBuilder builder = new StringBuilder();
builder.Append(x);
builder.Append(y);

потому что StringBuilder не может принимать такие ярлыки (вы могли бы называть их Append или Remove, что невозможно в String.Concat).

Как работает StringBuilder, выделяя начальный буфер и задавая длину строки равным 0. С каждым Append он должен проверять буфер, возможно, выделять больше пространства в буфере (обычно копируя старый буфер в новый буфер) скопируйте строку и увеличьте длину строки строителя. String.Concat не нужно выполнять всю эту дополнительную работу.

Итак, для простых конкатенаций строк x + y (т.е. String.Concat) всегда превосходит StringBuilder.

Теперь вы начнете получать преимущества от StringBuilder после того, как вы начнете конкатенировать множество строк в один буфер или будете делать много манипуляций в буфере, где вам нужно будет создавать новые строки, если не используя StringBuilder. Это связано с тем, что StringBuilder только иногда выделяет новую память в кусках, но String.Concat, String.SubString и т.д. (Почти) всегда выделяют новую память. (Что-то вроде ".SubString(0,0) или String.Concat(" "," ") не выделяет память, но это вырожденные случаи.)

Ответ 5

В дополнение к тому, что вы не используете StringBuilder, как наиболее эффективным образом, вы также не можете использовать конкатенацию строк максимально эффективно. Если вы знаете, сколько строк вы конкатенируете раньше времени, то делать все это на одной линии должно быть самым быстрым. Компилятор оптимизирует операцию таким образом, чтобы не создавались промежуточные строки.

Я добавил еще пару тестовых примеров. Один из них в основном такой же, как и предложенный, а другой генерирует строку в одной строке:

sw = Stopwatch.StartNew();
builder = new StringBuilder();
for (int j = 0; j < max; j++)
{
    builder.Clear();
    builder.Append("Your total is ");
    builder.Append("$500 ");
    builder.Append(DateTime.Now);
}
sw.Stop();
Console.WriteLine("StringBuilder (clearing)\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

sw = Stopwatch.StartNew();
for (int i = 0; i < max; i++)
{
    msg = "Your total is " + "$500" + DateTime.Now;
}
sw.Stop();
Console.WriteLine("String + (one line)\t: {0}ms", ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6));

И вот пример вывода, который я вижу на моей машине:

time: 1
String +    :   3707ms
StringBuilder   :   3910ms
StringBuilder (clearing)    :   3683ms
String + (one line) :   3645ms

time: 2
String +    :   3703ms
StringBuilder   :   3926ms
StringBuilder (clearing)    :   3666ms
String + (one line) :   3625ms

В целом:  - StringBuilder делает лучше, если вы создаете большую строку за несколько шагов или не знаете, сколько строк будет объединено вместе.
 - Мешать их все вместе в одном выражении лучше, когда это разумный вариант.

Ответ 6

Я думаю, что лучше сравнить эффективность между String и StringBuilder, а не время.

что msdn говорит: Строка называется неизменяемой, потому что ее значение не может быть изменено после его создания. Методы, которые, как представляется, изменяют строку, фактически возвращают новую строку, содержащую модификацию. Если необходимо изменить фактическое содержимое объектноподобного объекта, используйте класс System.Text.StringBuilder.

string msg = "Your total is "; // a new string object
msg += "$500 "; // a new string object
msg += DateTime.Now; // a new string object

Посмотрите, какой из них лучше.

Ответ 7

Вот пример, демонстрирующий ситуацию, в которой StringBuilder будет выполняться быстрее, чем конкатенация строк:

static void Main(string[] args)
{
    const int sLen = 30, Loops = 10000;
    DateTime sTime, eTime;
    int i;
    string sSource = new String('X', sLen);
    string sDest = "";
    // 
    // Time StringBuilder.
    // 
    for (int times = 0; times < 5; times++)
    {
        sTime = DateTime.Now;
        System.Text.StringBuilder sb = new System.Text.StringBuilder((int)(sLen * Loops * 1.1));
        Console.WriteLine("Result # " + (times + 1).ToString());
        for (i = 0; i < Loops; i++)
        {
            sb.Append(sSource);
        }
        sDest = sb.ToString();
        eTime = DateTime.Now;
        Console.WriteLine("String Builder took :" + (eTime - sTime).TotalSeconds + " seconds.");
        // 
        // Time string concatenation.
        // 
        sTime = DateTime.Now;
        for (i = 0; i < Loops; i++)
        {
            sDest += sSource;
            //Console.WriteLine(i);
        }
        eTime = DateTime.Now;
        Console.WriteLine("Concatenation took : " + (eTime - sTime).TotalSeconds + " seconds.");
        Console.WriteLine("\n");
    }
    // 
    // Make the console window stay open
    // so that you can see the results when running from the IDE.
    // 
}

Результат №1 String Builder взял: 0 секунд. Конкатенация взяла: 8.7659616 секунд.

Результат №2 String Builder взял: 0 секунд. Конкатенация взяла: 8.7659616 секунд.

Результат №3 String Builder взял: 0 секунд. Конкатенация взяла: 8.9378432 секунды.

Результат №4 String Builder взял: 0 секунд. Конкатенация взяла: 8.7972128 секунд.

Результат №5 String Builder взял: 0 секунд. Конкатенация взяла: 8.8753408 секунд.

StringBulder намного быстрее, чем + конкатенация.