Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Change BitHelper in System.Collections to be a ref struct
Browse files Browse the repository at this point in the history
Simplifies BitHelper, basing it on span instead of having to accomodate both pointers and arrays, and avoids allocating the BitHelper instance.  We now also clear the input optionally.
  • Loading branch information
stephentoub committed Jan 8, 2019
1 parent 4b5b5c1 commit 82ce02d
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 165 deletions.
132 changes: 17 additions & 115 deletions src/System.Collections/src/System/Collections/Generic/BitHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,136 +4,38 @@

namespace System.Collections.Generic
{
/// <summary>
/// ABOUT:
/// Helps with operations that rely on bit marking to indicate whether an item in the
/// collection should be added, removed, visited already, etc.
///
/// BitHelper doesn't allocate the array; you must pass in an array or ints allocated on the
/// stack or heap. ToIntArrayLength() tells you the int array size you must allocate.
///
/// USAGE:
/// Suppose you need to represent a bit array of length (i.e. logical bit array length)
/// BIT_ARRAY_LENGTH. Then this is the suggested way to instantiate BitHelper:
/// ***************************************************************************
/// int intArrayLength = BitHelper.ToIntArrayLength(BIT_ARRAY_LENGTH);
/// BitHelper bitHelper;
/// if (intArrayLength less than stack alloc threshold)
/// int* m_arrayPtr = stackalloc int[intArrayLength];
/// bitHelper = new BitHelper(m_arrayPtr, intArrayLength);
/// else
/// int[] m_arrayPtr = new int[intArrayLength];
/// bitHelper = new BitHelper(m_arrayPtr, intArrayLength);
/// ***************************************************************************
///
/// IMPORTANT:
/// The second ctor args, length, should be specified as the length of the int array, not
/// the logical bit array. Because length is used for bounds checking into the int array,
/// it's especially important to get this correct for the stackalloc version. See the code
/// samples above; this is the value gotten from ToIntArrayLength().
///
/// The length ctor argument is the only exception; for other methods -- MarkBit and
/// IsMarked -- pass in values as indices into the logical bit array, and it will be mapped
/// to the position within the array of ints.
///
/// FUTURE OPTIMIZATIONS:
/// A method such as FindFirstMarked/Unmarked Bit would be useful for callers that operate
/// on a bit array and then need to loop over it. In particular, if it avoided visiting
/// every bit, it would allow good perf improvements when the bit array is sparse.
/// </summary>
internal unsafe sealed class BitHelper
{ // should not be serialized
private const byte MarkedBitFlag = 1;
private const byte IntSize = 32;
internal ref struct BitHelper
{
private const int IntSize = sizeof(int) * 8;
private readonly Span<int> _span;

// m_length of underlying int array (not logical bit array)
private readonly int _length;

// ptr to stack alloc'd array of ints
private readonly int* _arrayPtr;

// array of ints
private readonly int[] _array;

// whether to operate on stack alloc'd or heap alloc'd array
private readonly bool _useStackAlloc;

/// <summary>
/// Instantiates a BitHelper with a heap alloc'd array of ints
/// </summary>
/// <param name="bitArrayPtr">Pointer to int array to hold bits</param>
/// <param name="length">length of int array</param>
internal BitHelper(int* bitArrayPtr, int length)
{
_arrayPtr = bitArrayPtr;
_length = length;
_useStackAlloc = true;
}

/// <summary>
/// Instantiates a BitHelper with a heap alloc'd array of ints
/// </summary>
/// <param name="bitArray">int array to hold bits</param>
/// <param name="length">length of int array</param>
internal BitHelper(int[] bitArray, int length)
internal BitHelper(Span<int> span, bool clear)
{
_array = bitArray;
_length = length;
if (clear)
{
span.Clear();
}
_span = span;
}

/// <summary>
/// Mark bit at specified position
/// </summary>
/// <param name="bitPosition"></param>
internal void MarkBit(int bitPosition)
{
int bitArrayIndex = bitPosition / IntSize;
if (bitArrayIndex < _length && bitArrayIndex >= 0)
if ((uint)bitArrayIndex < (uint)_span.Length)
{
int flag = (MarkedBitFlag << (bitPosition % IntSize));
if (_useStackAlloc)
{
_arrayPtr[bitArrayIndex] |= flag;
}
else
{
_array[bitArrayIndex] |= flag;
}
_span[bitArrayIndex] |= (1 << (bitPosition % IntSize));
}
}

/// <summary>
/// Is bit at specified position marked?
/// </summary>
/// <param name="bitPosition"></param>
/// <returns></returns>
internal bool IsMarked(int bitPosition)
{
int bitArrayIndex = bitPosition / IntSize;
if (bitArrayIndex < _length && bitArrayIndex >= 0)
{
int flag = (MarkedBitFlag << (bitPosition % IntSize));
if (_useStackAlloc)
{
return ((_arrayPtr[bitArrayIndex] & flag) != 0);
}
else
{
return ((_array[bitArrayIndex] & flag) != 0);
}
}
return false;
return
(uint)bitArrayIndex < (uint)_span.Length &&
(_span[bitArrayIndex] & (1 << (bitPosition % IntSize))) != 0;
}

/// <summary>
/// How many ints must be allocated to represent n bits. Returns (n+31)/32, but
/// avoids overflow
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
internal static int ToIntArrayLength(int n)
{
return n > 0 ? ((n - 1) / IntSize + 1) : 0;
}
/// <summary>How many ints must be allocated to represent n bits. Returns (n+31)/32, but avoids overflow.</summary>
internal static int ToIntArrayLength(int n) => n > 0 ? ((n - 1) / IntSize + 1) : 0;
}
}
55 changes: 16 additions & 39 deletions src/System.Collections/src/System/Collections/Generic/HashSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1360,17 +1360,10 @@ private unsafe void IntersectWithEnumerable(IEnumerable<T> other)
int originalLastIndex = _lastIndex;
int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex);

