StringBuilder.Append Vs StringBuilder.AppendFormat - PullRequest
36 голосов
/ 02 апреля 2009

Мне было интересно узнать о StringBuilder, и у меня возник вопрос, который я надеюсь, что сообщество сможет объяснить.

Давайте просто забудем о читаемости кода, который из них быстрее и почему?

StringBuilder.Append

StringBuilder sb = new StringBuilder();
sb.Append(string1);
sb.Append("----");
sb.Append(string2);

StringBuilder.AppendFormat:

StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}----{1}",string1,string2);

Ответы [ 9 ]

40 голосов
/ 02 апреля 2009

Невозможно сказать, не зная размеров string1 и string2.

При вызове AppendFormat он предварительно выделит буфер только один раз, учитывая длину строки формата и строки, которые будут вставлены, а затем объединит все и вставит его в буфер. Для очень больших строк это будет выгодно по сравнению с отдельными вызовами Append, что может привести к многократному расширению буфера.

Однако три вызова Append могут инициировать или не запускать увеличение буфера, и эта проверка выполняется при каждом вызове. Если строки достаточно малы и не запускается расширение буфера, то это будет быстрее, чем вызов AppendFormat, потому что ему не придется анализировать строку формата, чтобы выяснить, где выполнять замены.

Для окончательного ответа требуется больше данных

Следует отметить, что мало обсуждается использование статического метода Concat в классе String ( ответ Джона с использованием AppendWithCapacity напомнил мне об этом) , Результаты его теста показывают, что это лучший случай (при условии, что вам не нужно использовать конкретный спецификатор формата). String.Concat делает то же самое в том смысле, что он предопределит длину строк для конкатенации и предварительного распределения буфера (с немного большими издержками из-за циклических конструкций через параметры). Его производительность будет сопоставима с методом AppendWithCapacity Джона.

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

// One call to String.Concat.
string result = a + b + c;

НЕ

// Two calls to String.Concat.
string result = a + b;
result = result + c;

Для всех, кто ставит тестовый код

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

22 голосов
/ 02 апреля 2009

casperOne правильно . Как только вы достигнете определенного порога, метод Append() станет медленнее, чем AppendFormat(). Вот разные длины и пройденные тики 100 000 итераций каждого метода:

Длина: 1

Append()       - 50900
AppendFormat() - 126826

Длина: 1000

Append()       - 1241938
AppendFormat() - 1337396

Длина: 10000

Append()       - 12482051
AppendFormat() - 12740862

Длина: 20000

Append()       - 61029875
AppendFormat() - 60483914

Когда вводятся строки длиной около 20 000, функция AppendFormat() будет слегка превосходить Append().

Почему это происходит? См. ответ casperOne .

Edit:

Я перезапускаю каждый тест по отдельности в конфигурации выпуска и обновляю результаты.

12 голосов
/ 02 апреля 2009

casperOne абсолютно точно зависит от данных . Однако предположим, что вы пишете это как библиотеку классов для использования третьими лицами - что бы вы использовали?

Один из вариантов - получить лучшее из обоих миров - определить, сколько данных вам действительно нужно добавить, а затем использовать StringBuilder.EnsureCapacity , чтобы убедиться, что нам нужен только один изменение размера буфера.

Если бы меня не беспокоили слишком , я бы использовал Append x3 - кажется, "более вероятно" будет быстрее, так как разбор токенов формата строки при каждом вызове явно делает работа.

Обратите внимание, что я попросил команду BCL создать своего рода "кэшированный форматер", который мы могли бы создать с использованием строки формата, а затем повторно использовать повторно. Это безумие, что фреймворк должен анализировать строку формата каждый раз, когда он используется.

РЕДАКТИРОВАТЬ: Хорошо, я немного отредактировал код Джона для гибкости и добавил «AppendWithCapacity», который сначала просто определяет необходимую емкость. Вот результаты для разных длин - для длины 1 я использовал 1 000 000 итераций; для всех других длин я использовал 100 000. (Это было только для того, чтобы получить разумное время выполнения.) Время указано в миллис.

К сожалению, таблицы на самом деле не работают в SO. Длина была 1, 1000, 10000, 20000

Times:

  • Приложение: 162, 475, 7997, 17970
  • AppendFormat: 392, 499, 8541, 18993
  • AppendWithCapacity: 139, 189, 1558, 3085

Так что, как это случилось, я никогда не видел ритм AppendFormat beat, но я сделал См. Победа AppendWithCapacity с очень существенным отрывом.

Вот полный код:

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

public class StringBuilderTest
{            
    static void Append(string string1, string string2)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(string1);
        sb.Append("----");
        sb.Append(string2);
    }

    static void AppendWithCapacity(string string1, string string2)
    {
        int capacity = string1.Length + string2.Length + 4;
        StringBuilder sb = new StringBuilder(capacity);
        sb.Append(string1);
        sb.Append("----");
        sb.Append(string2);
    }

    static void AppendFormat(string string1, string string2)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("{0}----{1}", string1, string2);
    }

    static void Main(string[] args)
    {
        int size = int.Parse(args[0]);
        int iterations = int.Parse(args[1]);
        string method = args[2];

        Action<string,string> action;
        switch (method)
        {
            case "Append": action = Append; break;
            case "AppendWithCapacity": action = AppendWithCapacity; break;
            case "AppendFormat": action = AppendFormat; break;
            default: throw new ArgumentException();
        }

        string string1 = new string('x', size);
        string string2 = new string('y', size);

        // Make sure it's JITted
        action(string1, string2);
        GC.Collect();

        Stopwatch sw = Stopwatch.StartNew();
        for (int i=0; i < iterations; i++)
        {
            action(string1, string2);
        }
        sw.Stop();
        Console.WriteLine("Time: {0}ms", (int) sw.ElapsedMilliseconds);
    }
}
6 голосов
/ 02 апреля 2009

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

AppendFormat принимает String, а затем Object[], что означает, что формат должен быть проанализирован, и каждый Object в массиве должен быть ToString'd перед ним можно добавить во внутренний массив StringBuilder's.

Примечание: К точке зрения Касперона - сложно дать точный ответ без дополнительных данных.

2 голосов
/ 02 апреля 2009

StringBuilder также имеет каскадные добавления: Append() возвращает сам StringBuilder, поэтому вы можете написать свой код так:

StringBuilder sb = new StringBuilder();
sb.Append(string1)
  .Append("----")
  .Append(string2);

Чисто, и он генерирует меньше IL-кода (хотя это действительно микрооптимизация).

1 голос
/ 02 апреля 2009

Конечно профиль, чтобы знать наверняка в каждом конкретном случае.

Тем не менее, я думаю, что в целом это будет первое, потому что вы не повторяете синтаксический анализ строки формата.

Однако разница будет очень мала. До такой степени, что вы действительно должны рассмотреть возможность использования AppendFormat в большинстве случаев в любом случае.

0 голосов
/ 02 апреля 2009

Быстрее 1 в вашем случае, но это не справедливое сравнение. Вы должны спросить StringBuilder.AppendFormat () vs StringBuilder.Append (string.Format ()) - где первый быстрее из-за внутренней работы с массивом char.

Ваш второй вариант более читабелен.

0 голосов
/ 02 апреля 2009

1 должно быть быстрее, потому что он просто добавляет строки, тогда как 2 должен создать строку на основе формата, а затем добавить строку. Так что там есть дополнительный шаг.

0 голосов
/ 02 апреля 2009

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

...