【问题标题】:Performance creating concatenated strings创建连接字符串的性能
【发布时间】:2013-03-10 21:27:16
【问题描述】:

C# 中哪种创建字符串的方式运行时效率更高

第一名:

bool value = true;
int channel = 1;
String s = String.Format(":Channel{0}:Display {1}", channel, value ? "ON" : "OFF");

第二个:

bool value = true;
String channel = "1";
string  s = ":Channel" + channel + ":Display " + value ? "ON" : "OFF";

【问题讨论】:

  • 您确定这是一个性能问题吗?选择更易读的一个(IMO Format
  • @Bart Friederichs:性能问题通常没有共同点。尝试按照其他人的解决方案解决自己的性能问题通常是个坏主意,因为即使环境的微小差异也可能带来巨大的结果差异。
  • 不,没有性能问题。我知道预优化是万恶之源。但我来自其他性能罕见的硬件。如果我实现了一些新的东西,不管我是这样做还是那样做都没有关系——所以我可以第一次就做好。之后的所有更改都很糟糕......
  • @DGibbs 真;我认为应该是string s = ":Channel" + channel + ":Display " + (value ? "ON" : "OFF");
  • 第一个无论性能如何都要好得多:第一个技术允许您将字符串隔离到可本地化的资源中

标签: c# string performance


【解决方案1】:

我现在要添加一个示例来说明编译器如何处理这两者,因为在其他一些答案中似乎存在 很多 混淆(编辑:请注意,这些混淆中的大部分现在已被删除/编辑掉):

bool value = true;
int channel = 1;
String s = String.Format(":Channel{0}:Display {1}", channel,
    value ? "ON" : "OFF");

最后一行编译为:

String s = String.Format(":Channel{0}:Display {1}",
    new object[2] {(object)channel, value ? "ON" : "OFF")};

有趣的是,为int channel 创建了一个数组和一个“盒子”,当然还有寻找{0} / {1} 所需的额外解析。

现在第 2 个:

bool value = true;
String channel = "1";
string  s = ":Channel" + channel + ":Display " + (value ? "ON" : "OFF");

最后一行编译为:

string s = string.Concat(":Channel", channel, ":Display ", value ? "ON" : "OFF");

这里没有数组,没有盒子(channel 现在是一个字符串),也没有解析。重载为public static string string.Concat(string,string,string,string)string.Concat 实现非常高效,提前预分配一个大小合适的字符串然后覆盖等等。

在大多数代码中,两者都可以。第二个版本在技术上更高效(没有盒子,没有数组,没有解析),但是如果你以后想国际化,那就是一个主要的痛苦。在大多数应用程序中,您不会注意到两者之间有任何区别。解析速度很快,并且盒子/数组作为 gen-zero 对象被廉价地收集。但便宜不是免费的。

【讨论】:

    【解决方案2】:

    这可以帮助您自己测试。这是使用 .net v4.0.30319 运行时执行的。

    sw = new System.Diagnostics.Stopwatch();
    
    // Number 1
    bool value = true;
    int channel = 1;
    sw.Start();
    for (int i = 0; i <= 100000; i++)
    {
        String s = String.Format(":Channel{0}:Display {1}", channel, value ? "ON" : "OFF");
    }
    sw.Stop();
    
    sw.Reset();
    
    // Number 2
    sw.Start();
    for (int i = 0; i <= 100000; i++)
    {
        string s = "Channel" + channel + ":Display " + (value ? "ON" : "OFF");
    }
    sw.Stop();
    

    我的结果是:

    数字 1:136 毫秒

    数字 2:91 毫秒

    所以第二个选项具有更好的性能。第一个选项使用额外的方法调用 (string.Format()) 和替换参数(正如 Marc 所说)这一事实是不同的。

    如果我不使用 100.000 次迭代,而是使用 1.000.000,这就是我得到的

    数字 1:1308 毫秒

    数字 2:923 毫秒

    基本上,同样的结论。

    【讨论】:

    • 考虑到计时精度,我个人认为这 16 毫秒和 12 毫秒不够明确
    • @MarcGravell:对不起,当你写这篇评论时,我正在添加另一个有 100.000 次迭代的示例。
    • 这更像是 ;) 现在您所要做的就是在完全相同的 .NET(子)版本、操作系统(子)版本和完全相同的硬件上运行它后台负载条件...(好吧,我现在在开玩笑)
    • @MarcGravell:恕我直言,对于同一个 .net 运行时,结果似乎是非常确定的。执行时间可能会因机器而异,但考虑到我的测试显示的巨大差异,第二个选项更有效(至少从时间执行的角度来看)。
    • 确实;就像我已经说过的那样 - 这些数字要好得多; 12 vs 16...不是很确定
    【解决方案3】:

    关于 Marc Gravell 的回答:您可以通过传递 channel.ToString() 来避免装箱操作。我已经根据经验验证了这对#1 和#2 都有轻微的性能改进。

    • 在情况 #1 中,您可以避免装箱操作。
    • 在情况 #2 中,您避免调用隐式转换运算符。

    但是在试图反驳你的理论之后,(使用更长的连接和随机字符串)我必须承认 #2 比 #1 快。

    【讨论】:

      【解决方案4】:

      1 号是最有效的。

      String.Format() 函数在内部使用 System.Text.StringBuilder 类型来连接字符串。

      StringBuilder 用于表示可变(又名可编辑)字符串,它是连接字符串的最有效方式。 http://msdn.microsoft.com/en-us/library/system.text.stringbuilder.aspx.

      【讨论】:

      • 推理不完整;第二个版本使用string.Concat进行了高度优化。可以说更是如此,因为它使用快速分配和覆盖方法,而不是调整大小的后备缓冲区。如果这是一个循环,那么肯定:StringBuilder 会赢得胜利。对于单行连接:否; string.Concat 是最合适的方法,可以使用第二个示例中显示的代码来完成。
      • 来自MSDNUse the String class if you are concatenating a fixed number of String objects. In that case, the compiler may even combine individual concatenation operations into a single operation.
      • 您的编辑使这个答案变得更糟并且非常不正确。抱歉,但我认为您不了解编译器如何处理第二个示例。提示:这不是你所描述的。
      • 这是 Microsoft 使用 string.Concat() 指南的例外:“...当性能很重要时,您应该始终使用 StringBuilder 类来连接字符串。...”msdn.microsoft.com/en-us/library/ms228504.aspx
      • 作为一般经验法则,StringBuilder 仅在您连接非常大的字符串或非常多的字符串(可能在循环内)的情况下更有效。像“Hello”+“World”这样的东西将归结为String.Concat,这更适合这种情况,因为我们在编译之前就已经知道字符串的大小,并且不需要 Stringbuilder 提供的动态调整大小
      最近更新 更多