【问题标题】:Does instantiation of local string variable impact performance?本地字符串变量的实例化会影响性能吗?
【发布时间】:2013-03-22 20:56:43
【问题描述】:

我有两种情况:

    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? 的问题,但它似乎与我在这里需要知道的有点不同。谢谢。

【问题讨论】:

  • 你是如何编译代码的?是处于调试模式还是发布模式?我相信,在 Release 中,两个 IL 看起来完全一样。
  • 它是在调试中编译的。让我看看释放模式。
  • 与发布版本相同的结果。

标签: c# string performance initialization il


【解决方案1】:

一方面,您正在查看未优化的 IL - 因此所有“nop”。您可能会发现它在构建 Release 版本时会生成不同的代码。

即使使用未优化的版本,如果您在优化的 JIT 下运行,我希望它以相同的 JIT 代码结束。

即使使用非优化 JIT,确实实际上生成的代码在每次调用它时都会做更多的工作,我会惊讶地看到这对任何实际应用程序都有重大影响。

一如既往:

  • 在开始之前设定绩效目标,然后衡量这些目标。
  • 找出哪些决策以后在性能方面难以修复,并且比此类决策更担心这些决策,这些决策可以在以后更改而不会影响其他地方。
  • 编写最简单、最易读的代码。
  • 如果表现不够好,请调查是否做出损害可读性的更改有助于提高性能,从而避免痛苦。

【讨论】:

  • 除非您的 vm 通过将其通过电子邮件发送到远程服务器执行 il 来执行...我想知道,stloc/ldloc 对的循环相对成本是多少?你觉得它很可能被优化成一个寄存器?
  • @JerKimball:很可能。但是优化 JIT 真的不需要非常聪明就能发现这一点:)
  • 除了“nop”的存在,我看不出调试和发布版本有任何区别。
  • 可以肯定的是,前一种方法的可读性更好,使用类似 string.Format 的更复杂的字符串形成。这就是我争论是否让一个局部变量保存字符串然后将其传递给以下方法的地方。
  • @ashtee:无需争论——只需编写简单的代码即可。将解析格式字符串、构建新字符串等的复杂性与存储引用然后再次加载它的可能工作(在 JITting 之后可能甚至不存在)进行比较。
【解决方案2】:

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

  1. 在 WinDbg 中打开可执行文件。根据您的 EXE 使用匹配的 32 位或 64 位版本。
  2. 在加载 clrjit.dll 时键入 sxe ld clrjit 以中断。输入 g 继续直到休息。
  3. 使用.loadby sos clr 加载 SOS。请注意,对于早期的 CLR 版本,您需要使用 mscorwks 而不是 clr。
  4. !name2ee * <full class name>查找方法表的地址。
  5. 键入!dumpmt -md <address of MethoTable> 转储方法详细信息。请注意此时 CreateCopyOfString 和 DoNotCreateCopyOfString 尚未 JITed。
  6. 键入!bpmd <full class name>.CreateCopyOfString!bpmd <full class name>.DoNotCreateCopyOfString 以在调用方法时中断。键入 g 继续。也可以使用!bpmd -md <address of MethodDesc> 设置断点。
  7. 遇到断点时,键入!u <address of MethodDesc> 以转储该方法的机器代码。

请注意,当我尝试这个时,只有一个方法是 JITed,大概是因为运行时确定这两个方法是相同的,而另一个 JITing 是不必要的。因此,我酌情将调用注释掉并重复获取机器码。

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

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

因此,您可能会得出结论,由于正在执行相同的机器代码,因此两种方法的性能将是相同的。

【讨论】:

  • 感谢您的澄清。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-12
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 2016-03-30
  • 2019-09-30
相关资源
最近更新 更多