inline and throw


Picture from mobillegends.net about inline functions

Picture from mobillegends.net about inline functions

Studying the performance of methods in various collections, I came across an interesting fact: where you need to throw an Exception, programmers pull the method in a static class where throw occurs. At first I thought it was just convenient to have all the errors in one place and keep track of their uniformity there. Yes, it’s really convenient.

The second problem is inline. The fact is that JIT by default tries not to inline methods with throw in order to avoid complex binding around throwing an exception. Analyzing the method, JIT collects some metrics, from which a number is obtained. If the number exceeds a certain threshold, then the method will not be inlined. In the case of throw, this number immediately exceeds the threshold.

For more in-depth information about what inline methods are, you can, for example, look at girls on the bed. You can find out what JIT is guided by inline methods, for example, here. We will immediately move on to the benchmark.

Checking inline

The situation is simple: we have a class in which there are several absolutely identical methods. Well, almost.

public sealed class InliningService {    
    private readonly int _min;
    ...
    [MethodImpl(MethodImplOptions.AggressiveInlining)]    
    public int AggressiveInline(int a, int b) {        
    if (a < _min || b < _min)
        throw new InvalidOperationException();        

    return a + b;    
}

public int AutoInline(int a, int b) {
    if (_min < 0 || b < _min) Errors.InvalidOperation();           
    return a + b;    
}

public int WithoutInline(int a, int b) {
    if (a < _min || b < _min) 
        throw new InvalidOperationException();
    return a + b;    
}

In one case, we throw directly in the method (Without Inline), in the other case, we ask to still inline such a method (Aggressive Inline), and in the third case, we rely on JIT and calling a static method in which an error is thrown (Auto Inline ).

Benchmark: inline method in C#

Benchmark: inline method in C#

Looking at this benchmark, several observations can be made.

Firstly, the speed of work in all three popular versions of .NET is approximately the same. I propose to write off the strange increase in the method’s running time without inline’a on .NET Core 3.1 as an error. Moreover, the size of the IL-code (Code Size) is the same in all three frameworks.

Secondly, the speed of a method that has not been inlined is predictably slower than the version where the JIT decided to make it inline. And almost twice. This allows us to say that we need to hide throw in a static class where throwing Exception would be expensive and we hope for inline.

Thirdly, the Code Size column quite clearly hints to us that the aggressive inline method with throw in this case allows JIT to do inline, but by increasing the code size. Compared to AutoInline, the difference is dramatic. Such an inline is bad, because it would affect the operation and possibility of inline’a of other methods, would make it impossible to inline those methods where it is really important.

conclusions

  1. Create a static class a la Errors to throw Exceptions. This standardizes error throwing and makes the code cleaner.

  2. Methods of the Errors class can return an object representation of the generated Exception, but it’s better if the throw occurs directly in the method of this class. By introducing this practice when writing code, you can expand the possibilities of JIT’a inline.

  3. Throwing an Exception at a critical place in the code where we hope for inline is a bad idea that prevents JIT from inlining the method.

  4. No need to mess with MethodImplAttributeif you don’t understand how it works and what it can affect. Use aggressive inline only when you have confirmation (benchmark) that it will positively affect the application.

PS: Started writing in cart about performance. Check it out if you’re interested.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *