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.

I created a simple project using BenchMarkDotNet and made sure to include benchmarking of allocations as well as execution time. If you’re interested, I published the source as a gist.

For the primitive type case, the method Foo takes a string and returns its hash:

private int Foo(string s)
{
    return s.GetHashCode();
}

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 Foo:

private int Foo(WrapString s)
{
    return s.Value.GetHashCode();
}

public readonly struct WrapString
{
    public readonly string Value;

    public WrapString(string value)
    {
        Value = value;
    }
}

Then, because I was curious, I defined a generic struct for comparison:

private int Foo(Wrap<string> s)
{
    return s.Value.GetHashCode();
}

public readonly struct Wrap<T>
{
    public readonly T Value;

    public Wrap(T value)
    {
        Value = value;
    }
}

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.

Comments

blog comments powered by Disqus
Next Post
Why we need better validation  07 Jul 2018
Prior Post
Solving my Assembly Load Issues  23 Jun 2018
Related Posts
Basic validation  21 Jul 2018
Why we need better validation  07 Jul 2018
Equality has Symmetry  17 Mar 2018
Type Miscellany  05 Mar 2017
Testing Immutable Types  25 Feb 2017
Factory Methods  18 Feb 2017
Queue Concatenation  11 Feb 2017
Complex Queues  04 Feb 2017
The Problem with the Simple Queue  28 Jan 2017
Enumeration of Queues  21 Jan 2017
More csharp posts »
Archives
June 2018
2018