为什么在多行中串联字符串比在一个语句中添加字符串快
本文关键字:字符串 一个 语句 添加 为什么 | 更新日期: 2023-09-27 17:57:36
为什么速度等级为: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
}
}
}
首先,关于基准测试的几点:
- 我建议在计时开始之前为每个方法添加一个调用,这样就不会影响JIT编译时间
- 我建议在每次测试后添加对
GC.Collect()
的调用,这样一个测试创建的垃圾就不会影响另一个测试 - x64与x86可能会得到明显不同的结果
这里有各种成本:
- 方法调用开销
- 字符串创建
- 数组创建(隐藏)
- 整数到字符串的转换
在第一种方法中,编译器实际上是将代码转换为:
string[] bits = new string[] { "a = ", a.ToString(),
"b = ", b,
"c = ", c.ToString(),
"d = ", d };
return string.Concat(bits);
第二种和第三种方法创建几个中间字符串;看起来它们创建了大致相同的数量的中间字符串(因为第二种方法中的每一行都创建两个),但第三种方法需要更多的复制-中间字符串都包含"到目前为止的整个字符串",而第二种方法中的一半中间字符串只是短的("b = " + b
等)。这可能是造成差异的原因。
在这种情况下,我怀疑第一种方法中的数组创建(和填充)成本超过了第二种方法中中间字符串的成本。事实上,将第一个方法更改为只是创建和填充数组,这似乎占用了一半以上的运行时间(在我的机器上)。这个创建/人口也包括int.ToString()
呼叫,请注意。。。这似乎也是费用的很大一部分。
我已经在基准测试的本地副本中删除了int.ToString()
部分,结果仍然有效,但由于开销较少,因此更加清晰。
可能是因为编译器优化,检查IL,事实上,我想我记得Eric Lippert自己为MS的C#编译器写过这样的优化。
是的,它是:http://ericlippert.com/2013/06/17/string-concatenation-behind-the-scenes-part-one/
它们的编译方式不同。
看看:
.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
}