RecyclableList performance improvements & fixes. New tests added.
This commit is contained in:
1 parent
02138dfac6
commit
3ce3becfbd
3 files changed
+218
-27
No files matched your search
@@ -293,6 +293,8 @@ namespace Recyclable.Collections
|
|||||||
_count = targetItemIdx;
|
_count = targetItemIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T[] AsArray() => _memoryBlock;
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_count = 0;
|
_count = 0;
|
||||||
|
|||||||
@@ -274,39 +274,143 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
public void AddRange(in T[] items)
|
public void AddRange(in T[] items)
|
||||||
{
|
{
|
||||||
long sourceItemsCount = items.LongLength;
|
long targetCapacity = _longCount + items.LongLength;
|
||||||
long oldLongCount = _longCount;
|
|
||||||
long targetCapacity = oldLongCount + sourceItemsCount;
|
|
||||||
if (_capacity < targetCapacity)
|
if (_capacity < targetCapacity)
|
||||||
{
|
{
|
||||||
_ = EnsureCapacity(targetCapacity);
|
_ = EnsureCapacity(targetCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
int blockSize = _blockSize;
|
int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy = 0;
|
||||||
int targetItemIdx = (int)(oldLongCount % blockSize);
|
Span<T> itemsSpan = new(items);
|
||||||
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
|
||||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||||
int memoryBlocksCount = memoryBlocksSpan.Length;
|
Span<T> targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex);
|
||||||
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]);
|
while (!itemsSpan.IsEmpty)
|
||||||
for (var i = 0L; i < sourceItemsCount; i++)
|
|
||||||
{
|
{
|
||||||
targetBlockSpan[targetItemIdx++] = items[i];
|
if (targetBlockArraySpan.Length < itemsSpan.Length)
|
||||||
if (targetItemIdx == blockSize)
|
|
||||||
{
|
{
|
||||||
targetBlockIdx++;
|
toCopy = targetBlockArraySpan.Length;
|
||||||
if (targetBlockIdx == memoryBlocksCount)
|
itemsSpan[..toCopy].CopyTo(targetBlockArraySpan);
|
||||||
|
targetBlockIndex++;
|
||||||
|
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toCopy = itemsSpan.Length;
|
||||||
|
itemsSpan[..toCopy].CopyTo(targetBlockArraySpan);
|
||||||
|
targetBlockArraySpan = targetBlockArraySpan[..toCopy];
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsSpan = itemsSpan[toCopy..];
|
||||||
|
}
|
||||||
|
|
||||||
|
_nextItemIndex = toCopy;
|
||||||
|
_longCount = targetCapacity;
|
||||||
|
_lastBlockIndex = targetBlockIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(in RecyclableArrayList<T> items)
|
||||||
|
{
|
||||||
|
long targetCapacity = _longCount + items.Count;
|
||||||
|
if (_capacity < targetCapacity)
|
||||||
|
{
|
||||||
|
_ = EnsureCapacity(targetCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy = 0;
|
||||||
|
Span<T> itemsSpan = new(items.AsArray());
|
||||||
|
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||||
|
Span<T> targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex);
|
||||||
|
while (!itemsSpan.IsEmpty)
|
||||||
|
{
|
||||||
|
if (targetBlockArraySpan.Length < itemsSpan.Length)
|
||||||
|
{
|
||||||
|
toCopy = targetBlockArraySpan.Length;
|
||||||
|
itemsSpan[..toCopy].CopyTo(targetBlockArraySpan);
|
||||||
|
targetBlockIndex++;
|
||||||
|
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toCopy = itemsSpan.Length;
|
||||||
|
itemsSpan[..toCopy].CopyTo(targetBlockArraySpan);
|
||||||
|
targetBlockArraySpan = targetBlockArraySpan[..toCopy];
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsSpan = itemsSpan[toCopy..];
|
||||||
|
}
|
||||||
|
|
||||||
|
_nextItemIndex = toCopy;
|
||||||
|
_longCount = targetCapacity;
|
||||||
|
_lastBlockIndex = targetBlockIndex;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(in RecyclableList<T> items)
|
||||||
|
{
|
||||||
|
long itemsCount = items.LongCount;
|
||||||
|
long targetCapacity = _longCount + itemsCount;
|
||||||
|
if (_capacity < targetCapacity)
|
||||||
|
{
|
||||||
|
_ = EnsureCapacity(targetCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
int blockSize = _blockSize,
|
||||||
|
sourceBlockIndex = 0,
|
||||||
|
targetBlockIndex = _lastBlockIndex,
|
||||||
|
toCopy = 0;
|
||||||
|
|
||||||
|
Span<T[]> sourceMemoryBlocksSpan = new(items._memoryBlocks);
|
||||||
|
Span<T> itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize);
|
||||||
|
Span<T[]> targetMemoryBlocksSpan = new(_memoryBlocks);
|
||||||
|
Span<T> targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex);
|
||||||
|
long copiedCount = 0;
|
||||||
|
while (copiedCount < itemsCount)
|
||||||
|
{
|
||||||
|
toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length);
|
||||||
|
if (targetBlockArraySpan.Length < toCopy)
|
||||||
|
{
|
||||||
|
toCopy = targetBlockArraySpan.Length;
|
||||||
|
itemsSpan[..toCopy].CopyTo(targetBlockArraySpan);
|
||||||
|
targetBlockIndex++;
|
||||||
|
targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize);
|
||||||
|
itemsSpan = itemsSpan[toCopy..];
|
||||||
|
copiedCount += toCopy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemsSpan[..toCopy].CopyTo(targetBlockArraySpan);
|
||||||
|
targetBlockArraySpan = targetBlockArraySpan[toCopy..];
|
||||||
|
itemsSpan = itemsSpan[toCopy..];
|
||||||
|
if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length)
|
||||||
{
|
{
|
||||||
break;
|
sourceBlockIndex++;
|
||||||
|
itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
copiedCount += toCopy;
|
||||||
targetItemIdx = 0;
|
if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount)
|
||||||
|
{
|
||||||
|
targetBlockIndex++;
|
||||||
|
targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex]);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (targetCapacity % blockSize == 0)
|
||||||
|
{
|
||||||
|
_nextItemIndex = 0;
|
||||||
|
_lastBlockIndex = targetBlockIndex + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_nextItemIndex = (int)(targetCapacity % blockSize);
|
||||||
|
_lastBlockIndex = targetBlockIndex;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
_lastBlockIndex = targetBlockIdx;
|
|
||||||
_nextItemIndex = targetItemIdx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(in List<T> items)
|
public void AddRange(in List<T> items)
|
||||||
@@ -324,11 +428,12 @@ namespace Recyclable.Collections
|
|||||||
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
||||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||||
var memoryBlocksCount = memoryBlocksSpan.Length;
|
var memoryBlocksCount = memoryBlocksSpan.Length;
|
||||||
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]);
|
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize);
|
||||||
|
|
||||||
for (int i = 0; i < sourceItemsCount; i++)
|
for (int i = 0; i < sourceItemsCount; i++)
|
||||||
{
|
{
|
||||||
targetBlockSpan[targetItemIdx++] = items[i];
|
targetBlockSpan[targetItemIdx] = items[i];
|
||||||
if (targetItemIdx == blockSize)
|
if (targetItemIdx + 1 == blockSize)
|
||||||
{
|
{
|
||||||
targetBlockIdx++;
|
targetBlockIdx++;
|
||||||
if (targetBlockIdx == memoryBlocksCount)
|
if (targetBlockIdx == memoryBlocksCount)
|
||||||
@@ -336,9 +441,13 @@ namespace Recyclable.Collections
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx], 0, blockSize);
|
||||||
targetItemIdx = 0;
|
targetItemIdx = 0;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetItemIdx++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
@@ -383,7 +492,7 @@ namespace Recyclable.Collections
|
|||||||
_nextItemIndex = targetItemIdx;
|
_nextItemIndex = targetItemIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddRange(IEnumerable<T> source, int growByCount = RecyclableDefaults.MinPooledArrayLength)
|
public void AddRange(IEnumerable<T> source, int growByCount = RecyclableDefaults.MinPooledArrayLength)
|
||||||
{
|
{
|
||||||
if (source is T[] sourceArray)
|
if (source is T[] sourceArray)
|
||||||
{
|
{
|
||||||
@@ -403,6 +512,12 @@ namespace Recyclable.Collections
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source is RecyclableList<T> sourceRecyclableList)
|
||||||
|
{
|
||||||
|
AddRange(sourceRecyclableList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (source is IList<T> sourceIList)
|
if (source is IList<T> sourceIList)
|
||||||
{
|
{
|
||||||
AddRange(sourceIList);
|
AddRange(sourceIList);
|
||||||
|
|||||||
@@ -50,6 +50,25 @@ namespace Recyclable.CollectionsTests
|
|||||||
_ = list.Should().BeEquivalentTo(testNumbers);
|
_ = list.Should().BeEquivalentTo(testNumbers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddShouldAddItemWhenAfterAddRange()
|
||||||
|
{
|
||||||
|
// Prepare
|
||||||
|
int[] testNumbers = new[] { 1, 2, 2, 3 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using var list = new RecyclableList<int>();
|
||||||
|
list.AddRange(_testData);
|
||||||
|
foreach (var index in testNumbers)
|
||||||
|
{
|
||||||
|
list.Add(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_ = list.LongCount.Should().Be(_totalObjectCount + testNumbers.Length);
|
||||||
|
_ = list.Should().BeEquivalentTo(_testData.Concat(testNumbers));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ConstructorShouldAcceptDuplicates()
|
public void ConstructorShouldAcceptDuplicates()
|
||||||
{
|
{
|
||||||
@@ -300,7 +319,7 @@ namespace Recyclable.CollectionsTests
|
|||||||
{
|
{
|
||||||
// Prepare
|
// Prepare
|
||||||
var testData = _testData.ToArray();
|
var testData = _testData.ToArray();
|
||||||
var list = new RecyclableList<int>();
|
using var list = new RecyclableList<int>();
|
||||||
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -309,8 +328,8 @@ namespace Recyclable.CollectionsTests
|
|||||||
// Validate
|
// Validate
|
||||||
_ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only");
|
_ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only");
|
||||||
_ = list.Should().HaveCount(testData.Length)
|
_ = list.Should().HaveCount(testData.Length)
|
||||||
.And.ContainInConsecutiveOrder(testData)
|
.And.ContainInConsecutiveOrder(testData)
|
||||||
.And.BeEquivalentTo(testData);
|
.And.BeEquivalentTo(testData);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -318,7 +337,7 @@ namespace Recyclable.CollectionsTests
|
|||||||
{
|
{
|
||||||
// Prepare
|
// Prepare
|
||||||
var testData = _testData.ToList();
|
var testData = _testData.ToList();
|
||||||
var list = new RecyclableList<int>();
|
using var list = new RecyclableList<int>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
list.AddRange(testData);
|
list.AddRange(testData);
|
||||||
@@ -330,6 +349,61 @@ namespace Recyclable.CollectionsTests
|
|||||||
.And.BeEquivalentTo(testData);
|
.And.BeEquivalentTo(testData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsRecyclableList()
|
||||||
|
{
|
||||||
|
// Prepare
|
||||||
|
var testData = _testData.ToRecyclableList();
|
||||||
|
using var list = new RecyclableList<int>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.AddRange(testData);
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only");
|
||||||
|
_ = list.Should().HaveCount(testData.Count)
|
||||||
|
.And.ContainInConsecutiveOrder(testData)
|
||||||
|
.And.BeEquivalentTo(testData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddRangeShouldNotOverrideItemsWhenSourceIsArray()
|
||||||
|
{
|
||||||
|
// Prepare
|
||||||
|
var testData = _testData.ToArray();
|
||||||
|
var list = new RecyclableList<int>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.AddRange(testData);
|
||||||
|
list.AddRange(testData);
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
var expectedData = testData.Concat(testData).ToArray();
|
||||||
|
_ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only");
|
||||||
|
_ = list.Should().HaveCount(expectedData.Length)
|
||||||
|
.And.ContainInConsecutiveOrder(expectedData)
|
||||||
|
.And.BeEquivalentTo(expectedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddRangeShouldNotOverrideItemsWhenSourceIsRecyclableList()
|
||||||
|
{
|
||||||
|
// Prepare
|
||||||
|
using var testData = _testData.ToRecyclableList();
|
||||||
|
using var list = new RecyclableList<int>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
list.AddRange(testData);
|
||||||
|
list.AddRange(testData);
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
var expectedData = _testData.Concat(_testData).ToArray();
|
||||||
|
_ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only");
|
||||||
|
_ = list.Should().HaveCount(expectedData.Length)
|
||||||
|
.And.ContainInConsecutiveOrder(expectedData)
|
||||||
|
.And.BeEquivalentTo(expectedData);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InsertAtTheBeginningShouldMoveItems()
|
public void InsertAtTheBeginningShouldMoveItems()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user