5

If you have an enum that is used for bit flags, i.e.,

[Flags]
internal enum _flagsEnum : byte
{
    None = 0,           //00000000
    Option1 = 1,        //00000001
    Option2 = 1 << 1,   //00000010
    Option3 = 1 << 2,   //00000100
    Option4 = 1 << 3,   //00001000
    Option5 = 1 << 4,   //00010000
    Option6 = 1 << 5,   //00100000
    Option7 = 1 << 6,   //01000000
    Option8 = 1 << 7,   //10000000
    All = Byte.MaxValue,//11111111
}

_flagsEnum myFlagsEnum = _flagsEnum.None;

Is it faster to do..

bool hasFlag = myFlagsEnum.HasFlag(_flagsEnum.Option1);

or to do..

bool hasFlag = myFlagsEnum & _flagsEnum.Option1 != 0

If there's a performance difference between checking multiple flags, then take that into account as well.

Normally I'd check out the reference source, but in this case Enum.HasFlags just goes to an extern InternalHasFlags, so I have no idea what it's doing.

Yushatak
  • 741
  • 1
  • 5
  • 15
  • 2
    There is some performance cost. See: http://stackoverflow.com/questions/7368652/what-is-it-that-makes-enum-hasflag-so-slow – stephen.vakil Sep 14 '16 at 15:11
  • 1
    Seems like premature optimization. The one thing I don't like about `HasFlag` is it accepts any `enum` object as an argument, rather than one of the same type. – Mr Anderson Sep 14 '16 at 15:11
  • 1
    `HasFlag` does more than a simple bitwise check http://blogs.microsoft.co.il/bnaya/2011/01/28/enumhasflag-good-or-bad/ – Alex K. Sep 14 '16 at 15:11
  • @MrAnderson It's code that runs thousands of times (potentially per-second, depending on what the user is doing) in a large application, so I want it to be a performant as possible while remaining readable/maintainable. – Yushatak Sep 14 '16 at 15:13
  • @PanagiotisKanavos It's easier to not make a mistake, in my opinion, especially when doing large enums like ulong. – Yushatak Sep 14 '16 at 15:13
  • 1
    @Yushatak Then I would recommend making your own extension method to overload `HasFlag()` for your enum. – Mr Anderson Sep 14 '16 at 15:14
  • Yes I was just deciding that after reading the above-linked pages, particularly the SO link posted by @stephen.vakil - Stephen, if you post as an answer I will accept that. – Yushatak Sep 14 '16 at 15:16
  • 1
    The & operator takes a single processor instruction (TEST) that runs between 0.25 and 1 processor cycle on a modern CPU. It is never slower than a call into the CLR. If you care about speed then never use byte as the enum base type, the default (int) is fastest. – Hans Passant Sep 14 '16 at 15:20
  • If you are really concerned about optimization, don't make your enum's underlying type `byte`. CPU's are designed to work efficiently with 32-bit values. In some cases doing byte operations requires masking. – Mr Anderson Sep 14 '16 at 15:35
  • Yeah that's a good point that both you and Hans brought up - I will indeed change that. It's better to waste space than performance in this case. – Yushatak Sep 14 '16 at 15:36
  • 2
    Update: now the performance difference is "flattened" https://www.code4it.dev/blog/hasflag-performance-benchmarkdotnet – Davide Bellone Jul 01 '20 at 13:30

2 Answers2

12

There is a performance cost to using HasFlag, because the implementation verifies that the enum value that you pass is of the same type as the flag.

With this difference out of the way, the implementation is highly optimized to avoid promoting shorter types, such as byte, to int:

switch (pMTThis->GetNumInstanceFieldBytes()) {
case 1:
    cmp = ((*(UINT8*)pThis & *(UINT8*)pFlags) == *(UINT8*)pFlags);
    break;
case 2:
    cmp = ((*(UINT16*)pThis & *(UINT16*)pFlags) == *(UINT16*)pFlags);
    break;
case 4:
    cmp = ((*(UINT32*)pThis & *(UINT32*)pFlags) == *(UINT32*)pFlags);
    break;
case 8:
    cmp = ((*(UINT64*)pThis & *(UINT64*)pFlags) == *(UINT64*)pFlags);
    break;
default:
    // should not reach here.
    UNREACHABLE_MSG("Incorrect Enum Type size!");
    break;
}

The source of ReflectionEnum::InternalHasFlag can be found here.

Although the cost is relatively high, it is unlikely to matter, except for the most extreme situations. I would recommend keeping it, unless your profiler points to this call as a largest bottleneck in your program.

Jim Balter
  • 16,163
  • 3
  • 43
  • 66
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 3
    Also, there is boxing and unboxing going on as the `HasFlag` method takes an `Enum` as a parameter which is a class. – Bruno Zell Aug 13 '19 at 00:09
2

Unsafe.

How about this? In my benchmarks ~25% much faster than HasFlag, ~10%-15% slower than bitwise, but generic.

Perhaps someone will be able to optimize that.

[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static unsafe Boolean HasFlags<T>(T* first, T* second) where T : unmanaged, Enum
{
    Byte* pf = (Byte*) first;
    Byte* ps = (Byte*) second;

    for (Int32 i = 0; i < sizeof(T); i++)
    {
        if ((pf[i] & ps[i]) != ps[i])
        {
                return false;
        }
    }

    return true;
}
    
/// <remarks>Faster analog of Enum.HasFlag</remarks>
/// <inheritdoc cref="Enum.HasFlag"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe Boolean HasFlags<T>(this T first, T second) where T : unmanaged, Enum
{
    return HasFlags(&first, &second);
}