【问题标题】:Why concatenating strings in multiple lines faster than Adding String In One Statement?为什么在多行中连接字符串比在一个语句中添加字符串更快?
【发布时间】:2014-04-09 07:43:06
【问题描述】:

为什么速度排名是:AddStringInMutipleStatement > AddStringInOneStatement > AddStringInMutipleStatementEx

它是否与运行时创建的临时字符串对象有关?细节是什么?

namespace Test
{
    class Program
    {
        static string AddStringInOneStatement(int a, string b, int c, string d)
        {
            string str;
            str = "a = " + a.ToString() + "b = " + b + "c = " + c.ToString() + "d = " + d; 
            return str;
        }

        static string AddStringInMutipleStatement(int a, string b, int c, string d)
        {
            string str = "";
            str += "a = " + a.ToString();
            str += "b = " + b;
            str += "c = " + c.ToString();
            str += "d = " + d;
            return str;
        }

        static string AddStringInMutipleStatementEx(int a, string b, int c, string d)
        {
            string str = "";
            str += "a = ";
            str += a.ToString();
            str += "b = ";
            str += b;
            str += "c = ";
            str += c.ToString();
            str += "d = ";
            str += d;
            return str;
        }

        static void Main(string[] args)
        {
            uint times = 10000000;
            Stopwatch timer = new Stopwatch();
            timer.Start();
            for (int i = 0; i < times; i++)
            {
                AddStringInOneStatement(1, "2", 3, "4");
            }
            timer.Stop();
            Console.WriteLine("First: " + timer.ElapsedMilliseconds); // 4341 ms
            timer.Reset();
            timer.Start();
            for (int i = 0; i < times; i++)
            {
                AddStringInMutipleStatement(1, "2", 3, "4");
            }
            timer.Stop();
            Console.WriteLine("Second: " + timer.ElapsedMilliseconds); // 3427 ms
            timer.Reset();
            timer.Start();
            for (int i = 0; i < times; i++)
            {
                AddStringInMutipleStatementEx(1, "2", 3, "4");
            }
            timer.Stop();
            Console.WriteLine("Second: " + timer.ElapsedMilliseconds); // 5701 ms
        }
    }
}

【问题讨论】:

  • 我建议在这里使用 String.Format()。
  • 您如何对速度进行基准测试?
  • +1,这是一个合理的问题。我可以重现您的观察结果(发布版本):3078ms、2530ms、3297ms。
  • 你也应该在这里使用 StringBuilder 进行这样的连接,并有助于内存使用,也许还有性能。

标签: c# string concatenation


【解决方案1】:

首先,关于基准测试的几点:

  • 我建议在计时开始之前添加对每个方法的调用,这样您就不会测量 JIT 编译时间
  • 我建议在每次测试后添加对 GC.Collect() 的调用,以便一个测试创建的垃圾不会影响另一个测试
  • 您可能会在 x64 与 x86 上得到明显不同的结果

这里有各种费用:

  • 方法调用开销
  • 字符串创建
  • 数组创建(隐藏)
  • 整数到字符串的转换

在第一种方法中,编译器实际上是将您的代码转换为:

string[] bits = new string[] { "a = ", a.ToString(),
                               "b = ", b,
                               "c = ", c.ToString(),
                               "d = ", d };
return string.Concat(bits);

第二种和第三种方法创建几个中间字符串;看起来它们创建了大致相同的 number 个中间体(因为第二种方法中的每一行都创建了两个)但第三种方法需要更多的复制 - 中间字符串都包含“到目前为止的整个字符串" 而第二种方法中有一半的中间字符串很短("b = " + b 等)。这可能是造成差异的原因。

我怀疑在这种情况下,第一种方法中数组创建(和填充)的成本超过了第二种方法中中间字符串的成本。实际上,将第一种方法更改为 just 创建和填充数组,这似乎占用了一半以上的运行时间(在我的机器上)。该创建/填充还包括int.ToString() 调用,请注意……这似乎也是费用的很大一部分。

我已在您的基准测试的本地副本中删除了 int.ToString() 部分,结果仍然成立 - 但更清晰,因为持续开销更少。

【讨论】:

  • 谢谢乔恩!我想问更多关于: str += "a = " + a.ToString();它是否只创建 1 个中间字符串?我认为它就像: string temp1 = "a = " + a.ToString();字符串 temp2 = str + temp1; str = temp2;我错了吗?
  • 一般来说,在调用Collect 之后运行WaitForPendingFinalizers 也不是一个坏主意,尽管在这种情况下当然不会有什么不同。
【解决方案2】:

可能是因为编译器优化,检查 IL,事实上我想我记得 Eric Lippert 自己为 MS 的 C# 编译器编写了这样的优化。

是的,这里是:http://ericlippert.com/2013/06/17/string-concatenation-behind-the-scenes-part-one/

