web-dev-qa-db-ja.com

C#では、BitArrayはビット単位のシフトを使用した単純な接続よりもビット値を取得する方が高速ですか?

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

2)。 _System.Collections.BitArray_Get(int index) メソッドを使用

  • 何が速いですか?
  • .NETプロジェクトのどのような状況でBitArrayは、ビット単位のシフトを使用した単純な結合よりも便利でしょうか?
20
Secret

BitArrayは任意の数のブール値を処理できるようになりますが、byteは8つだけ、intは32などしか保持できません。これが2つの最大の違いになります。

また、BitArrayIEnumerableを実装しますが、整数型は明らかにそうではありません。したがって、すべてはプロジェクトの要件に依存します。 IEnumerableまたは配列のようなインターフェースが必要な場合は、BitArrayを使用してください。

追跡しているwhat種類のデータでより明示的であるという理由だけで、どちらのソリューションでも実際にbool[]を使用します。 T

BitArrayまたはbitfieldは、8つのブール値を1バイトに「パック」するため、bool[]の約1/8のスペースを使用しますが、bool自体は8ビットバイト全体を占有します。 lots of BitArrayを格納するまでは、ビットフィールドまたはboolsを使用するスペースの利点は問題になりません。 (数学は読者に任されています:-))


ベンチマーク

結果:私のプリミティブテスト環境では、BitArraybitより高速であるように見えますが、積分で自分で行うのと同じ桁ですタイプ。 bool[]もテストされましたが、これは驚くほど高速でした。メモリ内の単一バイトへのアクセスは、異なるバイトの個々のビットへのアクセスよりも複雑ではなくなります。

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

コード:

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、

あなたのベンチマークは残念ながら決定的ではありません。レイジーロード、キャッシング、プリフェッチ(CPU、ホストOS、.NETランタイムによる)の影響は考慮されていません。

テストの順序を入れ替える(またはテストメソッドを複数回呼び出す)と、異なる時間測定に気付く場合があります。

「Any CPU」プラットフォームターゲットと.NET 4.0クライアントプロファイルで構築された元のベンチマークを、i7-3770 CPUと64ビットWindows 7を搭載したマシンで実行しました。

私が得たのはこれです:

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

これはあなたの観察とほぼ一致しています。

ただし、UInt32テストの前にBitArrayテストを実行すると、次の結果が得られました。

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

UInt32およびBitArrayテストの時間を見ると、測定された時間がテスト自体に接続されているようではなく、テストが実行される順序に接続されているように見えます。

これらの副作用を少なくとも少しは軽減するために、プログラムを実行するたびにテストメソッドを2回実行して、次の結果を得ました。

テストの順序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.

テスト順序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.

テストメソッドの2番目の呼び出しを見ると、少なくとも最新の.NETランタイムを備えたi7 CPUでは、Int32テストはBitArrayテストよりも高速ですである一方、BoolArrayテストはまだ最速です。

(私は回答としてJonathonのベンチマークへの私の応答を書く必要があったことをお詫びしますが、新しいSOユーザーとしてコメントすることは許可されていません...)

編集:

テストメソッドの順序を入れ替える代わりに、最初のテストを呼び出す直前にThread.Sleep(5000)または同様のコードを配置してみることができます...

また、元のテストでは境界チェック「if(bitnum <0 || bitnum> 31)」が含まれているため、UInt32テストは不利になっているようです。 、3000万回実行されます。他の2つのテストには、このような境界チェックは含まれていません。ただし、BitArrayとbool配列の両方が内部的に境界チェックを行うため、これは実際には完全な真実ではありません。

私はテストしませんでしたが、境界チェックを削除すると、UInt32とBoolArrayのテストが同様に実行されると思いますが、それはパブリックAPIには適していません。

23
user2819245