Understanding auto-insertion: when can inline compiler methods using private variables and abstract methods?

Using C #, but I believe this question is relevant to other (c) related languages. Consider this ...

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

Will the compiler be built into this if called in other classes? I think the answer is, of course, but here's the confusion: the radius is private. Therefore, from the point of view of manual programming, we could not embed this method, since the radius is private.

So what does the compiler do? I suppose he can embed it anyway, because if I remember correctly the 'private' 'public' ect. do modifiers only affect human written code, and can assembly language access any part of its own program if it wants to?

OK, but what about abstraction? Consider this ...

 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; } } 

In a class other than animals:

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

Can this be inserted? I'm pretty sure not, because the compiler doesn't know which animal is being used. But what if I did this?

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

Does it really matter? If not, of course, could it be ?:

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

Something will change if instead of the bool method above I had to do:

 float animalAge = a.GetAge(); 

In general, can too many abstract getters and setters cause a performance hit? If it comes to what is important, what would be the best solution?

+5
source share
1 answer

In general, there is no easy way to predict whether a method will be embedded. You should actually write a program and look at the machine code created for it. This is quite easy to do in a C program, you can ask the compiler to create a list of build code (for example, / FA for MSVC, -S for GCC).

More complicated in .NET due to jitter, exactly at the time the code was compiled. Technically, the optimizer source code is available from the CoreCLR project, but it’s very difficult to understand what it does, a lot of pretty impregnable C ++ code. You must use the "visual" in Visual Studio and use the debugger.

This takes a bit of preparation to make sure you get the actual optimized code, it usually disables the optimizer to make debugging easier. Switch to the Release configuration and use "Tools"> "Options"> "Debug"> "General"> check the box "Suppress JIT Optimization". If you need an optimal floating point code, then you always, always want a 64-bit code, so use Project> Properties> Build tab, untick "Prefer 32-bit".

And write a small test program to implement this method. It can be tricky; you can easily get the code at all. In this case it’s easy, Console.WriteLine () is a good way to make this method used, it cannot be optimized. So:

 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; } } 

Set a breakpoint on Main () and press F5. Then use Debug> Windows> Disassembly to look at the machine code. On my machine with a Haswell kernel (supported by AVX) I get:

 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 

I annotated the code to help sort this out, it may be cryptic if you have never looked at it before. But, without a doubt, you can say that the method was built-in. There is no CALL instruction; it was bound to two instructions (VMOVSS and VMULSS).

As expected. Accessibility does not play any role in decision making, it is a simple trick that does not change the logical operation of the program. This is important for the C # compiler at first, next to the verifier built into the jitter, but then disappears as a problem with the code generator and optimizer.

Just do the same for the abstract class. You will see that the method is not embedded, the indirect CALL instruction is required. Even if the method is completely empty. Some language compilers can turn virtual method calls into non-virtual calls when they know the type of object, but the C # compiler is not one of them. The jitter optimizer does not work either.

There are other reasons why the method will not be embedded, a moving target that is difficult to document. But roughly speaking, methods with too many MSIL, try / catch / throw, loop, CAS, some degenerate structural cases, the MarshalByRefObject base will not be included. Always look at the actual machine code.

The [MethodImpl (MethodImplOptions.AgressiveInlining)] attribute can cause the optimizer to revise the MSIL limit. MethodImplOptions.Noinlining helps turn off embedding, something you might want to do to get a better exception stack trace or slow down jitter because the assembly cannot be deployed.

Learn more about optimizations performed by the jitter optimizer in this post .

+1
source

Source: https://habr.com/ru/post/1265339/


All Articles