performance oriented solutions
As the following statements are true in .NET
sizeof(bool) == 1
*(byte*)&someBool == 1
where someBool is true
*(byte*)&someBool == 0
where someBool is false
you could fall back to unsafe
code and pointer casting (as C# will not allow simply casting bool
to byte
or int
).
Your code would then look something like this
if (*(byte*)&bool1 + *(byte*)&bool2 + *(byte*)&bool3 > 1)
{
// do stuff
}
The benefit here would be that you don't have any additional branching making this one faster than the obvious myBool ? 1 : 0
.
The drawback here would be the usage of unsafe
and pointers which often isn't a well received solution in the managed .NET world. Also the assumption that sizeof(bool) == 1
could be questioned as this doesn't apply to all languages but at least in C# .NET it holds true.
If the pointer stuff is too annoying for you, you could always hide it in an extension method:
using System.Runtime.CompilerServices;
// ...
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int ToInt(this bool b) => *(byte*)&b;
your code would then turn into a more readable
if (bool1.ToInt() + bool2.ToInt() + bool3.ToInt() > 1)
{
// do stuff
}
Obviously you could always combine this with LINQ as you please
if (myBools.Sum(b => b.ToInt()) > 1)
{
// do stuff
}
or if you value performance over anything else this one's probably faster
bool[] myBools = ...
fixed (bool* boolPtr = myBools)
{
byte* bytePtr = (byte*)boolPtr;
int numberOfTrueBools = 0;
// count all true booleans in the array
for (int i = 0; i < myBools.Length; numberOfTrueBools += bytePtr[i], i++);
// do something with your numberOfTrueBools ...
}
Or if you have a huge input array you could even go for a hardware accelerated SIMD solution ...
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
// ...
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static unsafe int CountTrueBytesSIMD(this bool[] myBools)
{
// we need to get a pointer to the bool array to do our magic
fixed (bool* ptr = myBools)
{
// reinterpret all booleans as bytes
byte* bytePtr = (byte*)ptr;
// calculate the number of 32 bit integers that would fit into the array
int dwordLength = myBools.Length >> 2;
// for SIMD, allocate a result vector
Vector128<int> result = Vector128<int>.Zero;
// loop variable
int i = 0;
// it could be that SSSE3 isn't supported...
if (Ssse3.IsSupported)
{
// remember: we're assuming little endian!
// we need this mask to convert the byte vectors to valid int vectors
Vector128<int> cleanupMask = Vector128.Create(0x000000FF);
// iterate over the array processing 16 bytes at once
// TODO: you could even go to 32 byte chunks if AVX-2 is supported...
for (; i < dwordLength - Vector128<int>.Count; i += Vector128<int>.Count)
{
// load 16 bools / bytes from memory
Vector128<byte> v = Sse2.LoadVector128((byte*)((int*)bytePtr + i));
// now count the number of "true" bytes in every 32 bit integers
// 1. shift
Vector128<int> v0 = v.As<byte, int>();
Vector128<int> v1 = Sse2.ShiftRightLogical128BitLane(v, 1).As<byte, int>();
Vector128<int> v2 = Sse2.ShiftRightLogical128BitLane(v, 2).As<byte, int>();
Vector128<int> v3 = Sse2.ShiftRightLogical128BitLane(v, 3).As<byte, int>();
// 2. cleanup invalid bytes
v0 = Sse2.And(v0, cleanupMask);
v1 = Sse2.And(v1, cleanupMask);
v2 = Sse2.And(v2, cleanupMask);
v3 = Sse2.And(v3, cleanupMask);
// 3. add them together. We now have a vector of ints holding the number
// of "true" booleans / 0x01 bytes in their 32 bit memory region
Vector128<int> roundResult = Sse2.Add(Sse2.Add(Sse2.Add(v0, v1), v2), v3);
// 4 now add everything to the result
result = Sse2.Add(result, roundResult);
}
// reduce the result vector to a scalar by horizontally adding log_2(n) times
// where n is the number of words in out vector
result = Ssse3.HorizontalAdd(result, result);
result = Ssse3.HorizontalAdd(result, result);
}
int totalNumberOfTrueBools = result.ToScalar();
// now add all remaining booleans together
// (if the input array wasn't a multiple of 16 bytes or SSSE3 wasn't supported)
i <<= 2;
for (; i < myBools.Length; totalNumberOfTrueBools += bytePtr[i], i++);
return totalNumberOfTrueBools;
}
}