RecyclableList performance improvements & fixes. New tests added.

This commit is contained in:
Marek Lemańczyk committed 2023-03-10 16:52:36 +01:00
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;
+138 -23
View File
@@ -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()
{ {