Является ли String.Format столь же эффективным, как StringBuilder?

150

Предположим, у меня есть строитель строк в С#, который делает это:

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();

будет ли это так же эффективно или более эффективно, как если бы:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);

Если так, то почему?

РЕДАКТИРОВАТЬ

После некоторых интересных ответов я понял, что, вероятно, мне следовало бы немного яснее понять, о чем я спрашиваю. Я не столько спрашивал, что быстрее при конкатенации строки, но что быстрее при вводе одной строки в другую.

В обоих случаях выше я хочу вставить одну или несколько строк в середину предопределенной строки шаблона.

Извините за путаницу

  • 0
    Пожалуйста, оставьте их открытыми для будущих улучшений.
  • 4
    В особом сценарии самым быстрым является ни то, ни другое: если заменяемая деталь имеет размер, равный размеру новой детали, вы можете изменить строку на месте. К сожалению, это требует отражения или небезопасного кода и намеренно нарушает неизменность строки. Не очень хорошая практика, но если скорость это проблема ... :)
Показать ещё 1 комментарий
Теги:
performance
stringbuilder
string.format

12 ответов

139
Лучший ответ

String.Format использует StringBuilder внутренне:

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}

Вышеприведенный код является фрагментом из mscorlib, поэтому вопрос становится "is StringBuilder.Append() быстрее, чем StringBuilder.AppendFormat()"?

Без бенчмаркинга я бы сказал, что пример кода выше будет работать быстрее, используя .Append(). Но это догадка, попробуйте провести сравнительный анализ и/или профилировать два, чтобы получить правильное сравнение.

Этот парень, Джерри Диксон, сделал некоторый бенчмаркинг:

http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

Обновлено:

К сожалению, вышеупомянутая ссылка с тех пор умерла. Однако есть еще копия на машине обратного пути:

http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

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

  • 7
    Одна из проблем , с критериями на странице Джерри Диксона является то , что он никогда не называет .ToString() на StringBuilder объекта. На протяжении многих итераций это время имеет большое значение и означает, что он не совсем сравнивает яблоки с яблоками. По этой причине он демонстрирует такую высокую производительность для StringBuilder и, вероятно, объясняет его удивление. Я просто повторил тест с исправлением этой ошибки и получил ожидаемые результаты: String + оператор был самым быстрым, а затем StringBuilder с String.Format замыкающего.
  • 5
    6 лет спустя это уже не так. В Net4 string.Format () создает и кэширует экземпляр StringBuilder, который он использует повторно, поэтому в некоторых тестовых случаях он может быть быстрее, чем StringBuilder. Ниже приведен пересмотренный тест в ответе (который все еще говорит, что concat является самым быстрым, и для моего тестового случая формат на 10% медленнее, чем StringBuilder).
43

Из документация MSDN:

Производительность операции конкатенации для объекта String или StringBuilder зависит от того, как часто происходит распределение памяти. Операция конкатенации строк всегда выделяет память, тогда как операция конкатенации StringBuilder выделяет только память, если буфер объекта StringBuilder слишком мал для размещения новых данных. Следовательно, класс String является предпочтительным для операции конкатенации, если фиксированное число объектов String конкатенировано. В этом случае отдельные операции конкатенации могут быть даже объединены в одну операцию компилятором. Объект StringBuilder предпочтительнее для операции конкатенации, если произвольное количество строк конкатенировано; например, если цикл объединяет случайное число строк пользовательского ввода.

12

Я провел несколько быстрых тестов производительности, и для 100 000 операций, усредненных за 10 запусков, первый метод (String Builder) занимает почти половину времени второго (String Format).

Итак, если это нечасто, это не имеет значения. Но если это обычная операция, вы можете использовать первый метод.

10

Я бы ожидал, что String.Format будет медленнее - он должен проанализировать строку и затем объединить ее.

Пара примечаний:

  • Формат - это способ использования видимых пользователем строк в профессиональных приложениях; это позволяет избежать ошибок локализации.
  • Если вы знаете длину результирующей строки заранее, используйте конструктор StringBuilder (Int32), чтобы предопределить емкость
