If someone still looking for some different solution that is fast enough: I would suggest using aggressive inlining [MethodImpl(256)] on GetBit and SetBit methods. Also with look-up table of OR and XOR values for bit positions. Removing bit position check since System.IndexOutOfRangeException shall be enough to indicate error in bit position, and we need not to consume CPU for additional checks.
Also if performing this on large number of elements, and debugging, would strongly suggest the use of [DebuggerHidden] attribute. DebuggerHidden attribute helps debugger skip debugging of methods marked with this attribute and speed-up the debug process.
Use code from the Jonathon Reinhart answer and add this methods and tests for TestBitFieldOpt and TestBitFieldOpt2.
static readonly uint[] BITMASK = new uint[]
{
0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, 0x00000040, 0x00000080,
0x00000100, 0x00000200, 0x00000400, 0x00000800, 0x00001000, 0x00002000, 0x00004000, 0x00008000,
0x00010000, 0x00020000, 0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, 0x00800000,
0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000
};
static readonly uint[] BITMASK_XOR = new uint[]
{
0xFFFFFFFE, 0xFFFFFFFD, 0xFFFFFFFB, 0xFFFFFFF7, 0xFFFFFFEF, 0xFFFFFFDF, 0xFFFFFFBF, 0xFFFFFF7F,
0xFFFFFEFF, 0xFFFFFDFF, 0xFFFFFBFF, 0xFFFFF7FF, 0xFFFFEFFF, 0xFFFFDFFF, 0xFFFFBFFF, 0xFFFF7FFF,
0xFFFEFFFF, 0xFFFDFFFF, 0xFFFBFFFF, 0xFFF7FFFF, 0xFFEFFFFF, 0xFFDFFFFF, 0xFFBFFFFF, 0xFF7FFFFF,
0xFEFFFFFF, 0xFDFFFFFF, 0xFBFFFFFF, 0xF7FFFFFF, 0xEFFFFFFF, 0xDFFFFFFF, 0xBFFFFFFF, 0x7FFFFFFF
};
static long TestBitFieldOpt(Random r, int n)
{
bool value;
UInt32 bitfield = 0;
var sw = Stopwatch.StartNew();
for (int i = 0; i < n; i++)
{
SetBitOpt(ref bitfield, r.Next(32), true);
value = GetBitOpt(bitfield, r.Next(32));
SetBitOpt(ref bitfield, r.Next(32), value);
}
sw.Stop();
return sw.ElapsedMilliseconds;
}
static long TestBitFieldOpt2(Random r, int n)
{
bool value;
UInt32 bitfield = 0;
var sw = Stopwatch.StartNew();
for (int i = 0; i < n; i++)
{
bitfield = SetBitOpt2(bitfield, r.Next(32), true);
value = GetBitOpt(bitfield, r.Next(32));
bitfield = SetBitOpt2(bitfield, r.Next(32), value);
}
sw.Stop();
return sw.ElapsedMilliseconds;
}
[MethodImpl(256)]
static bool GetBitOpt(UInt32 bitfield, int bitindex)
{
return (bitfield & BITMASK[bitindex]) != 0;
}
[MethodImpl(256)]
static void SetBitOpt(ref UInt32 bitfield, int bitindex, bool value)
{
if (value)
bitfield |= BITMASK[bitindex];
else
bitfield &= BITMASK_XOR[bitindex];
}
[MethodImpl(256)]
static UInt32 SetBitOpt2(UInt32 bitfield, int bitindex, bool value)
{
if (value)
return (bitfield | BITMASK[bitindex]);
return (bitfield & BITMASK_XOR[bitindex]);
}
I've increased the number of tests by power of 10 (to get more realistic results), and the results of optimized code are pretty close to the fastest method:
Testing with 100000000 operations:
A BitArray (32) took : 4947 ms.
A UInt32 bitfield took : 4399 ms.
A UInt32 bitfieldopt : 3583 ms.
A UInt32 bitfieldopt2 : 3583 ms.
A List<bool>(32) took : 3491 ms.
Generally less variables on local stack, less branches, and pre-calculated values win most of the time. So get the book and pencil and shorten the math to have those less ... true inlining inside functions helps a lot, but better using [MethodImpl(256)] attribute on methods for readability/up-keep of source code, as presented above.
Hope this helps someone find solution for his problem(s).
System.Diagnostics.Stopwatch
you could time if it's faster. It's best to try it in as close to the production environment as possible. – Rupertruperta