为什么比在一个语句中添加字符串更快地连接多行中的字符串?

ric*_*cky 1 c# string concatenation

为什么速度排名是: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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 5

首先,您的基准测试的几点:

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

这里有各种费用:

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

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

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

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

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

我已经删除了int.ToString()你的基准测试的本地副本中的部分,结果仍然存在 - 但更清晰,因为开销较少.