Является ли использование метода удаления StringBuilder более эффективным для использования памяти, чем создание нового StringBuilder в цикле?

StackOverflow https://stackoverflow.com/questions/266923

Вопрос

В C #, который более эффективен с точки зрения памяти:Вариант №1 или Вариант №2?

public void TestStringBuilder()
{
    //potentially a collection with several hundred items:
    string[] outputStrings = new string[] { "test1", "test2", "test3" };

    //Option #1
    StringBuilder formattedOutput = new StringBuilder();
    foreach (string outputString in outputStrings)
    {
        formattedOutput.Append("prefix ");
        formattedOutput.Append(outputString);
        formattedOutput.Append(" postfix");

        string output = formattedOutput.ToString();
        ExistingOutputMethodThatOnlyTakesAString(output);

        //Clear existing string to make ready for next iteration:
        formattedOutput.Remove(0, output.Length);
    }

    //Option #2
    foreach (string outputString in outputStrings)
    {
        StringBuilder formattedOutputInsideALoop = new StringBuilder();

        formattedOutputInsideALoop.Append("prefix ");
        formattedOutputInsideALoop.Append(outputString);
        formattedOutputInsideALoop.Append(" postfix");

        ExistingOutputMethodThatOnlyTakesAString(
           formattedOutputInsideALoop.ToString());
    }
}

private void ExistingOutputMethodThatOnlyTakesAString(string output)
{
    //This method actually writes out to a file.
    System.Console.WriteLine(output);
}
Это было полезно?

Решение

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

Я изменил параметр # 1, чтобы воспользоваться предложением @Ty для использования StringBuilder.Length = 0 вместо метода Remove. Это сделало код этих двух вариантов более похожим. Два различия теперь заключаются в том, находится ли конструктор для StringBuilder в цикле или вне его, и опция # 1 теперь использует метод Length для очистки StringBuilder. Обе опции были настроены для работы над массивом outputStrings с 100 000 элементов, чтобы сборщик мусора выполнял какую-то работу.

В паре ответов предлагались подсказки для просмотра различных счетчиков PerfMon & amp; такие и использовать результаты, чтобы выбрать вариант. Я провел небольшое исследование и в конечном итоге использовал встроенный Performance Explorer выпуска Visual Studio Team Systems Developer, который у меня есть на работе. Я нашел вторую запись в серии из нескольких статей, в которой объясняется, как ее настроить

Другие советы

Вариант 2 должен (я считаю) на самом деле превзойти вариант 1. Акт вызова Remove & force "quot; StringBuilder, чтобы взять копию строки, которая уже возвращена. Строка на самом деле является изменяемой в StringBuilder, и StringBuilder не берет копию, если в этом нет необходимости. С опцией 1 он копирует перед очисткой массива - с опцией 2 копирование не требуется.

Единственный недостаток варианта 2 заключается в том, что если строка окажется длинной, при добавлении будет сделано несколько копий, тогда как вариант 1 сохраняет исходный размер буфера. Однако в этом случае укажите начальную емкость, чтобы избежать дополнительного копирования. (В вашем примере кода длина строки будет превышать 16 символов по умолчанию - при инициализации, скажем, 32, будет сокращено количество необходимых дополнительных строк.)

Однако, помимо производительности, вариант 2 просто чище.

Во время профилирования вы также можете попробовать установить нулевую длину StringBuilder при входе в цикл.

formattedOutput.Length = 0;

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

foreach (string outputString in outputStrings)
    {    
        string output = "prefix " + outputString + " postfix";
        ExistingOutputMethodThatOnlyTakesAString(output)  
    }

Переменная с именем output имеет тот же размер, что и в вашей исходной реализации, но никакие другие объекты не требуются.StringBuilder использует строки и другие объекты внутри, и вы создадите множество объектов, которые необходимо собрать в GC'd.

Обе строки из варианта 1:

string output = formattedOutput.ToString();

И строка из варианта 2:

ExistingOutputMethodThatOnlyTakesAString(
           formattedOutputInsideALoop.ToString());

создаст неизменяемый объект со значением префикса + OutputString + postfix.Эта строка имеет одинаковый размер независимо от того, как вы ее создаете.На самом деле вы спрашиваете, что более эффективно работает с памятью:

    StringBuilder formattedOutput = new StringBuilder(); 
    // create new string builder

или

    formattedOutput.Remove(0, output.Length); 
    // reuse existing string builder

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

