Является ли использование метода удаления StringBuilder более эффективным для использования памяти, чем создание нового StringBuilder в цикле?
-
06-07-2019 - |
Вопрос
В 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.
Роб
что для тебя важнее?
<Ол> <Литий> <р> памяти р> <Литий> <р> скорость р> <Литий> <р> ясность р> Ол>