A question came up in an online discussion about the relative performance of using a bare primitive type vs wrapping that type into a struct. Wrapping the type is a common technique used to avoid runtime errors, enlisting the compiler to enforce the correct semantics at compile time instead. But, sometimes people worry about the performance cost of doing this.
My experience is that type safety usually results in code that scales better. For example, it might enable better runtime optimization, or it might reduce code duplication, or it might permit better algorithms.
In this case, we’re explicitly going to look at the micro-optimization of wrapping a primitive type into a struct. Is there actually a performance problem to address?
I didn’t think there was.
But, facts always trump opinions (or guesses), so I decided to write a benchmark to find out.
For the primitive type case, the method
Foo takes a string and returns its hash:
I originally used
.Length the but the optimizer is just a little too clever and it managed to optimize away the method completely.
For comparison, I created a simple wrapper struct, and an overload of
Then, because I was curious, I defined a generic struct for comparison:
Here are the results of running the Benchmark:
| Method | Mean | Error | StdDev | Median | Allocated | |--------------------|----------|-----------|-----------|----------|-----------| | Primitive | 6.092 ns | 0.2577 ns | 0.5437 ns | 5.875 ns | 0 B | | UsingStruct | 5.916 ns | 0.1123 ns | 0.1050 ns | 5.904 ns | 0 B | | UsingGenericStruct | 5.846 ns | 0.0706 ns | 0.0660 ns | 5.829 ns | 0 B |
I did all of this on my laptop, and while I was “hands off” while the benchmark was running, this isn’t a machine that’s been configured to eliminate as much background activity as possible. The only tuning I did was to configure power management for best performance.
It’s initially interesting to see that the struct versions appear to run just a hair faster, but when you look at the error and standard deviation values, it becomes pretty clear that there’s no meaningful difference in performance.
Crucially, they all run with zero memory allocation, so there’s absolutely no impact on the heap.