Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# 7.2 In Keyword Performance

I am trying to test how much performant (Or not) the "in" keyword added to C# is. The in keyword should be able to pass a readonly reference to a value type into a method, instead of first copying the value then passing it in.

By bypassing this copy, in should be faster, but in my tests it doesn't seem any faster at all.

I am using BenchMarkDotNet to benchmark my code. The code looks like :

public struct Input
{
    public decimal Number1 { get; set; }
    public decimal Number2 { get; set; }
}

public class InBenchmarking
{
    const int loops = 50000000;
    Input inputInstance;

    public InBenchmarking()
    {
        inputInstance = new Input
        {
        };
    }

    [Benchmark]
    public decimal DoSomethingRefLoop()
    {
        decimal result = 0M;
        for (int i = 0; i < loops; i++)
        {
            result = DoSomethingRef(ref inputInstance);
        }
        return result;
    }

    [Benchmark]
    public decimal DoSomethingInLoop()
    {
        decimal result = 0M;
        for (int i = 0; i < loops; i++)
        {
            result = DoSomethingIn(inputInstance);
        }
        return result;
    }


    [Benchmark(Baseline = true)]
    public decimal DoSomethingLoop()
    {
        decimal result = 0M;
        for (int i = 0; i < loops; i++)
        {
            result = DoSomething(inputInstance);
        }
        return result;
    }

    public decimal DoSomething(Input input)
    {
        return input.Number1;
    }

    public decimal DoSomethingIn(in Input input)
    {
        return input.Number1;
    }

    public decimal DoSomethingRef(ref Input input)
    {
        return input.Number1;
    }
}

As you can see, I'm including a loop to use the "ref" keyword which also passes by reference, but is not readonly. This does seem to be faster.

The results of this test are :

             Method |     Mean |     Error |    StdDev | Scaled | ScaledSD |
------------------- |---------:|----------:|----------:|-------:|---------:|
 DoSomethingRefLoop | 20.15 ms | 0.3967 ms | 0.6058 ms |   0.41 |     0.03 |
  DoSomethingInLoop | 48.88 ms | 0.9756 ms | 2.5529 ms |   0.98 |     0.08 |
    DoSomethingLoop | 49.84 ms | 1.0872 ms | 3.1367 ms |   1.00 |     0.00 |

So using "in" doesn't seem to be faster at all. I feel like it's possible that something is being optimized in a way I don't anticipate and that's accounting for the performance difference. I have tried increasing the size of the struct up to 16 decimal fields, but again, it didn't make a difference between in and by value.

How can I structure my benchmark test to truly see the difference between in, ref, and passing by value?

like image 839
MindingData Avatar asked Jan 08 '18 00:01

MindingData


Video Answer


1 Answers

The issue is that you're using a non-readonly struct, so the compiler is creating a defensive copy of the input argument within the DoSomethingIn method.

This happens because you're using the getter method of the Number1 property, and the compiler isn't sure whether the struct state will change because of that (and since the argument is passed as a readonly reference, that wouldn't be valid).

If you edit your struct like this:

public readonly struct Input
{
    public decimal Number1 { get; }
    public decimal Number2 { get; }
}

and run the benchmark again, you'll get the same performance with the in method as with the ref method, as was your original assumption.

NOTE: the readonly struct modifier isn't mandatory, you can solve this problem by exposing the fields directly as well, like this:

public struct Input
{
    public decimal Number1;
    public decimal Number2;
}

The point is that, as stated here, that:

The compiler cannot know if any member method modifies the state of the struct. To ensure that the object is not modified, the compiler creates a copy and calls member references using that copy. Any modifications are to that defensive copy.

EDIT #2: to further clarify why the readonly struct modifier would be needed (as again, in is the same as ref readonly), here's another paragraph from the docs:

[...] Other times, you may want to create an immutable struct. Then you can always pass by readonly reference. That practice removes the defensive copies that take place when you access methods of a struct used as an in parameter.

like image 74
Sergio0694 Avatar answered Oct 11 '22 08:10

Sergio0694