73 votes

Qu'est-ce qui rend Enum.HasFlag si lent?

Je faisais quelques tests de vitesse et j'ai remarqué qu'Enum.HasFlag est environ 16 fois plus lent que l'utilisation de l'opération bit à bit.

Est-ce que quelqu'un connaît les composants internes de Enum.HasFlag et pourquoi il est si lent? Je veux dire deux fois plus lent ne serait pas trop grave mais cela rend la fonction inutilisable quand elle est 16 fois plus lente.

Au cas où quelqu'un se demanderait, voici le code que j'utilise pour tester sa vitesse.

 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);
            }
        }
    }

}
 

Résultats: HasFlag: 52ms 53.6ms 55ms Bitwise: 3ms 3ms 3ms

81voto

Reed Copsey Points 315315

Personne ne sait le fonctionnement interne de l'Enum.HasFlag et pourquoi c'est si lent?

Le chèque est juste un simple contrôle de bit en Enum.HasFlag - ce n'est pas le problème ici. Cela étant dit, il est plus lent que votre propre bit de vérifier...

Il ya un couple de raisons de ce ralentissement:

Tout d'abord, Enum.HasFlag n'explicite assurez-vous que le type de l'enum et le type de l'indicateur du même type et de la même Enum. Il y a un certain coût dans cette case.

Deuxièmement, il y a une malheureuse boîte et unbox de la valeur au cours d'une conversion en UInt64 qui se produit à l'intérieur de l' HasFlag. C'est, je crois, en raison de l'exigence d' Enum.HasFlag travailler avec tous les énumérations, quel que soit le sous-jacent type de stockage.

Cela étant dit, il est un grand avantage pour Enum.HasFlag - il est fiable, propre, et rend le code très clair et expressif. Pour la plupart, je sens que de ce fait, il vaut le coût, mais si vous l'utilisez très critique pour les performances de la boucle, il peut être la peine de le faire à votre propre contrôle.

28voto

svick Points 81772

Le code décompilé de Enum.HasFlags() ressemble à ceci:

 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);
}
 

Si je devais deviner, je dirais que vérifier le type est ce qui le ralentit le plus.

3voto

Le Jitter devrait inclure ceci comme une opération simple au niveau des bits. JITter est suffisamment conscient pour gérer même certaines méthodes du framework (via MethodImplOptions.InternalCall je pense?), Mais HasFlag semble avoir échappé à l'attention sérieuse de Microsoft.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X