【讨论】:

    【解决方案3】:

    它们的编译方式不同。

    看看:

    .method private hidebysig static string AddStringInOneStatement(int32 a, string b, int32 c, string d) cil managed
    {
        .maxstack 3
        .locals init (
            [0] string str,
            [1] string CS$1$0000,
            [2] string[] CS$0$0001)
        L_0000: nop 
        L_0001: ldc.i4.8 
        L_0002: newarr string
        L_0007: stloc.2 
        L_0008: ldloc.2 
        L_0009: ldc.i4.0 
        L_000a: ldstr "a = "
        L_000f: stelem.ref 
        L_0010: ldloc.2 
        L_0011: ldc.i4.1 
        L_0012: ldarga.s a
        L_0014: call instance string [mscorlib]System.Int32::ToString()
        L_0019: stelem.ref 
        L_001a: ldloc.2 
        L_001b: ldc.i4.2 
        L_001c: ldstr "b = "
        L_0021: stelem.ref 
        L_0022: ldloc.2 
        L_0023: ldc.i4.3 
        L_0024: ldarg.1 
        L_0025: stelem.ref 
        L_0026: ldloc.2 
        L_0027: ldc.i4.4 
        L_0028: ldstr "c = "
        L_002d: stelem.ref 
        L_002e: ldloc.2 
        L_002f: ldc.i4.5 
        L_0030: ldarga.s c
        L_0032: call instance string [mscorlib]System.Int32::ToString()
        L_0037: stelem.ref 
        L_0038: ldloc.2 
        L_0039: ldc.i4.6 
        L_003a: ldstr "d = "
        L_003f: stelem.ref 
        L_0040: ldloc.2 
        L_0041: ldc.i4.7 
        L_0042: ldarg.3 
        L_0043: stelem.ref 
        L_0044: ldloc.2 
        L_0045: call string [mscorlib]System.String::Concat(string[])
        L_004a: stloc.0 
        L_004b: ldloc.0 
        L_004c: stloc.1 
        L_004d: br.s L_004f
        L_004f: ldloc.1 
        L_0050: ret 
    }
    

    ...

    .method private hidebysig static string AddStringInMutipleStatement(int32 a, string b, int32 c, string d) cil managed
    {
        .maxstack 3
        .locals init (
            [0] string str,
            [1] string CS$1$0000)
        L_0000: nop 
        L_0001: ldstr ""
        L_0006: stloc.0 
        L_0007: ldloc.0 
        L_0008: ldstr "a = "
        L_000d: ldarga.s a
        L_000f: call instance string [mscorlib]System.Int32::ToString()
        L_0014: call string [mscorlib]System.String::Concat(string, string, string)
        L_0019: stloc.0 
        L_001a: ldloc.0 
        L_001b: ldstr "b = "
        L_0020: ldarg.1 
        L_0021: call string [mscorlib]System.String::Concat(string, string, string)
        L_0026: stloc.0 
        L_0027: ldloc.0 
        L_0028: ldstr "c = "
        L_002d: ldarga.s c
        L_002f: call instance string [mscorlib]System.Int32::ToString()
        L_0034: call string [mscorlib]System.String::Concat(string, string, string)
        L_0039: stloc.0 
        L_003a: ldloc.0 
        L_003b: ldstr "d = "
        L_0040: ldarg.3 
        L_0041: call string [mscorlib]System.String::Concat(string, string, string)
        L_0046: stloc.0 
        L_0047: ldloc.0 
        L_0048: stloc.1 
        L_0049: br.s L_004b
        L_004b: ldloc.1 
        L_004c: ret 
    }
    

    ...

    .method private hidebysig static string AddStringInMutipleStatementEx(int32 a, string b, int32 c, string d) cil managed
    {
        .maxstack 2
        .locals init (
            [0] string str,
            [1] string CS$1$0000)
        L_0000: nop 
        L_0001: ldstr ""
        L_0006: stloc.0 
        L_0007: ldloc.0 
        L_0008: ldstr "a = "
        L_000d: call string [mscorlib]System.String::Concat(string, string)
        L_0012: stloc.0 
        L_0013: ldloc.0 
        L_0014: ldarga.s a
        L_0016: call instance string [mscorlib]System.Int32::ToString()
        L_001b: call string [mscorlib]System.String::Concat(string, string)
        L_0020: stloc.0 
        L_0021: ldloc.0 
        L_0022: ldstr "b = "
        L_0027: call string [mscorlib]System.String::Concat(string, string)
        L_002c: stloc.0 
        L_002d: ldloc.0 
        L_002e: ldarg.1 
        L_002f: call string [mscorlib]System.String::Concat(string, string)
        L_0034: stloc.0 
        L_0035: ldloc.0 
        L_0036: ldstr "c = "
        L_003b: call string [mscorlib]System.String::Concat(string, string)
        L_0040: stloc.0 
        L_0041: ldloc.0 
        L_0042: ldarga.s c
        L_0044: call instance string [mscorlib]System.Int32::ToString()
        L_0049: call string [mscorlib]System.String::Concat(string, string)
        L_004e: stloc.0 
        L_004f: ldloc.0 
        L_0050: ldstr "d = "
        L_0055: call string [mscorlib]System.String::Concat(string, string)
        L_005a: stloc.0 
        L_005b: ldloc.0 
        L_005c: ldarg.3 
        L_005d: call string [mscorlib]System.String::Concat(string, string)
        L_0062: stloc.0 
        L_0063: ldloc.0 
        L_0064: stloc.1 
        L_0065: br.s L_0067
        L_0067: ldloc.1 
        L_0068: ret 
    }
    

    【讨论】:

      猜你喜欢
      • 2020-12-18
      • 2011-11-10
      • 2013-12-09
      • 2021-01-28
      • 1970-01-01
      • 2014-03-15
      • 1970-01-01
      • 2017-06-30
      • 1970-01-01
      相关资源
      最近更新 更多