What's Faster In C#? A Struct Or A Class?

What's Faster In C#? A Struct Or A Class?

In this article I’ll take a look at the difference in performance between using a struct or a class in a C# application. Understanding this difference will help you make the correct choice between structs and classes in every situation.

Check out this C# code:

public class PointClass
{
    public int X { get; set; }
    public int Y { get; set; }
    public PointClass(int x, int y)
    {
        X = x;
        Y = y;
    }
}

public class PointClassFinalized : PointClass
{
    public PointClassFinalized(int x, int y) : base(x, y)
    {
    }
    ~PointClassFinalized()
    {
        // how will the finalizer affect performance?
    }
}

public struct PointStruct
{
    public int X { get; set; }
    public int Y { get; set; }
    public PointStruct(int x, int y)
    {
        X = x;
        Y = y;
    }
}

public class Benchmark
{
    public const int Iterations = 1000000;

    // performance benchmark with finalized classes
    public bool MeasureTestA()
    {
        var list = new PointClassFinalized[Iterations];
        for (int i = 0; i < Iterations; i++)
        {
            list[i] = new PointClassFinalized(i, i);
        }
        return true;
    }

    // performance benchmark with normal classes
    public bool MeasureTestB()
    {
        var list = new PointClass[Iterations];
        for (int i = 0; i < Iterations; i++)
        {
            list[i] = new PointClass(i, i);
        }
        return true;
    }

    // performance benchmark with structs
    public bool MeasureTestC()
    {
        var list = new PointStruct[Iterations];
        for (int i = 0; i < Iterations; i++)
        {
            list[i] = new PointStruct(i, i);
        }
        return true;
    }
}

I’ve got a PointClass and a PointStruct, both containers for storing X and Y integer values. And there’s also a PointClassFinalized with a finalizer.

The MeasureTestA method allocates an array of 1,000,000 PointClassFinalized instances.

The MeasureTestB method is almost identical, but it allocates 1,000,000 instances of PointClass instead.

And there’s MeasureTestC which allocates 1,000,000 instances of PointStruct.

Which method do you think is fastest?

Let’s find out. Here's the output of my test runner:

Did you expect that?

Let’s focus on MeasureTestB and MeasureTestC for now. The only difference between these two methods is that the one allocates classes, and the other allocates structs.

MeasureTestC allocates structs and runs in only 17 milliseconds which is 8.6 times faster than MeasureTestB which allocates classes!

That’s quite a difference!

So what’s going on here?

An Arrays of Classes

The difference is caused by how structs and classes are stored in memory. Here’s what the memory layout looks like for a list of PointClass instances:

The list array is a local variable, so it’s stored on the stack. It references an array of PointClass instances on the heap.

But here’s the twist: PointClass is a reference type, so it’s stored elsewhere on the heap. The array only maintains a list of object references that point to PointClass instances stored elsewhere on the heap.

The orange arrows in the diagram show how each array element references an object located elsewhere on the heap.

To fill the array with objects, MeasureTestB has to allocate 1,000,000 objects on the heap and store their references in the array. And when you access a specific array element, the .NET runtime needs to retrieve the object reference and then ‘follow’ the reference to get to the PointClass instance.

This also wastes a lot of memory. The array with one million object references takes up 8 MB of memory, but all the individual PointClass instances add another 24 MB of memory to the heap. So in total the code for MeasureTestB consumes 32 MB of heap memory.

The Finalizer

The PointClassFinalized class in MeasureTestA has a finalizer which slows down this process even further.

The .NET Framework runs all finalizers on a single thread, so now that thread has to process 1,000,000 objects in turn before the garbage collector can reclaim the memory.

And you can clearly see this in the benchmark results. MeasureTestA is 1.7 times slower than MeasureTestB.

An Array of Structs

Now compare this to the memory layout of an array of PoinstStruct instances:

Structs are value types, which means they are stored inline inside their containing data type. So now all PointStruct instances are stored inside the array itself. There is only a single object on the heap.

To initialize the array, the .NET runtime can now write the X and Y values directly into the correct array elements. There’s no need to allocate new objects on the heap and store their references. And when you access a specific array element, the .NET runtime can retrieve the struct directly because it’s stored inside the array.

The code is also much more efficient with memory. The array with one million structs still takes up 8 MB of memory, but now there's nothing else on the heap, this is all there is. So in total the code for MeasureTestC consumes only 8 MB of heap memory. This is four times better than the array of classes!

All these savings add up, and this is why MeasureTestC is the fastest.

My Advice

So does that mean you should always use a struct?

Hell, no! Here’s what you need to do:

  • When you’re storing more than 30–40 bytes of data, use a class.
  • When you are storing reference types, use a class.
  • When you are storing up to a few thousand instances, use a class.
  • When you list is long-lived, use a class.
  • In all other cases, use structs.

C# Performance Training

This benchmark is part of my online training course Introduction To C# Performance that teaches software developers how to speed up their C# code.

Introduction To C# Performance

This course will teach you how to speed up your C# code with performance tricks that every professional developer should know.

View The Training Course

The course teaches the fundamentals of the .NET Framework, how to read Intermediate Language generated by the C# compiler, what the Garbage Collector does, how the stack and heap behave, what boxing is, and much more.

If you're interested in C# code optimization, then feel free to check out the course.