理解自动内联:编译器内联方法何时可以涉及私有变量&抽象方法?

时间:2017-03-12 02:36:24

标签: performance compilation inline private abstract

使用C#,但我认为这个问题也与其他(大多数相关的)语言相关。 考虑一下......

private float radius = 0.0f; // Set somewhere else
public float GetDiameter() {
   return radius * 2.0f;
}

如果在其他类中调用,编译器会内联吗?我认为答案当然是,但这里有困惑:半径是私人的。因此,从手动编程的角度来看,我们不可能内联这个方法,因为半径是私有的。

那么编译器做什么?我认为它无论如何都可以内联,因为如果我没记错的话,那就是私人' '公'等。修饰符只影响人类编写的代码,如果需要,汇编语言可以访问自己程序的任何部分吗?

好的,但抽象怎么样? 考虑一下......

public abstract class Animal {
   abstract public bool CanFly();
}

public class Hawk : Animal {
...
   override public bool CanFly() {
      if (age < 1.0f) return false; // Baby hawks can't fly yet
      return true;
   }
}

public class Dog : Animal {
...
   override public bool CanFly() {
      return false;
   }
}

在非动物类中:

...
Animal a = GetNextAnimal();
if (a.CanFly()) {
...

这可以内联吗?我几乎肯定不会,因为编译器不知道正在使用什么样的动物。但如果相反我做了......

...
Animal a = new Hawk();
if (a.CanFly()) {
...

这会有所作为吗?如果没有,肯定这个可以是?:

...
Hawk a = new Hawk();
if (a.CanFly()) {
...

如果不是上面的bool方法,我会做什么改变:

float animalAge = a.GetAge();

一般情况下,太多抽象的getter和setter会导致性能下降吗?如果达到一个重要的点,那将是最好的解决方案?

1 个答案:

答案 0 :(得分:1)

通常没有简单的方法可以预先预测方法是否会被内联。您必须实际编写程序并查看为其生成的机器代码。这在C程序中很容易实现,您可以要求编译器生成汇编代码列表(如MSVC的/ FA,GCC的-S)。

由于抖动及时编译代码,因此在.NET中更加复杂。从技术上讲,优化器的源代码可以从CoreCLR项目中获得,但很难弄清楚它的作用,很多非常坚不可摧的C ++代码。你必须利用&#34; visual&#34;在Visual Studio中使用调试器。

这需要做一些准备以确保获得实际优化的代码,它通常会禁用优化器以使调试变得容易。切换到发布配置并使用工具&gt;选项&gt;调试&gt;一般&gt;解开&#34;抑制JIT优化&#34;复选框。如果您想要最佳浮点代码,那么您始终需要64位代码,因此请使用Project&gt;属性&gt;构建选项卡,取消勾选&#34;首选32位&#34;。

并编写一个小测试程序来练习该方法。这可能很棘手,你可能很容易就完全没有代码了。在这种情况下很容易,Console.WriteLine()是一种强制使用此方法的好方法,它无法被优化掉。所以:

class Program {
    static void Main(string[] args) {
        var obj = new Example();
        Console.WriteLine(obj.GetDiameter());
    }
}

class Example {
    private float radius = 0.0f;
    public float GetDiameter() {
        return radius * 2.0f;
    }
}

在Main()上设置断点并按F5。然后使用Debug&gt; Windows&gt;反汇编来查看机器代码。在我的具有Haswell核心(支持AVX)的机器上,我得到:

00007FFEB9D50480  sub         rsp,28h                   ; setup stack frame
00007FFEB9D50484  mov         rcx,7FFEB9C45A78h         ; rcx = typeof(Example)
00007FFEB9D5048E  call        00007FFF19362530          ; rax = new Example()
00007FFEB9D50493  vmovss      xmm0,dword ptr [rax+8]    ; xmm0 = Example.field
00007FFEB9D50499  vmulss      xmm0,xmm0,dword ptr [7FFEB9D504B0h]  ; xmm0 *= 2.0
00007FFEB9D504A2  call        00007FFF01647BB0          ; Console.WriteLine()
00007FFEB9D504A7  nop                                   ; alignment
00007FFEB9D504A8  add         rsp,28h                   ; tear down stack frame
00007FFEB9D504AC  ret 

我注释了代码以帮助理解它,如果你以前从未看过它,可能会很神秘。但毫无疑问,你可以说这个方法被内联了。没有CALL指令,它内联到两条指令(VMOVSS和VMULSS)。

如你所料。可访问性在内联决策中没有任何作用,它是一个简单的代码提升技巧,不会改变程序的逻辑操作。它首先对C#编译器很重要,在抖动内置的验证器旁边,然后作为代码生成器和优化器的关注点消失。

为抽象类做同样的事情。您将看到该方法内联,需要间接CALL指令。即使该方法完全是空的。有些语言编译器在知道对象的类型但是C#编译器不是其中之一时,可以将虚方法调用转换为非虚拟调用。抖动优化器也没有。

还有其他原因导致方法无法内联,移动目标难以记录。但粗略地说,有太多MSIL,try / catch / throw,循环,CAS需求,一些退化结构案例,MarshalByRefObject基础的方法都不会被内联。请务必查看实际的机器代码。

[MethodImpl(MethodImplOptions.AgressiveInlining)]属性可以强制优化器重新考虑MSIL限制。 MethodImplOptions.Noinlining有助于禁用内联,这是您可能想要做的事情,以获得更好的异常堆栈跟踪或减慢抖动,因为可能没有部署程序集。

有关抖动优化器在this post中执行优化的更多信息。