【问题标题】:Does call method slow down performance?调用方法会降低性能吗?
【发布时间】:2011-12-08 03:13:27
【问题描述】:

例如:
代码 1:

void Main()
{
    Console.WriteLine("Some texts");
}

代码 2:

void Main()
{
    Foo();
}

void Foo()
{
    Console.WriteLine("Some texts");
}

代码 2 的运行速度是否比代码 1 慢?虽然当我们构建版本时,JIT 将内联代码 2,因此代码 2 的运行速度与代码 1 一样快。但是当我使用 LinqPad 测试它们时,我得到了 IL 结果:

代码 1:

IL_0000:  ldstr       "Some texts"
IL_0005:  call        System.Console.WriteLine

代码 2:

IL_0000:  ldarg.0     
IL_0001:  call        UserQuery.Foo

Foo:
IL_0000:  ldstr       "Some texts"
IL_0005:  call        System.Console.WriteLine
IL_000A:  ret      

我们可以看到代码 2 中的 IL 结果有一些额外的步骤来调用 Foo(),这是否证明代码 2 运行速度比代码 1 慢?

【问题讨论】:

  • 您是在调试模式还是发布模式下运行?
  • @Tudor :我在LinqPad 中对它们进行了测试,但我不知道它以哪种模式运行。有没有办法让 Visual Studio 像这样显示 IL 结果?
  • 你有两匹马。你想知道哪个更快。您是否 (1) 在互联网上随机询问陌生人哪一个更快,或者 (2) 与马匹比赛,看看哪一个获胜?
  • 你有一个程序,里面有一个Console.Writeline,你想知道函数调用是否会减慢它的速度?你需要一种透视感。您说的可能需要 186 光英里,而函数调用可能需要 1 光英尺。

标签: c# performance jit


【解决方案1】:

首先,您查看的是 IL,而不是 JITted 汇编代码。你所展示的并不能证明什么。您需要查看 JITted 输出以查看 JITter 是否内联代码。请注意,JITter 因平台而异(例如 x86 与 x64)以及框架版本与框架版本不同。

其次,当然,作为书面版本,第二版的运行速度会比第一版慢。当我说“按书面规定”时,我的意思是假设 JITter 没有内联版本二中的调用。额外的调用添加了一些机器指令,这些指令当然需要一些额外的周期来执行(同样,不要忘记我说过“按原样写!”)。然而,性能上的差异极不可能是有意义的。您必须在最紧凑的循环中进行数万亿次迭代才能看到有意义的性能差异。

【讨论】:

  • 最后,JITer 会在版本二中内联调用吗?
  • 如果你真的想知道,编译这两个版本,将它们推过 JITter 并查看输出的程序集。
  • 涉及两个编译器。您看到的是 c# 编译器的输出,它生成 IL 代码。当你的程序运行时,这个 IL 代码将被 jitted,这意味着第二个编译器,一个即时编译器,将为微处理器生成机器代码。即使 c# 编译器没有内联方法,抖动也会。
【解决方案2】:

是的方法调用会稍微减慢代码执行速度,如果它们没有被 c#-compiler 或 jit-compiler 内联。但是,除非您的代码在循环中运行并被执行一百万次左右,否则您应该真正专注于生成干净、可理解和可维护的代码。当我开始编写以毫秒或微秒为单位的单个语句的执行时间时。今天,它们以纳秒为单位。时间通常主要浪费在 I/O 操作上。有时也可以归咎于糟糕的算法。如果您的设计结构清晰,则与从一开始就进行时间优化并因此可能结构不佳的代码相比,将性能不佳的代码部分替换为更好的代码部分会容易得多。

我最近经历过。我必须在 Visio 中的 c# 程序中生成复杂的图形。事实证明,Visio 自动化非常慢。创建图形需要几分钟。幸运的是,我已经将所有图形内容放在了一个组件中,该组件通过产品中性界面公开了图形命令。即:界面不包含任何 Visio 特定的东西。用新的 SVG 组件替换我的 Visio 组件非常容易,它在不到一秒的时间内完成了相同的任务。此外,绝对不需要对我的算法或程序的任何其他部分进行任何更改。

当然,我的图形包装器组件添加了更多方法调用。此外,它是通过一个接口访问的,这进一步减慢了整个过程。然而,最终,正是这个接口和这些额外的方法调用,让我能够实现一个更快的解决方案。请记住:分钟与不到一秒!

【讨论】:

    【解决方案3】:

    在这种情况下,由于这是我最后一次阅读此类内容的实现,是的,它将被内联。

    一般来说,答案可能是——关于内联规则的文档是信息性材料,主要是在博客中,而不是规范性文档。不仅版本之间的细节会发生变化,而且几乎肯定会发生变化。

    在实践中:

    如果您怀疑性能热点可以从手动内联中受益,请尝试并再次进行分析。或者至少看看那个特定片段的 jitted 代码。

    理论上:

    不过,我喜欢知道小方法是内联的,都是一样的。

    【讨论】:

      【解决方案4】:

      它是如此微不足道(阅读:可能有),你不应该担心它。

      【讨论】:

        【解决方案5】:

        C# 编译器不进行内联,这就是您在 IL 代码中看到的内容。内联是 JIT 优化器的工作,它在运行时执行(如果它决定内联一个函数会使程序更高效)。

        【讨论】:

          【解决方案6】:

          编译器创建自己对代码的解释(汇编代码),因此代码示例将在同一件事中为 CPU 处理。 (在释放模式下)

          【讨论】:

          • 我假设您指的是 JIT 编译器,因为我相信 C# 编译器不会生成汇编代码。此外,编译器很可能是女性:p
          • 据我所知,C# 编译器仍然会生成汇编代码。但是你可能对编译器是女性是正确的。这就是为什么她从不给我警告,她只是在事后抱怨:P
          • @Lukazoid(和 stackr)也许你们两个在这里使用“组装”来表示不同的东西。 C# 编译器创建包含 IL 的程序集。 JIT 编译器创建机器码;机器代码有时被称为“汇编代码”,因为它与汇编语言密切相关。
          • @phoog 对我来说,汇编代码是汇编语言中的机器代码。程序集是一个库,可以包含 IL。所以我想我的想法和你一样。
          • @Lukazoid 但汇编语言与机器语言有细微的不同(并且更易于人类阅读)。详情请见en.wikipedia.org/wiki/Assembly_language
          猜你喜欢
          • 1970-01-01
          • 2018-12-12
          • 1970-01-01
          • 1970-01-01
          • 2011-04-26
          • 2018-03-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多