RecyclableList.Remove & .RemoveAt performance improvements.

This commit is contained in:
mlemanczyk committed 2023-04-08 00:34:23 +02:00
1 parent 064b3f0cb0
commit 3369bcc6cb
1 file changed
+102 -52
+102 -52
View File
@@ -50,71 +50,77 @@ namespace Recyclable.Collections
public int NextItemIndex => _nextItemIndex; public int NextItemIndex => _nextItemIndex;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void DoRemoveAt(long index) private static void DoRemoveAt(RecyclableList<T> list, long index)
{ {
int blockSize = _blockSize; list._longCount--;
int nextItemIndex = _nextItemIndex; if (index < list._longCount)
T[][] memoryBlocks = _memoryBlocks;
_longCount--;
if (index < _longCount)
{ {
int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); CopyItems(list, index);
int sourceItemIndex = (int)((index + 1) & (blockSize - 1));
int targetBlockIndex = (int)(index >> _blockSizePow2Shift);
int targetItemIndex = (int)(index & (blockSize - 1));
int lastTakenBlockIndex = LastTakenBlockIndex;
while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex)))
{
int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0
? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex)
: Math.Min(nextItemIndex, blockSize - targetItemIndex);
Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy);
// We didn't have enough room in the target array block. There are still items in the source array block to copy.
if (sourceItemIndex + toCopy < blockSize)
{
sourceItemIndex += toCopy;
targetBlockIndex++;
targetItemIndex = 0;
}
// We copied all the source items in the current array block. But have we filled the target?
else
{
sourceItemIndex = 0;
sourceBlockIndex++;
if (targetItemIndex + toCopy < blockSize)
{
targetItemIndex += toCopy;
}
else
{
targetBlockIndex++;
targetItemIndex = 0;
}
}
}
} }
if (nextItemIndex > 0) if (list._nextItemIndex > 0)
{ {
_nextItemIndex--; list._nextItemIndex--;
} }
else else
{ {
_nextItemIndex = blockSize - 1; list._nextItemIndex = list._blockSize - 1;
_nextItemBlockIndex--; list._nextItemBlockIndex--;
} }
if (NeedsClearing) if (NeedsClearing)
{ {
#pragma warning disable CS8601 // In real use cases we'll never access it #pragma warning disable CS8601 // In real use cases we'll never access it
new Span<T>(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; new Span<T>(list._memoryBlocks[list._nextItemBlockIndex], list._nextItemIndex, 1)[0] = default;
#pragma warning restore CS8601 #pragma warning restore CS8601
} }
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
private static void CopyItems(RecyclableList<T> list, long index)
{
T[][] memoryBlocks = list._memoryBlocks;
int nextItemIndex = list._nextItemIndex;
int blockSize = list._blockSize;
int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2Shift);
int sourceItemIndex = (int)((index + 1) & (blockSize - 1));
int targetBlockIndex = (int)(index >> list._blockSizePow2Shift);
int targetItemIndex = (int)(index & (blockSize - 1));
int lastTakenBlockIndex = list.LastTakenBlockIndex;
while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex)))
{
int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0
? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex)
: Math.Min(nextItemIndex, blockSize - targetItemIndex);
Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy);
// We didn't have enough room in the target array block. There are still items in the source array block to copy.
if (sourceItemIndex + toCopy < blockSize)
{
sourceItemIndex += toCopy;
targetBlockIndex++;
targetItemIndex = 0;
}
// We copied all the source items in the current array block. But have we filled the target?
else
{
sourceItemIndex = 0;
sourceBlockIndex++;
if (targetItemIndex + toCopy < blockSize)
{
targetItemIndex += toCopy;
}
else
{
targetBlockIndex++;
targetItemIndex = 0;
}
}
}
}
private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message);
protected void AddRangeEnumerated(IEnumerable<T> source, int growByCount) protected void AddRangeEnumerated(IEnumerable<T> source, int growByCount)
@@ -231,6 +237,7 @@ namespace Recyclable.Collections
return; return;
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex)
{ {
Span<T[]> memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); Span<T[]> memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1);
@@ -258,7 +265,7 @@ namespace Recyclable.Collections
return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool<T[]> memoryBlocksPool, ArrayPool<T> blockArrayPool) protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool<T[]> memoryBlocksPool, ArrayPool<T> blockArrayPool)
{ {
int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize);
@@ -796,7 +803,7 @@ namespace Recyclable.Collections
var itemIndex = LongIndexOf(item); var itemIndex = LongIndexOf(item);
if (itemIndex >= 0) if (itemIndex >= 0)
{ {
DoRemoveAt(itemIndex); DoRemoveAt(this, itemIndex);
return true; return true;
} }
@@ -825,6 +832,7 @@ namespace Recyclable.Collections
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void RemoveAt(int index) public void RemoveAt(int index)
{ {
if (index > _longCount - 1 || index < 0) if (index > _longCount - 1 || index < 0)
@@ -832,7 +840,28 @@ namespace Recyclable.Collections
ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}");
} }
DoRemoveAt(index); _longCount--;
if (index < _longCount)
{
CopyItems(this, index);
}
if (_nextItemIndex > 0)
{
_nextItemIndex--;
}
else
{
_nextItemIndex = _blockSize - 1;
_nextItemBlockIndex--;
}
if (NeedsClearing)
{
#pragma warning disable CS8601 // In real use cases we'll never access it
new Span<T>(_memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default;
#pragma warning restore CS8601
}
} }
public void RemoveAt(long index) public void RemoveAt(long index)
@@ -842,7 +871,28 @@ namespace Recyclable.Collections
ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}");
} }
DoRemoveAt(index); _longCount--;
if (index < _longCount)
{
CopyItems(this, index);
}
if (_nextItemIndex > 0)
{
_nextItemIndex--;
}
else
{
_nextItemIndex = _blockSize - 1;
_nextItemBlockIndex--;
}
if (NeedsClearing)
{
#pragma warning disable CS8601 // In real use cases we'll never access it
new Span<T>(_memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default;
#pragma warning restore CS8601
}
} }
IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();