/EnumHasFlags

Performance benchmarks for Enum.HasFlags

Primary LanguageC#

The Enum.HasFlag() method was introduced in .NET 4 to provide an easy way of checking if a bit flag exists for a given value. A useful addition to the Framework I'm sure you would agree, but may introduce some inherent performance issues in your code.

Check the existence of a bit flag has traditionally be completed with the following code:

if ((value & flag) == flag)

Now lets take a look at how the Enum.HasFlag() method implements the same check:

public Boolean HasFlag(Enum flag)
{
    if (!this.GetType().IsEquivalentTo(flag.GetType()))
    {
        throw new ArgumentException(
            Environment.GetResourceString("Argument_EnumTypeDoesNotMatch", flag.GetType(), this.GetType())); 
    }
 
    ulong uFlag = ToUInt64(flag.GetValue()); 
    ulong uThis = ToUInt64(GetValue());
    return ((uThis & uFlag) == uFlag); 
}

In the above code we can see multiple issues:

  1. There is a type safety check which coverts both the flag and instance to Enum types.
  2. Boxing occurs when calling Enum.ToUInt64.GetValue(), which happens for both the flag and instance values.

Code

We will test each method of performing a bit flag check to compare the performance differences. To test this I will be using the excellent BenchmarkDotNet library. If you haven't used BenchmarkDotNet before, you should definitely check it out, you'll never have to write performance testing boilerplate again!

To test against the Framework method, I created a simple extension method containing the simplfied bit flag check:

public static bool HasBitFlags(this TestEnum value, TestEnum flag)
{
    return (value & flag) == flag;
}

The code for this performance test can be found in my GitHub repository.

Results

The following results are produced by BenchmarkDotNet:

BenchmarkDotNet=v0.9.7.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6560U CPU 2.20GHz, ProcessorCount=4
Frequency=2156249 ticks, Resolution=463.7683 ns, Timer=TSC
HostCLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT]
JitModules=clrjit-v4.6.1080.0

Type=EnumHasFlags  Mode=Throughput  
Method Median StdDev Gen 0 Gen 1 Gen 2 Bytes Allocated/Op
DotNetHasFlagsTrue 23.2160 ns 0.4048 ns 3,092.59 - - 18.86
DotNetHasFlagsFalse 24.0702 ns 0.7277 ns 3,683.00 - - 22.54
CustomHasFlagsTrue 0.0056 ns 0.0394 ns - - - 0.00
CustomHasFlagsFalse 0.0007 ns 0.0576 ns - - - 0.00
### Conclusion The results clearly show that extra boxing and type-safety check in the Framework-provided `Enum.HasFlag()` cause a massive spike in time taken to execute the check as well as memory allocated. Although the times and memory sizes are still fairly small, when bit flags are being checked in a hot code path, they could certainly add up enough to cause a large performance issue.