Почему StringBuilder медленнее, чем конкатенация строк? - PullRequest
10 голосов
/ 11 ноября 2011

Почему 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

РЕДАКТИРОВАТЬ: Перемещение из области видимости переменных как предложено:

enter image description here

Ответы [ 7 ]

15 голосов
/ 11 ноября 2011

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

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();
    }
}
6 голосов
/ 11 ноября 2011

Вы создаете новый экземпляр 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 не нужно создавать тонны промежуточных строк, которые просто будут выброшены, вы получите намного лучшую производительность. Вот для чего он предназначен. Для небольших строк лучше использовать конкатенацию строк для простоты и ясности.

2 голосов
/ 19 января 2012

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

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

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 работает лучше, если вы строите большую строку за много шагов, или вы не знаете, сколько строк будет объединено вместе.
- Объединение их всех в одинВыражение лучше, когда это разумный вариант.

2 голосов
/ 11 ноября 2011

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

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

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

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

1 голос
/ 11 ноября 2011

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

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());

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

String.Concat(x, y);

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

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

, потому что StringBuilder не может использовать такие ярлыки (вы можете вызвать их присоединение или удаление, что невозможно с помощью String.Concat).

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

Так что для простых конкатенаций строк x + y (т. Е. String.Concat) всегда будет превосходить StringBuilder.

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

0 голосов
/ 19 января 2012

Вот пример, демонстрирующий ситуацию, в которой 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 Построитель строк занял: 0 секунд.Объединение заняло: 8,7659616 секунд.

Результат # 2 Построитель строк занял: 0 секунд.Объединение заняло: 8,7659616 секунд.

Результат # 3 Построитель строк занял: 0 секунд.Конкатенация заняла: 8,9378432 секунд.

Результат # 4 Построитель строк занял: 0 секунд.Объединение заняло: 8,7972128 секунд.

Результат # 5 Построитель строк занял: 0 секунд.Конкатенация заняла: 8,8753408 секунд.

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

0 голосов
/ 11 ноября 2011

Я думаю, что лучше сравнивать эффективность между 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

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

...