是什么让Enum.HasFlag这么慢?

时间:2011-09-10 00:00:07

标签: c# .net

我正在做一些速度测试,我注意到Enum.HasFlag比使用按位操作慢大约16倍。

有没有人知道Enum.HasFlag的内部以及为什么它如此之慢?我的意思是两倍慢不会太糟糕但是当它慢16倍时它会使该功能无法使用。

如果有人想知道,这是我用来测试其速度的代码。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace app
{
    public class Program
    {
        [Flags]
        public enum Test
        {
            Flag1 = 1,
            Flag2 = 2,
            Flag3 = 4,
            Flag4 = 8
        }
        static int num = 0;
        static Random rand;
        static void Main(string[] args)
        {
            int seed = (int)DateTime.UtcNow.Ticks;

            var st1 = new SpeedTest(delegate
            {
                Test t = Test.Flag1;
                t |= (Test)rand.Next(1, 9);
                if (t.HasFlag(Test.Flag4))
                    num++;
            });

            var st2 = new SpeedTest(delegate
            {
                Test t = Test.Flag1;
                t |= (Test)rand.Next(1, 9);
                if (HasFlag(t , Test.Flag4))
                    num++;
            });

            rand = new Random(seed);
            st1.Test();
            rand = new Random(seed);
            st2.Test();

            Console.WriteLine("Random to prevent optimizing out things {0}", num);
            Console.WriteLine("HasFlag: {0}ms {1}ms {2}ms", st1.Min, st1.Average, st1.Max);
            Console.WriteLine("Bitwise: {0}ms {1}ms {2}ms", st2.Min, st2.Average, st2.Max);
            Console.ReadLine();
        }
        static bool HasFlag(Test flags, Test flag)
        {
            return (flags & flag) != 0;
        }
    }
    [DebuggerDisplay("Average = {Average}")]
    class SpeedTest
    {
        public int Iterations { get; set; }

        public int Times { get; set; }

        public List<Stopwatch> Watches { get; set; }

        public Action Function { get; set; }

        public long Min { get { return Watches.Min(s => s.ElapsedMilliseconds); } }

        public long Max { get { return Watches.Max(s => s.ElapsedMilliseconds); } }

        public double Average { get { return Watches.Average(s => s.ElapsedMilliseconds); } }

        public SpeedTest(Action func)
        {
            Times = 10;
            Iterations = 100000;
            Function = func;
            Watches = new List<Stopwatch>();
        }

        public void Test()
        {
            Watches.Clear();
            for (int i = 0; i < Times; i++)
            {
                var sw = Stopwatch.StartNew();
                for (int o = 0; o < Iterations; o++)
                {
                    Function();
                }
                sw.Stop();
                Watches.Add(sw);
            }
        }
    }

}

结果: HasFlag:52ms 53.6ms 55ms 按位:3ms 3ms 3ms

3 个答案:

答案 0 :(得分:69)

  

有没有人知道Enum.HasFlag的内部以及为什么它如此之慢?

实际检查只是一个简单的检查Enum.HasFlag - 这不是问题所在。话虽如此,它比你自己的位检查要慢......

这种放缓有几个原因:

首先,Enum.HasFlag进行显式检查以确保枚举的类型和标志的类型都是相同的类型,并且来自相同的枚举。这张支票有一些费用。

其次,在UInt64内部转换为HasFlag期间,有一个不幸的框和unbox值。我相信这是因为要求Enum.HasFlag适用于所有枚举,无论底层存储类型如何。

话虽如此,Enum.HasFlag有一个巨大的优势 - 它可靠,干净,并且使代码非常明显和富有表现力。在大多数情况下,我觉得这样做是值得的 - 但如果你在一个性能非常关键的循环中使用它,那么值得你自己检查一下。

答案 1 :(得分:26)

Enum.HasFlags()的反编译代码如下所示:

public bool HasFlag(Enum flag)
{
    if (!base.GetType().IsEquivalentTo(flag.GetType()))
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_EnumTypeDoesNotMatch", new object[] { flag.GetType(), base.GetType() }));
    }
    ulong num = ToUInt64(flag.GetValue());
    return ((ToUInt64(this.GetValue()) & num) == num);
}

如果我猜测,我会说检查类型是最让它减慢的原因。

答案 2 :(得分:3)

JITter应该将此作为一个简单的按位操作。 JITter知道甚至可以自定义处理某些框架方法(通过MethodImplOptions.InternalCall我认为?)但是HasFlag似乎已经逃脱了微软的严重关注。