In C#, Why Is A Stack of Structs Referencing Reference Types Slower to Push Than Structs Containing Value Types?

78 views Asked by At

If I have a Stack, I would have expected that a mystack.Push() would be about the same performance for T being a struct, and maybe a little slower for T being a reference type. When I benchmark this scenario, it seems that a struct containing a reference type performs slightly worse than a struct containing a value. This despite both structs being the same size in memory (64 bits).

The benchmark was performed using .NET 6 and benchmarkdotnet.

Here are my test structs; note they should all be the same size on my x64 processor:

public readonly record struct RecStruct(long A);

public readonly record struct PtrStruct(UIntPtr A);

public readonly record struct StructRef(object Ref);

And here is my benchmarkdotnet project class

public class StructPerformance
{
    private const int repeats = 1_000_000;

    [Benchmark]
    public Stack<RecStruct> PushStructWithCap()
    {
        var stack = new Stack<RecStruct>(repeats);
        var strct = new RecStruct(0);
        for (int i = 0; i < repeats; i++)
        {
            stack.Push(strct);
        }
        return stack;
    }

    [Benchmark]
    public Stack<PtrStruct> PushPtrStructWithCap()
    {
        var stack = new Stack<PtrStruct>(repeats);
        var ptr = new UIntPtr(0);
        var strct = new PtrStruct(ptr);
        for (int i = 0; i < repeats; i++)
        {
            stack.Push(strct);
        }
        return stack;
    }

    [Benchmark]
    public Stack<StructRef> PushStructRefWithCap()
    {
        var stack = new Stack<StructRef>(repeats);
        var obj = new object();
        var strct = new StructRef(obj);
        for (int i = 0; i < repeats; i++)
        {
            stack.Push(strct);
        }
        return stack;
    }

    [Benchmark]
    public Stack<object> PushObjectWithCap()
    {
        var stack = new Stack<object>(repeats);
        var obj = new object();
        for (int i = 0; i < repeats; i++)
        {
            stack.Push(obj);
        }
        return stack;
    }
}

My benchmark results are interesting, and seem to show that pushing a struct containing an object reference performs about as poorly as pushing an object, while an unmanaged structs perform better.

| Method                | Mean     | Error     | StdDev    | Median   |
|---------------------- |---------:|----------:|----------:|---------:|
| PushStructWithCap    | 2.605 ms | 0.0517 ms | 0.1315 ms | 2.583 ms |
| PushPtrStructWithCap | 2.586 ms | 0.0516 ms | 0.1121 ms | 2.558 ms |
| PushStructRefWithCap | 3.245 ms | 0.0643 ms | 0.1207 ms | 3.234 ms |
| PushObjectWithCap    | 3.231 ms | 0.0630 ms | 0.1018 ms | 3.217 ms |

What is the reason for the extra ~ns/op on Stack.Push() for structs containing references?

0

There are 0 answers