BitHelper bitHelper;
if (intArrayLength <= StackAllocThreshold)
{
int* bitArrayPtr = stackalloc int[intArrayLength];
bitHelper = new BitHelper(bitArrayPtr, intArrayLength);
}
else
{
int[] bitArray = new int[intArrayLength];
bitHelper = new BitHelper(bitArray, intArrayLength);
}
Span<int> span = stackalloc int[StackAllocThreshold];
BitHelper bitHelper = intArrayLength <= StackAllocThreshold ?
new BitHelper(span.Slice(0, intArrayLength), clear: true) :
new BitHelper(new int[intArrayLength], clear: false);

// mark if contains: find index of in slots array and mark corresponding element in bit array
foreach (T item in other)
Expand Down Expand Up @@ -1465,24 +1458,15 @@ private unsafe void SymmetricExceptWithEnumerable(IEnumerable<T> other)
int originalLastIndex = _lastIndex;
int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex);

BitHelper itemsToRemove;
BitHelper itemsAddedFromOther;
if (intArrayLength <= StackAllocThreshold / 2)
{
int* itemsToRemovePtr = stackalloc int[intArrayLength];
itemsToRemove = new BitHelper(itemsToRemovePtr, intArrayLength);

int* itemsAddedFromOtherPtr = stackalloc int[intArrayLength];
itemsAddedFromOther = new BitHelper(itemsAddedFromOtherPtr, intArrayLength);
}
else
{
int[] itemsToRemoveArray = new int[intArrayLength];
itemsToRemove = new BitHelper(itemsToRemoveArray, intArrayLength);
Span<int> itemsToRemoveSpan = stackalloc int[StackAllocThreshold / 2];
BitHelper itemsToRemove = intArrayLength <= StackAllocThreshold / 2 ?
new BitHelper(itemsToRemoveSpan.Slice(0, intArrayLength), clear: true) :
new BitHelper(new int[intArrayLength], clear: false);

int[] itemsAddedFromOtherArray = new int[intArrayLength];
itemsAddedFromOther = new BitHelper(itemsAddedFromOtherArray, intArrayLength);
}
Span<int> itemsAddedFromOtherSpan = stackalloc int[StackAllocThreshold / 2];
BitHelper itemsAddedFromOther = intArrayLength <= StackAllocThreshold / 2 ?
new BitHelper(itemsAddedFromOtherSpan.Slice(0, intArrayLength), clear: true) :
new BitHelper(new int[intArrayLength], clear: false);

foreach (T item in other)
{
Expand Down Expand Up @@ -1629,17 +1613,10 @@ private unsafe ElementCount CheckUniqueAndUnfoundElements(IEnumerable<T> other,
int originalLastIndex = _lastIndex;
int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex);

BitHelper bitHelper;
if (intArrayLength <= StackAllocThreshold)
{
int* bitArrayPtr = stackalloc int[intArrayLength];
bitHelper = new BitHelper(bitArrayPtr, intArrayLength);
}
else
{
int[] bitArray = new int[intArrayLength];
bitHelper = new BitHelper(bitArray, intArrayLength);
}
Span<int> span = stackalloc int[StackAllocThreshold];
BitHelper bitHelper = intArrayLength <= StackAllocThreshold ?
new BitHelper(span.Slice(0, intArrayLength), clear: true) :
new BitHelper(new int[intArrayLength], clear: false);

// count of items in other not found in this
int unfoundCount = 0;
Expand Down
15 changes: 4 additions & 11 deletions src/System.Collections/src/System/Collections/Generic/SortedSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1449,17 +1449,10 @@ private unsafe ElementCount CheckUniqueAndUnfoundElements(IEnumerable<T> other,
int originalLastIndex = Count;
int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex);

BitHelper bitHelper;
if (intArrayLength <= StackAllocThreshold)
{
int* bitArrayPtr = stackalloc int[intArrayLength];
bitHelper = new BitHelper(bitArrayPtr, intArrayLength);
}
else
{
int[] bitArray = new int[intArrayLength];
bitHelper = new BitHelper(bitArray, intArrayLength);
}
Span<int> span = stackalloc int[StackAllocThreshold];
BitHelper bitHelper = intArrayLength <= StackAllocThreshold ?
new BitHelper(span.Slice(0, intArrayLength), clear: true) :
new BitHelper(new int[intArrayLength], clear: false);

// count of items in other not found in this
int UnfoundCount = 0;
Expand Down

0 comments on commit 82ce02d

Please sign in to comment.