本地字符串变量的实例化会影响性能吗?

时间:2013-03-22 20:56:44

标签: c# string performance initialization il

我有两种情况:

    static void CreateCopyOfString()
    {
        string s = "Hello";
        ProcessString(s);
    }

    static void DoNotCreateCopyOfString()
    {
        ProcessString("Hello");
    }

这两种情况的IL看起来像这样:

    .method private hidebysig static void  CreateCopyOfString() cil managed
    {
        // Code size       15 (0xf)
        .maxstack  1
        .locals init ([0] string s)
        IL_0000:  nop
        IL_0001:  ldstr      "Hello"
        IL_0006:  stloc.0
        IL_0007:  ldloc.0
        IL_0008:  call       void ConsoleApplication1.Program::ProcessString(string)
        IL_000d:  nop
        IL_000e:  ret
    } // end of method Program::CreateCopyOfString

    .method private hidebysig static void  DoNotCreateCopyOfString() cil managed
    {
          // Code size       13 (0xd)
          .maxstack  8
          IL_0000:  nop
          IL_0001:  ldstr      "Hello"
          IL_0006:  call       void ConsoleApplication1.Program::ProcessString(string)
          IL_000b:  nop
          IL_000c:  ret
    } // end of method Program::DoNotCreateCopyOfString

在第一种情况下,会额外调用string initstloc.0ldloc.0。 这是否意味着第一种情况比第二种情况更弱,其中字符串直接传递给方法而不是首先将其存储在局部变量中?

我看到了问题Does initialization of local variable with null impacts performance?,但它似乎与我在这里需要知道的有点不同。感谢。

2 个答案:

答案 0 :(得分:11)

你一直在看未经优化的IL,因为所有的" nop" s。您可能在构建发布版本时发现它生成了不同的代码。

即使使用未经优化的版本,如果您在优化的JIT下运行,我也希望它最终使用相同的JITted代码。

即使有一个非优化的JIT 实际生成的代码在每次调用时都做了更多工作,我也会错开,看看这对任何实际应用都有重大影响

一如既往:

  • 在开始之前设置效果目标,并衡量
  • 确定以后在性能方面难以解决哪些决策,并担心这些决定远远超过此类决定,以后可以更改,而不影响其他地方。
  • 编写最简单,最易读的代码。
  • 如果表现不佳,请调查是否进行了有害可读性的更改,以帮助提高效果以保证痛苦。

答案 1 :(得分:1)

不,它不会影响性能。 您可以通过验证为两者生成的机器代码是否相同来确认这一点。 请注意,在优化的JIT中,ProcessString可能是内联的。为避免这种情况,您可以添加[MethodImpl(MethodImplOptions.NoInlining)]。 编译优化(发布)版本。

  1. 在WinDbg中打开可执行文件。根据您的EXE使用匹配的32位或64位版本。
  2. 键入sxe ld clrjit以在加载clrjit.dll时中断。输入g继续,直到休息。
  3. 使用.loadby sos clr加载SOS。请注意,对于早期的CLR版本,您需要使用mscorwks而不是clr。
  4. 使用!name2ee * <full class name>查找方法表的地址。
  5. 键入!dumpmt -md <address of MethoTable>以转储方法详细信息。请注意,此时CreateCopyOfString和DoNotCreateCopyOfString尚未进行JIT。
  6. 键入!bpmd <full class name>.CreateCopyOfString!bpmd <full class name>.DoNotCreateCopyOfString以在调用方法时中断。输入g继续。也可以使用!bpmd -md <address of MethodDesc>来设置断点。
  7. 当遇到断点时,键入!u <address of MethodDesc>以转储该方法的机器代码。
  8. 请注意,当我尝试这个时,只有一个方法是JITed,大概是因为运行时确定两个方法是相同的并且JITing另一个是不必要的。因此,我在适当的时候注释掉了这个电话,并重复了一下以获取机器代码。

    实际的寄存器和地址会有所不同,但这两种方法都产生了以下机器代码:

    sub     rsp,28h
    mov     rcx,121E3258h
    mov     rcx,qword ptr [rcx]
    call    000007fe`9852c038
    nop
    add     rsp,28h
    ret
    

    因此,您可以得出结论,由于正在执行相同的机器代码,因此任一方法的性能都是相同的。