Search code examples
c#performancec#-4.0clr

C# Struct Layouts and unexpected bench marking result?


I'm not really micro-managing the performance of an application, but I'm curios on the below scenario.

For Structs, by default, C# compiler generates the layout, LayoutType. Sequential. This means the fields should stay in the order defined by the programmer. I believe that this is to support interoperability with unmanaged code. However most user defined Structs have nothing to do with interoperability. I have read that for better performance, we can explicitly specify the LayoutKind.Auto, and let the CLR to decide the best possible layout. In order to test this, I thought of doing a quick benchmark on both layouts. However my result says the default layout (LayoutType.Sequnetial) is bit quicker than the explicit layout (LayoutType.Auto). I was expecting the reverse.

Below is the test I ran on my machine (x86 running .NET 4)

//uses LayoutKind.Sequence by default
public struct StructSeq
{
    private readonly Byte mb;
    private readonly Int16 mx;
    public string a;
    public string b;
    public string c;
    public string d;
}

[StructLayout(LayoutKind.Auto)]
public struct StructAuto
{
    private readonly Byte mb;
    private readonly Int16 mx;
    public string a;
    public string b;
    public string c;
    public string d;
}

public sealed class Program
{
    public static void Main()
    {
        StructSeq sq = new StructSeq();
        Stopwatch sw1 = new Stopwatch();
        sw1.Start();
        for (int i = 0; i < 10000; i++)
        {
            sq = ProcessStructSeq(sq);
        }
        sw1.Stop();
        Console.WriteLine("Struct LayoutKind.Sequence (default) {0}", sw1.Elapsed.TotalMilliseconds);

        StructAuto so = new StructAuto();
        Stopwatch sw2 = new Stopwatch();
        sw2.Start();
        for (int i = 0; i < 10000; i++)
        {
            so = ProcessStructAuto(so);
        }
        sw2.Stop();
        Console.WriteLine("Struct LayoutKind.Auto (explicit) {0}", sw2.Elapsed.TotalMilliseconds);
        Console.ReadLine();
    }

    public static StructSeq ProcessStructSeq(StructSeq structSeq)
    {
        structSeq.a = "1";
        structSeq.b = "2";
        structSeq.c = "3";
        structSeq.d = "4";
        return structSeq;
    }        

    public static StructAuto ProcessStructAuto(StructAuto structAuto)
    {
        structAuto.a = "1";
        structAuto.b = "2";
        structAuto.c = "3";
        structAuto.d = "4";
        return structAuto;
    }
}

Below is a sample result I get on my machine (x86 running .NET 4)

Struct LayoutKind.Sequence (default) 0.7488

Struct LayoutKind.Auto (explicit) 0.7643

I ran this test multiple times and I always get Struct LayoutKind.Sequence (default) < Struct LayoutKind.Auto (explicit)

Even though it is a micro milliseconds difference, I ‘m expecting the Struct LayoutKind.Auto (explicit) to be lower than the Struct LayoutKind.Sequence (default).

Does anyone know the reason for this? Or is it my benchmarking is not accurate enough give me the right result?


Solution

  • Honestly, it's so close that it wouldn't make any sort of visible difference unless you were processing a few million of these structs. In fact, running it multiple times may yield different results. I would up the number of iterations and try to run the program without the debugger attached to see if anything changes.

    Just using structs doesn't immediately make your code faster though, there are many pitfalls that make structs far slower than their class equivalents.

    If you want to optimize this benchmark, you should pass the structs to the process methods as references and not return another struct (avoiding the creation of 2 additional structs for the method), which should provide a much larger speedup than the different layout kinds:

    public static void ProcessStructSeq(ref StructSeq structSeq)
    {
        structSeq.a = "1";
        structSeq.b = "2";
        structSeq.c = "3";
        structSeq.d = "4";
    }        
    
    public static void ProcessStructAuto(ref StructAuto structAuto)
    {
        structAuto.a = "1";
        structAuto.b = "2";
        structAuto.c = "3";
        structAuto.d = "4";
    }
    

    Also, there's a point where structs become slower than their class counterparts, and that's estimated to be at about 16 bytes according to this MSDN article and further explained in this StackOverflow question.