哪个字符串串联操作更快+";或字符串.凹面

本文关键字:字符串 quot 凹面 操作 | 更新日期: 2023-09-27 17:59:18

我一直在阅读不同的答案,哪种字符串串联操作更好。我在某个地方读到"+"在内部调用string.Concat方法,而string.Concat在两者之间更快。当我查看IL代码时,它似乎并没有建议使用上述语句。

对于

string s1 = "1" + "2";

IL代码

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       8 (0x8)
  .maxstack  1
  .locals init ([0] string s1)
  IL_0000:  nop
  IL_0001:  ldstr      "12"
  IL_0006:  stloc.0
  IL_0007:  ret
} // end of method Program::Main

此外,我从IL代码中注意到,"+"只初始化一个字符串,而字符串。Concat初始化要连接的两个字符串。我也试过使用多个字符串。在使用内存方面,"+"似乎只使用了一个字符串变量,而另一个选项在内部使用了更多的变量。

对于,

string s1 = string.concat("1", "2");

IL代码

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       18 (0x12)
  .maxstack  2
  .locals init ([0] string s1)
  IL_0000:  nop
  IL_0001:  ldstr      "1"
  IL_0006:  ldstr      "2"
  IL_000b:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_0010:  stloc.0
  IL_0011:  ret
} // end of method Program::Main

所以,我们可以从上面的IL代码中得出结论,"+"比"string.Concat"更快,因为它使用较小的变量来执行相同的操作吗?

哪个字符串串联操作更快+";或字符串.凹面

这种比较是错误的(假设您对字符串常量的串联不感兴趣)。

在您的第一个代码段中,连接已经由C#编译器执行:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       8 (0x8)
  .maxstack  1
  .locals init ([0] string s1)
  IL_0000:  nop
  IL_0001:  ldstr      "12"      // The strings are already concatenated in the IL.
  IL_0006:  stloc.0
  IL_0007:  ret
}

在第二个片段中,对string.Concat的调用仍然是:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       18 (0x12)
  .maxstack  2
  .locals init ([0] string s1)
  IL_0000:  nop
  IL_0001:  ldstr      "1"
  IL_0006:  ldstr      "2"
  IL_000b:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_0010:  stloc.0
  IL_0011:  ret
}

因此,尝试使用常量来辨别两个片段的性能是没有意义的,因为您会得到不具有代表性的结果。

在一般情况下,C#编译器将在字符串上编译一个+运算符链,作为对string.Concat的单个调用。您可以通过执行与您所做的测试几乎相同的测试来验证这一点,使用变量而不是常量。

作为演示,请考虑这两种C#方法。使用+连接字符串:

static string Plus(string a, string b, string c)
{
    return a + b + c;
}

另一个调用string.Concat:

static string Concat(string a, string b, string c)
{
    return string.Concat(a, b, c);
}

现在看看他们各自的IL,使用调试配置:

.method private hidebysig static string Plus (
        string a,
        string b,
        string c
    ) cil managed 
{
    .locals init (
        [0] string V_0
    )
    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.1
    IL_0003: ldarg.2
    IL_0004: call string [mscorlib]System.String::Concat(string,  string,  string)
    IL_0009: stloc.0
    IL_000a: br.s IL_000c
    IL_000c: ldloc.0
    IL_000d: ret
}

和:

.method private hidebysig static string Concat (
        string a,
        string b,
        string c
    ) cil managed 
{
    .locals init (
        [0] string V_0
    )
    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.1
    IL_0003: ldarg.2
    IL_0004: call string [mscorlib]System.String::Concat(string,  string,  string)
    IL_0009: stloc.0
    IL_000a: br.s IL_000c
    IL_000c: ldloc.0
    IL_000d: ret
}

他们完全一样(除了名字)。如果我们使用Release配置构建,我们会得到更短的IL,但两种方法仍然相同。

总之,在这种特殊情况下,我们可以放心地假设,在表达同一事物的两种方式之间,我们不会观察到任何性能差异。

在一般情况下(IL不相同或几乎相同),我们不能基于CLR的心理模型对性能做出任何假设。即使我们确实有一个完全准确的CLR心理模型,我们也必须考虑字节码最终会得到编译的机器代码,这与IL不同(例如,x86代码有寄存器,但IL没有)。

为了说明性能,我们使用了评测器,因为它们可以为我们提供实用、详细的指标。

String类中没有Operator +,因此整个+转换为String.Concat是由C#编译器完成的,因此它们是相同的。

停止对代码进行微优化或过早优化。试着编写一个执行正确的代码,然后如果以后遇到性能问题,则对应用程序进行评测,看看问题出在哪里。字符串是不可变的。如果您有一段代码由于字符串串联而出现性能问题,则应该考虑使用StringBuilder。

我们应该忘记小效率,比如说97%的时间:过早优化是万恶之源。然而我们不应该通过在关键的3%中增加我们的机会