Если вам действительно нужно знать, какой из двух вариантов более эффективен в вашем приложении (это, вероятно, зависит от размера вашего списка, префикса и выходных строк) Я бы порекомендовал red-gate ANTS Profiler http://www.red-gate.com/products/ants_profiler/index.htm

Джейсон

Ненавижу это говорить, но как насчет того, чтобы просто проверить это?

Этот материал легко узнать самостоятельно. Запустите Perfmon.exe и добавьте счетчик для .NET Memory + Gen 0 Collections. Запустите тестовый код миллион раз. Вы увидите, что для варианта № 1 требуется половина количества вариантов, необходимого для варианта № 2.

Мы говорили об этом раньше с помощью Java , вот [Release] результаты версии C #:

Option #1 (10000000 iterations): 11264ms
Option #2 (10000000 iterations): 12779ms

Обновление: в моем ненаучном анализе, позволившем выполнить два метода при мониторинге всех счетчиков производительности памяти в perfmon, не было обнаружено каких-либо заметных различий ни с одним из этих методов (за исключением того, что некоторые счетчики имели всплеск только во время любого теста). выполнения).

А вот что я использовал для проверки:

class Program
{
    const int __iterations = 10000000;

    static void Main(string[] args)
    {
        TestStringBuilder();
        Console.ReadLine();
    }

    public static void TestStringBuilder()
    {
        //potentially a collection with several hundred items:
        var outputStrings = new [] { "test1", "test2", "test3" };

        var stopWatch = new Stopwatch();

        //Option #1
        stopWatch.Start();
        var formattedOutput = new StringBuilder();

        for (var i = 0; i < __iterations; i++)
        {
            foreach (var outputString in outputStrings)
            {
                formattedOutput.Append("prefix ");
                formattedOutput.Append(outputString);
                formattedOutput.Append(" postfix");

                var output = formattedOutput.ToString();
                ExistingOutputMethodThatOnlyTakesAString(output);

                //Clear existing string to make ready for next iteration:
                formattedOutput.Remove(0, output.Length);
            }
        }
        stopWatch.Stop();

        Console.WriteLine("Option #1 ({1} iterations): {0}ms", stopWatch.ElapsedMilliseconds, __iterations);
            Console.ReadLine();
        stopWatch.Reset();

        //Option #2
        stopWatch.Start();
        for (var i = 0; i < __iterations; i++)
        {
            foreach (var outputString in outputStrings)
            {
                StringBuilder formattedOutputInsideALoop = new StringBuilder();

                formattedOutputInsideALoop.Append("prefix ");
                formattedOutputInsideALoop.Append(outputString);
                formattedOutputInsideALoop.Append(" postfix");

                ExistingOutputMethodThatOnlyTakesAString(
                   formattedOutputInsideALoop.ToString());
            }
        }
        stopWatch.Stop();

        Console.WriteLine("Option #2 ({1} iterations): {0}ms", stopWatch.ElapsedMilliseconds, __iterations);
    }

    private static void ExistingOutputMethodThatOnlyTakesAString(string s)
    {
        // do nothing
    }
} 

Вариант 1 в этом сценарии несколько быстрее, хотя вариант 2 легче читать и поддерживать. Если вы не выполняете эту операцию миллионы раз подряд, я бы придерживался варианта 2, потому что я подозреваю, что варианты 1 и 2 примерно одинаковы при выполнении в одной итерации.

Я бы сказал вариант № 2, если определенно более простой. С точки зрения производительности, звучит как то, что вам просто нужно проверить и увидеть. Я полагаю, что не так уж и важно выбрать менее простой вариант.

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

Я думаю, что вы, возможно, попадаете в ловушку преждевременной оптимизации (корень всего зла - Кнут).Ваш ввод-вывод потребует гораздо больше ресурсов, чем построитель строк.

Я обычно выбираю более четкий / чистый вариант, в данном случае вариант 2.

Роб

<Ол>
  • Измерь это
  • Предварительно выделите как можно ближе к объему памяти, который, по вашему мнению, вам понадобится
  • Если вы предпочитаете скорость, рассмотрите возможность использования параллельного подхода с многопоточностью от середины к концу, достаточно прямой вперед (при необходимости расширяйте разделение труда)
  • измерить еще раз
  • что для тебя важнее?

    <Ол> <Литий> <р> памяти <Литий> <р> скорость <Литий> <р> ясность
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top