在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); }
Sixto Saez.. 7
几个答案温和地暗示我脱下我的笨蛋并自己弄明白,所以下面是我的结果.我认为这种情绪通常与这个网站的内容相悖,但如果你想做正确的事情,你可能会做.... :)
我修改了选项#1以利用@Ty建议使用StringBuilder.Length = 0而不是Remove方法.这使得两个选项的代码更相似.现在两个不同之处在于StringBuilder的构造函数是在循环中还是在循环之外,而选项#1现在使用Length方法来清除StringBuilder.这两个选项都设置为在具有100,000个元素的outputStrings数组上运行,以使垃圾收集器完成一些工作.
几个答案提供了一些提示,以查看各种PerfMon计数器等,并使用结果选择一个选项.我做了一些研究,最后使用了我工作的Visual Studio Team Systems Developer版本的内置Performance Explorer.我找到了一个多部分系列的第二篇博客文章,解释了如何在这里设置它.基本上,您连接单元测试以指向您要分析的代码; 通过向导和一些配置; 并启动单元测试分析.我启用了.NET对象分配和生命周期指标.分析的结果很难为这个答案格式化,所以我把它们放在最后.如果您将文本复制并粘贴到Excel中并稍微按摩它们,它们将是可读的.
选项#1的内存效率最高,因为它使垃圾收集器的工作量减少了一半,并且它将一半的内存和实例分配给StringBuilder对象而不是选项#2.对于日常编码,选择#2选项非常好.
如果您还在阅读,我问了这个问题,因为选项#2将使经验C/C++开发人员的内存泄漏检测器变得暴躁.如果在重新分配之前未释放StringBuilder实例,则会发生巨大的内存泄漏.当然,我们C#开发人员并不担心这些事情(直到他们跳起来咬我们).谢谢大家!!
ClassName Instances TotalBytesAllocated Gen0_InstancesCollected Gen0BytesCollected Gen1InstancesCollected Gen1BytesCollected =======Option #1 System.Text.StringBuilder 100,001 2,000,020 100,016 2,000,320 2 40 System.String 301,020 32,587,168 201,147 11,165,268 3 246 System.Char[] 200,000 8,977,780 200,022 8,979,678 2 90 System.String[] 1 400,016 26 1,512 0 0 System.Int32 100,000 1,200,000 100,061 1,200,732 2 24 System.Object[] 100,000 2,000,000 100,070 2,004,092 2 40 ======Option #2 System.Text.StringBuilder 200,000 4,000,000 200,011 4,000,220 4 80 System.String 401,018 37,587,036 301,127 16,164,318 3 214 System.Char[] 200,000 9,377,780 200,024 9,379,768 0 0 System.String[] 1 400,016 20 1,208 0 0 System.Int32 100,000 1,200,000 100,051 1,200,612 1 12 System.Object[] 100,000 2,000,000 100,058 2,003,004 1 20
Jon Skeet.. 6
选项2应该(我相信)实际上胜过选项1.调用Remove
"强制"StringBuilder 的行为获取它已经返回的字符串的副本.该字符串在StringBuilder中实际上是可变的,并且除非需要,否则StringBuilder不会获取副本.使用选项1,它在基本清除阵列之前复制 - 使用选项2不需要复制.
选项2的唯一缺点是,如果字符串最终变长,则在附加时会有多个副本 - 而选项1保留缓冲区的原始大小.但是,如果情况确实如此,请指定初始容量以避免额外复制.(在您的示例代码中,字符串最终将大于默认的16个字符 - 使用容量为32来初始化它将减少所需的额外字符串.)
然而,除了性能之外,选项2更简洁.
几个答案温和地暗示我脱下我的笨蛋并自己弄明白,所以下面是我的结果.我认为这种情绪通常与这个网站的内容相悖,但如果你想做正确的事情,你可能会做.... :)
我修改了选项#1以利用@Ty建议使用StringBuilder.Length = 0而不是Remove方法.这使得两个选项的代码更相似.现在两个不同之处在于StringBuilder的构造函数是在循环中还是在循环之外,而选项#1现在使用Length方法来清除StringBuilder.这两个选项都设置为在具有100,000个元素的outputStrings数组上运行,以使垃圾收集器完成一些工作.
几个答案提供了一些提示,以查看各种PerfMon计数器等,并使用结果选择一个选项.我做了一些研究,最后使用了我工作的Visual Studio Team Systems Developer版本的内置Performance Explorer.我找到了一个多部分系列的第二篇博客文章,解释了如何在这里设置它.基本上,您连接单元测试以指向您要分析的代码; 通过向导和一些配置; 并启动单元测试分析.我启用了.NET对象分配和生命周期指标.分析的结果很难为这个答案格式化,所以我把它们放在最后.如果您将文本复制并粘贴到Excel中并稍微按摩它们,它们将是可读的.
选项#1的内存效率最高,因为它使垃圾收集器的工作量减少了一半,并且它将一半的内存和实例分配给StringBuilder对象而不是选项#2.对于日常编码,选择#2选项非常好.
如果您还在阅读,我问了这个问题,因为选项#2将使经验C/C++开发人员的内存泄漏检测器变得暴躁.如果在重新分配之前未释放StringBuilder实例,则会发生巨大的内存泄漏.当然,我们C#开发人员并不担心这些事情(直到他们跳起来咬我们).谢谢大家!!
ClassName Instances TotalBytesAllocated Gen0_InstancesCollected Gen0BytesCollected Gen1InstancesCollected Gen1BytesCollected =======Option #1 System.Text.StringBuilder 100,001 2,000,020 100,016 2,000,320 2 40 System.String 301,020 32,587,168 201,147 11,165,268 3 246 System.Char[] 200,000 8,977,780 200,022 8,979,678 2 90 System.String[] 1 400,016 26 1,512 0 0 System.Int32 100,000 1,200,000 100,061 1,200,732 2 24 System.Object[] 100,000 2,000,000 100,070 2,004,092 2 40 ======Option #2 System.Text.StringBuilder 200,000 4,000,000 200,011 4,000,220 4 80 System.String 401,018 37,587,036 301,127 16,164,318 3 214 System.Char[] 200,000 9,377,780 200,024 9,379,768 0 0 System.String[] 1 400,016 20 1,208 0 0 System.Int32 100,000 1,200,000 100,051 1,200,612 1 12 System.Object[] 100,000 2,000,000 100,058 2,003,004 1 20
选项2应该(我相信)实际上胜过选项1.调用Remove
"强制"StringBuilder 的行为获取它已经返回的字符串的副本.该字符串在StringBuilder中实际上是可变的,并且除非需要,否则StringBuilder不会获取副本.使用选项1,它在基本清除阵列之前复制 - 使用选项2不需要复制.
选项2的唯一缺点是,如果字符串最终变长,则在附加时会有多个副本 - 而选项1保留缓冲区的原始大小.但是,如果情况确实如此,请指定初始容量以避免额外复制.(在您的示例代码中,字符串最终将大于默认的16个字符 - 使用容量为32来初始化它将减少所需的额外字符串.)
然而,除了性能之外,选项2更简洁.