web-dev-qa-db-fra.com

BitArray est-il plus rapide en C # pour obtenir une valeur en bits qu'une simple conjonction avec décalage au niveau du bit?

1). var bitValue = (byteValue & (1 << bitNumber)) != 0;

2). en utilisant System.Collections.BitArray avec une méthode Get(int index)

  • Qu'est-ce qui est plus rapide?
  • Dans quelles situations pour les projets .NET BitArray pourrait être plus utile qu'une simple conjonction avec le décalage au niveau du bit?
20
Secret

BitArray va être capable de gérer un nombre arbitraire de valeurs booléennes, alors qu'un byte ne contiendra que 8, int seulement 32, etc. Cela va être le la plus grande différence entre les deux.

De plus, BitArray implémente IEnumerable, ce qui n'est évidemment pas le cas d'un type intégral. Tout dépend donc des exigences de votre projet; si vous avez besoin d'une IEnumerable ou d'une interface de type tableau, alors optez pour la BitArray.

J'utiliserais en fait un bool[] Sur l'une ou l'autre solution, simplement parce qu'il est plus explicite dans quel type de données vous suivez . T

BitArray ou bitfield utilisera environ 1/8 de l'espace d'un bool[] car ils "regroupent" 8 valeurs booléennes dans un seul octet, tandis qu'un bool par lui-même prendra tout l'octet 8 bits. L'avantage d'espace d'utiliser un champ de bits ou BitArray n'aura pas d'importance jusqu'à ce que vous stockiez lots de bools. (Le calcul est laissé au lecteur :-))


Benchmark

Résultats: Pour mon environnement de test primitif, il semble que BitArray est un bit plus rapide, mais est du même ordre de grandeur que le fait vous-même avec un type intégral. Un bool[], Qui était sans surprise le plus rapide, a également été testé. L'accès à des octets uniques en mémoire sera moins complexe que l'accès à des bits individuels dans différents octets.

Testing with 10000000 operations:
   A UInt32 bitfield took 808 ms.
   A BitArray (32) took 574 ms.
   A List<bool>(32) took 436 ms.

Code:

class Program
{
    static void Main(string[] args)
    {
        Random r = new Random();
        r.Next(1000);

        const int N = 10000000;

        Console.WriteLine("Testing with {0} operations:", N);

        Console.WriteLine("   A UInt32 bitfield took {0} ms.", TestBitField(r, N));
        Console.WriteLine("   A BitArray (32) took {0} ms.", TestBitArray(r, N));
        Console.WriteLine("   A List<bool>(32) took {0} ms.", TestBoolArray(r, N));

        Console.Read();
    }


    static long TestBitField(Random r, int n)
    {
        UInt32 bitfield = 0;

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < n; i++) {

            SetBit(ref bitfield, r.Next(32), true);
            bool b = GetBit(bitfield, r.Next(32));
            SetBit(ref bitfield, r.Next(32), b);
        }
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }

    static bool GetBit(UInt32 x, int bitnum) {
        if (bitnum < 0 || bitnum > 31)
            throw new ArgumentOutOfRangeException("Invalid bit number");

        return (x & (1 << bitnum)) != 0;
    }

    static void SetBit(ref UInt32 x, int bitnum, bool val)
    {
        if (bitnum < 0 || bitnum > 31)
            throw new ArgumentOutOfRangeException("Invalid bit number");

        if (val)
            x |= (UInt32)(1 << bitnum);
        else
            x &= ~(UInt32)(1 << bitnum);
    }



    static long TestBitArray(Random r, int n)
    {
        BitArray b = new BitArray(32, false);     // 40 bytes

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < n; i++) {

            b.Set(r.Next(32), true);
            bool v = b.Get(r.Next(32));
            b.Set(r.Next(32), v);
        }
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }



    static long TestBoolArray(Random r, int n)
    {
        bool[] ba = new bool[32];

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < n; i++) {

            ba[r.Next(32)] = true;
            bool v = ba[r.Next(32)];
            ba[r.Next(32)] = v;
        }
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }
}
24

@Jonathon Reinhart,

votre référence n'est malheureusement pas concluante. Il ne prend pas en compte les effets d'un éventuel chargement différé, mise en cache et/ou prélecture (par le CPU, le système d'exploitation hôte et/ou le runtime .NET).

Mélangez l'ordre des tests (ou appelez plusieurs fois les méthodes de test) et vous remarquerez peut-être différentes mesures de temps.

J'ai fait votre benchmark original construit avec la cible de plate-forme "Any CPU" et le profil client .NET 4.0, fonctionnant sur ma machine avec un processeur i7-3770 et Windows 7 64 bits.

Ce que j'ai obtenu était ceci:

Testing with 10000000 operations:
   A UInt32 bitfield took 484 ms.
   A BitArray (32) took 459 ms.
   A List<bool>(32) took 393 ms.

ce qui correspond à peu près à vos observations.

Cependant, l'exécution du test BitArray avant le test UInt32 a donné ceci:

Testing with 10000000 operations:
   A BitArray (32) took 513 ms.
   A UInt32 bitfield took 456 ms.
   A List<bool>(32) took 417 ms.

En regardant les heures des tests UInt32 et BitArray, vous remarquerez que le temps mesuré ne semble pas être connecté aux tests eux-mêmes, mais plutôt à l'ordre dans lequel les tests sont exécutés.

Pour atténuer ces effets secondaires au moins un peu, j'ai exécuté les méthodes de test deux fois dans chaque programme avec les résultats suivants.

Ordre de test Int32, BitArray, BoolArray, UInt32, BitArray, BoolArray:

Testing with 10000000 operations:
   A UInt32 bitfield took 476 ms.
   A BitArray (32) took 448 ms.
   A List<bool>(32) took 367 ms.

   A UInt32 bitfield took 419 ms.  <<-- Watch this.
   A BitArray (32) took 444 ms.    <<-- Watch this.
   A List<bool>(32) took 388 ms.

Ordre de test BitArray, UInt32, BoolArray, BitArray, UInt32, BoolArray:

Testing with 10000000 operations:
   A BitArray (32) took 514 ms.
   A UInt32 bitfield took 413 ms.
   A List<bool>(32) took 379 ms.

   A BitArray (32) took 444 ms.    <<-- Watch this.
   A UInt32 bitfield took 413 ms.  <<-- Watch this.
   A List<bool>(32) took 381 ms.

En regardant les deuxièmes invocations des méthodes de test, il semble qu'au moins sur les CPU i7 avec un runtime .NET à jour, le le test UInt32 est plus rapide que le test BitArray, tandis que le test BoolArray est étant toujours le plus rapide.

(Je m'excuse d'avoir dû écrire ma réponse au benchmark de Jonathon comme réponse, mais en tant que nouvel utilisateur SO je ne suis pas autorisé à commenter ...)

ÉDITER:

Au lieu de mélanger l'ordre des méthodes de test, vous pouvez essayer de mettre un Thread.Sleep (5000) ou similaire juste avant d'appeler le premier test ...

Le test d'origine semble également désavantager le test UInt32, car il inclut une vérification des limites " if (bitnum <0 || bitnum> 31) " , qui est exécuté 30 millions de fois. Aucun des deux autres tests ne comprend une telle vérification des limites. Cependant, ce n'est en fait pas toute la vérité, car BitArray et le tableau bool effectuent des vérifications de limites en interne.

Bien que je n'aie pas testé, je m'attends à ce que l'élimination des vérifications des limites fasse que les tests UInt32 et BoolArray fonctionnent de manière similaire, mais ce ne serait pas une bonne proposition pour une API publique.

23
user2819245