RecyclableArrayList.Remove, RecyclableArrayList.RemoveAt, RecyclableList.Remove & RecyclableList.RemoveAt performance improvements.
Support for removal of any item implemented in RecyclableList.Remove & .RemoveAt.
This commit is contained in:
1 parent
d6b351b1eb
commit
1ce91c0062
3 files changed
+133
-49
No files matched your search
@@ -411,6 +411,7 @@ namespace Recyclable.Collections
|
|||||||
_count++;
|
_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool Remove(T itemToRemove)
|
public bool Remove(T itemToRemove)
|
||||||
{
|
{
|
||||||
var itemIdx = IndexOf(itemToRemove);
|
var itemIdx = IndexOf(itemToRemove);
|
||||||
@@ -423,24 +424,28 @@ namespace Recyclable.Collections
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void RemoveAt(int index)
|
public void RemoveAt(int index)
|
||||||
{
|
{
|
||||||
int oldCount = _count;
|
if (index == _count - 1)
|
||||||
int oldCountMinus1 = oldCount - 1;
|
|
||||||
if (index == oldCountMinus1)
|
|
||||||
{
|
{
|
||||||
_count--;
|
_count--;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else if (index > _count - 1)
|
||||||
if (index >= oldCount)
|
|
||||||
{
|
{
|
||||||
ThrowArgumentOutOfRangeException($"Argument \"{index}\" is out of range. The current version supports removing the last element, only ");
|
ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range");
|
||||||
return;
|
}
|
||||||
|
else if (index < --_count)
|
||||||
|
{
|
||||||
|
Array.Copy(_memoryBlock, index + 1, _memoryBlock, index, _count - index);
|
||||||
}
|
}
|
||||||
|
|
||||||
_memoryBlock.CopyItems(index, oldCountMinus1, ref _memoryBlock);
|
if (NeedsClearing)
|
||||||
_count--;
|
{
|
||||||
|
#pragma warning disable CS8601 // In real use cases we'll never access it
|
||||||
|
new Span<T>(_memoryBlock, _count, 1)[0] = default;
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => Enumerate(this).GetEnumerator();
|
public IEnumerator<T> GetEnumerator() => Enumerate(this).GetEnumerator();
|
||||||
|
|||||||
@@ -49,42 +49,73 @@ namespace Recyclable.Collections
|
|||||||
public int NextItemBlockIndex => _nextItemBlockIndex;
|
public int NextItemBlockIndex => _nextItemBlockIndex;
|
||||||
public int NextItemIndex => _nextItemIndex;
|
public int NextItemIndex => _nextItemIndex;
|
||||||
|
|
||||||
private static void RemoveAt(RecyclableList<T> list, long index, ArrayPool<T> blockArrayPool)
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private void DoRemoveAt(long index)
|
||||||
{
|
{
|
||||||
long oldCountMinus1 = list._longCount - 1;
|
int blockSize = _blockSize;
|
||||||
if (index != oldCountMinus1)
|
int nextItemIndex = _nextItemIndex;
|
||||||
|
T[][] memoryBlocks = _memoryBlocks;
|
||||||
|
_longCount--;
|
||||||
|
if (index < _longCount)
|
||||||
{
|
{
|
||||||
ThrowArgumentOutOfRangeException();
|
int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift);
|
||||||
}
|
int sourceItemIndex = (int)((index + 1) & (blockSize - 1));
|
||||||
|
|
||||||
list._longCount--;
|
int targetBlockIndex = (int)(index >> _blockSizePow2Shift);
|
||||||
int blockSize = list._blockSize;
|
int targetItemIndex = (int)(index & (blockSize - 1));
|
||||||
if (list._nextItemIndex > 0)
|
|
||||||
|
int lastTakenBlockIndex = LastTakenBlockIndex;
|
||||||
|
while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex)))
|
||||||
{
|
{
|
||||||
list._nextItemIndex--;
|
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
|
else
|
||||||
{
|
{
|
||||||
list._nextItemBlockIndex--;
|
targetBlockIndex++;
|
||||||
list._nextItemIndex = blockSize;
|
targetItemIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list._capacity - oldCountMinus1 == blockSize)
|
|
||||||
{
|
|
||||||
T[][] memoryBlocks = list._memoryBlocks;
|
|
||||||
if (blockSize >= RecyclableDefaults.MinPooledArrayLength)
|
|
||||||
{
|
|
||||||
blockArrayPool.Return(memoryBlocks[list.ReservedBlockCount - 1], NeedsClearing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list._capacity -= blockSize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ThrowArgumentOutOfRangeException()
|
if (nextItemIndex > 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException("index");
|
_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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
@@ -765,7 +796,7 @@ namespace Recyclable.Collections
|
|||||||
var itemIndex = LongIndexOf(item);
|
var itemIndex = LongIndexOf(item);
|
||||||
if (itemIndex >= 0)
|
if (itemIndex >= 0)
|
||||||
{
|
{
|
||||||
RemoveAt(this, itemIndex, _defaultBlockArrayPool);
|
DoRemoveAt(itemIndex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,8 +825,25 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveAt(int index) => RemoveAt(this, index, _blockArrayPool);
|
public void RemoveAt(int index)
|
||||||
public void RemoveAt(long index) => RemoveAt(this, index, _blockArrayPool);
|
{
|
||||||
|
if (index > _longCount - 1 || index < 0)
|
||||||
|
{
|
||||||
|
ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
DoRemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(long index)
|
||||||
|
{
|
||||||
|
if (index > _longCount - 1 || index < 0)
|
||||||
|
{
|
||||||
|
ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
DoRemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
|
||||||
|
|
||||||
|
|||||||
@@ -545,34 +545,65 @@ namespace Recyclable.CollectionsTests
|
|||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))]
|
[MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))]
|
||||||
public void RemoveAtShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
|
public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
|
||||||
{
|
{
|
||||||
if (itemsCount is 0 or 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare
|
// Prepare
|
||||||
using var list = new RecyclableList<long>(testData, minBlockSize: targetBlockSize);
|
using var list = new RecyclableList<long>(testData, minBlockSize: targetBlockSize);
|
||||||
|
|
||||||
|
// Act & Validate
|
||||||
|
for (var deleted = 1; deleted <= itemsCount; deleted++)
|
||||||
|
{
|
||||||
// Act
|
// Act
|
||||||
_ = Assert.Throws<ArgumentOutOfRangeException>(() => list.RemoveAt(0));
|
list.RemoveAt(0);
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_ = list.Count.Should().Be((int)(itemsCount - deleted));
|
||||||
|
_ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = list.Should().BeEmpty();
|
||||||
|
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))]
|
[MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))]
|
||||||
public void RemoveShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
|
public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
|
||||||
{
|
{
|
||||||
if (itemsCount is 0 or 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare
|
// Prepare
|
||||||
using var list = new RecyclableList<long>(testData, minBlockSize: targetBlockSize);
|
using var list = new RecyclableList<long>(testData, minBlockSize: targetBlockSize);
|
||||||
|
|
||||||
|
// Act & Validate
|
||||||
|
for (var deleted = 1; deleted <= itemsCount; deleted++)
|
||||||
|
{
|
||||||
// Act
|
// Act
|
||||||
_ = Assert.Throws<ArgumentOutOfRangeException>(() => list.Remove(testData.First()));
|
list.RemoveAt(list.Count - 1);
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_ = list.Count.Should().Be((int)(itemsCount - deleted));
|
||||||
|
_ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = list.Should().BeEmpty();
|
||||||
|
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))]
|
||||||
|
public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
|
||||||
|
{
|
||||||
|
// Prepare
|
||||||
|
using var list = new RecyclableList<long>(testData, minBlockSize: targetBlockSize);
|
||||||
|
|
||||||
|
// Act & Validate
|
||||||
|
for (var deleted = 1; deleted <= itemsCount; deleted++)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
_ = list.Remove(deleted).Should().BeTrue();
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_ = list.Count.Should().Be((int)(itemsCount - deleted));
|
||||||
|
_ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|||||||
Reference in new issue
Block a user