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

Performance 理解自动内联:何时可以使用涉及私有变量的编译器内联方法&;抽象方法?,performance,compilation,inline,private,abstract,Performance,Compilation,Inline,Private,Abstract,使用C#,但我认为这个问题也适用于其他(大多数与C相关的)语言。考虑这个… 如果在其他类中调用,编译器会内联这个吗?我想答案当然是肯定的,但这里有一个困惑:radius是私有的。因此,从手动编程的角度来看,我们不可能内联这个方法,因为radius是私有的 那么编译器做什么呢?我认为它无论如何都可以内联它,因为如果我记得正确的话,“private”和“public”等。修饰符只影响人类编写的代码,汇编语言可以访问自己程序的任何部分(如果需要) 好吧,但是抽象呢?考虑这个… 这个可以内联吗?我几乎可

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

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

那么编译器做什么呢?我认为它无论如何都可以内联它,因为如果我记得正确的话,“private”和“public”等。修饰符只影响人类编写的代码,汇编语言可以访问自己程序的任何部分(如果需要)

好吧,但是抽象呢?考虑这个…

这个可以内联吗?我几乎可以肯定不会,因为编译器不知道使用的是哪种动物。但是如果我真的

...
Animal a = new Hawk();
if (a.CanFly()) {
...
这有区别吗?如果不是的话,这个当然可以:

...
Hawk a = new Hawk();
if (a.CanFly()) {
...
如果我不使用上面的bool方法,而是:

float animalAge = a.GetAge();

一般来说,太多的抽象getter和setter会导致性能下降吗?如果这一点非常重要,那么最好的解决方案是什么?

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

由于编译代码时的抖动,在.NET中更加复杂。从技术上讲,优化器的源代码可以从CORCLR项目中获得,但是很难弄清楚它是怎么做的,很多非常难攻克的C++代码。您必须利用VisualStudio中的“visual”并使用调试器

这需要做一些准备,以确保获得实际的优化代码,它通常会禁用优化器以简化调试。切换到发布配置,并使用工具>选项>调试>常规>取消选中“抑制JIT优化”复选框。如果您想要最佳浮点代码,那么您总是想要64位代码,所以请使用“项目>属性>生成”选项卡,取消选中“首选32位”

并编写一个小的测试程序来练习这个方法。这可能很棘手,您可能很容易就没有代码了。在这种情况下很容易,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。然后使用调试>Windows>反汇编查看机器代码。在配备Haswell core(支持AVX)的机器上,我得到:

我对代码进行了注释以帮助理解它,如果您以前从未看过它,它可能会很神秘。但毫无疑问,您可以看出该方法是内联的。没有调用指令,它内联到两条指令(VMOVSS和VMULSS)

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

对抽象类执行完全相同的操作。您将看到该方法没有内联,需要一条间接调用指令。即使方法是完全空的。有些语言编译器知道对象的类型时,可以将虚拟方法调用转换为非虚拟调用,但C#编译器不是其中之一。抖动优化器也没有。编辑:已在设备化调用上完成

还有其他原因导致方法无法内联,移动目标很难记录。但大致上,包含太多MSIL、try/catch/throw、循环、CAS需求、一些退化结构情况、MarshalByRefObject基的方法不会内联。始终查看实际的机器代码以确定

[MethodImpl(MethodImplOptions.AgressiveInline)]属性可以强制优化器重新考虑MSIL限制。MethodImplOptions.NoInLine有助于禁用内联,这类操作可能是为了获得更好的异常堆栈跟踪或减缓抖动,因为可能没有部署程序集

更多关于jitter optimizer在中执行的优化的信息。

我认为在C#world(以及许多其他基于VM的语言中)这变得更加复杂,因为有一个从C#到IL的编译器进行一些优化,包括内联,还有一个VM的JIT编译器也进行一些优化,包括内联,但JIT编译器也可能依赖一些统计指标,例如注意
GetNextAnimal
总是以某种方式生成
Hawk
因此,在“Hawk”代码中加入了一个小的预检查/陷阱,检查这个条件是否成立(如果没有生成更多/不同的代码),看看YouTube上的视频,这是针对Java的,但C#是类似的。它展示了很多惊人的技巧,但一些与你的问题最相关的技巧在1:00-1:15左右展示,他描述了“推测性优化”。另请看同一演示文稿的第89张幻灯片,其中显示了一些相关的JVM配置,如
MaxInlineLevel
,另请参见Wiki上的文章
...
Hawk a = new Hawk();
if (a.CanFly()) {
...
float animalAge = a.GetAge();
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;
    }
}
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