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会导致性能下降吗?如果达到一个重要的点,那将是最好的解决方案?
通常没有简单的方法可以预先预测方法是否会被内联.您必须实际编写程序并查看为其生成的机器代码.这在C程序中很容易实现,您可以要求编译器生成汇编代码列表(如MSVC的/ FA,GCC的-S).由于抖动及时编译代码,因此在.NET中更加复杂.从技术上讲,优化器的源代码可以从CoreCLR项目中获得,但很难弄清楚它的作用,很多非常坚不可摧的C代码.您必须利用Visual Studio中的“可视化”并使用调试器.
这需要做一些准备以确保获得实际优化的代码,它通常会禁用优化器以使调试变得容易.切换到发布配置并使用工具>选项>调试>一般>取消勾选“抑制JIT优化”复选框.如果您想要最佳浮点代码,那么您始终需要64位代码,因此请使用Project>属性>构建选项卡,取消选中“首选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.然后使用Debug> Windows>反汇编来查看机器代码.在我的具有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中执行的优化的更多信息.