8

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

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

С другой стороны, если вы говорите о большом фрагменте статического текста с двумя или тремя переменными в нем, даже если это немного менее эффективно, я думаю, что ясность, которую вы получаете от string.Format, стоит того, Я использовал это ранее на этой неделе, когда нужно разместить один бит динамического текста в центре документа на 4 страницы. Будет легче обновить этот большой фрагмент текста, если его в одном фрагменте, чем обновление трех частей, которые вы объедините вместе.

  • 0
    Да! Используйте String.Format, когда это имеет смысл, т.е. когда вы форматируете строки. Используйте конкатенацию строк или StringBuilder при выполнении механической конкатенации. Всегда старайтесь выбрать метод, который сообщает о вашем намерении следующему сопровождающему.
7

Если только потому, что string.Format точно не делает то, что вы можете подумать, вот повторение тестов через 6 лет после Net45.

Конкат по-прежнему самый быстрый, но на самом деле разница менее 30%. StringBuilder и Format отличаются только 5-10%. Я несколько раз тестировал 20% тестов.

Миллисекунды, миллион итераций:

  • Конкатенация: 367
  • Новый stringBuilder для каждого ключа: 452
  • Cached StringBuilder: 419
  • string.Format: 475

Урок, который я убираю, состоит в том, что разница в производительности тривиальна и поэтому не должна мешать вам писать простейший читаемый код. Что за мои деньги часто, но не всегда a + b + c.

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);
  • 2
    Под «string.Format точно не делается то, что вы могли подумать», я имею в виду, что в исходном коде 4.5 он пытается создать и повторно использовать кэшированный экземпляр StringBuilder. Таким образом, я включил этот подход в тесте
5

String.Format использует StringBuilder внутренне... так логично, что приводит к мысли, что он будет немного менее результативным из-за большего количества накладных расходов. Однако простая конкатенация строк - это самый быстрый способ впрыска одной строки между двумя другими... в значительной степени. Это доказательство было продемонстрировано Рико Мариани в его первой спектакле "Опрос", много лет назад. Простым фактом является то, что конкатенации... когда количество частей струны известно (без ограничений.. вы можете объединить тысячу частей... пока вы знаете, что это всегда 1000 частей)... всегда быстрее, чем StringBuilder или String.Format. Они могут выполняться с единичным распределением памяти в виде серии копий памяти. Здесь является доказательством

И вот фактический код для некоторых методов String.Concat, которые в конечном итоге называют FillStringChecked, который использует указатели для копирования памяти (извлеченные через Reflector):

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}

Итак, тогда:

string what = "cat";
string inthehat = "The " + what + " in the hat!";

Наслаждайтесь!

  • 0
    в Net4 string.Format кэширует и повторно использует экземпляр StringBuilder, поэтому в некоторых случаях может быть быстрее.
3

О, также самый быстрый был бы:

string cat = "cat";
string s = "The " + cat + " in the hat";
  • 0
    нет, конкатенация строк очень медленная, потому что .NET создает дополнительные копии строковых переменных между операциями concat, в данном случае: две дополнительные копии плюс окончательная копия для назначения. Результат: крайне низкая производительность по сравнению со StringBuilder который в первую очередь предназначен для оптимизации этого типа кодирования.
  • 0
    Быстрее всего набрать возможно;)
Показать ещё 2 комментария
0

Это действительно зависит от вашего шаблона использования.
Подробный ориентир между string.Join, string,Concat и string.Format можно найти здесь: String.Format не подходит для интенсивной регистрации

0

В обоих случаях выше я хочу вставить одну или несколько строк в середину предопределенной строки шаблона.

В этом случае я бы предложил String.Format является самым быстрым, потому что он предназначен для этой цели.

0

Я бы предположил, что нет, поскольку String.Format не был предназначен для конкатенации, это был дизайн для форматирования вывода различных входных данных, таких как дата.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
0

Это действительно зависит. Для небольших строк с небольшим количеством конкатенаций на самом деле быстрее просто добавить строки.

String s = "String A" + "String B";

Но для большей строки (очень больших строк) более эффективно использовать StringBuilder.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню