What’s faster in C#: a struct or a class?
In this article I’ll take a look at structs and classes in C#.
Understanding the performance difference between these two will help you pick the correct code for every occasion.
Check out my code:
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:
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?
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 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 list only maintains an array of object references that point to PointClass instances stored elsewhere on the heap.
Note the orange arrows in the diagram. These are array elements that reference objects 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. That’s a lot of work!
When you access a specific array element, the .NET runtime needs to retrieve the object reference and then ‘follow’ the reference to get at the PointClass instance.
And when the array goes out of scope, the .NET garbage collector has to dispose every single PointClass instance to reclaim the memory.
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.
Now compare this to the memory layout of a list 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 right there, inside the array.
When the array goes out of scope, the .NET garbage collector now only needs to dispose a single object.
All these savings add up, and this is why MeasureTestC is the fastest.
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.
What are your thoughts on structs and classes in C#?
Write a comment and let me know!