Подтвердить что ты не робот

StringBuilder.Append Vs StringBuilder.AppendFormat

Мне было интересно о 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);
4b9b3361

Ответ 1

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

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

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

Дополнительные данные необходимы для окончательного ответа

Следует отметить, что обсуждается использование статического метода Concat в классе String (Ответить Jon, используя AppendWithCapacity, напомнил мне об этом). Его результаты теста показывают, что это лучший случай (если вам не нужно использовать специальный спецификатор формата). String.Concat делает то же самое в том, что он будет предопределять длину строк для конкатенации и предварительного распределения буфера (с немного большей накладкой из-за циклических конструкций через параметры). Его производительность будет сопоставима с методом Jon 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;

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

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

Ответ 2

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

Длина: 1

Append()       - 50900
AppendFormat() - 126826

Длина: 1000

Append()       - 1241938
AppendFormat() - 1337396

Длина: 10 000

Append()       - 12482051
AppendFormat() - 12740862

Длина: 20 000

Append()       - 61029875
AppendFormat() - 60483914

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

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

Edit:

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

Ответ 3

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

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

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

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

EDIT: Хорошо, я немного изменил код Джона для удобства и добавил "AppendWithCapacity", который сначала сначала создает необходимую емкость. Вот результаты для разных длин - для длины 1 я использовал 1,000,000 итераций; для всех других длин я использовал 100 000. (Это было просто для того, чтобы получить разумное время работы.) Все времена в миллисе.

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

Время:

  • Добавить: 162, 475, 7997, 17970
  • AppendFormat: 392, 499, 8541, 18993
  • AppendWithCapacity: 139, 189, 1558, 3085

Так как это случилось, я никогда не видел AppendFormat бить Append - но я действительно видел 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 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);
    }
}

Ответ 4

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

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

Примечание.. Для точки casperOne трудно дать точный ответ без дополнительных данных.

Ответ 5

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

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

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Однако ваш второй вариант более читабельен.