RecyclableList.Contains reworked for performance improvements. 69% vs List.Contains
This commit is contained in:
1 parent
91abf1a614
commit
10ae744875
4 files changed
+245
-59
No files matched your search
@@ -111,5 +111,17 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
var data = TestObjectsAsList;
|
var data = TestObjectsAsList;
|
||||||
DoNothing(data.Count);
|
DoNothing(data.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public void List_Contains()
|
||||||
|
{
|
||||||
|
var data = TestObjects;
|
||||||
|
var list = TestObjectsAsList;
|
||||||
|
var dataCount = 100_000;
|
||||||
|
for (var i = 0; i < dataCount; i++)
|
||||||
|
{
|
||||||
|
DoNothing(list.Contains(data[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
-4
@@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
{
|
{
|
||||||
public partial class RecyclableCollectionsBenchmarks : BenchmarkBase
|
public partial class RecyclableCollectionsBenchmarks : BenchmarkBase
|
||||||
{
|
{
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableList_Create()
|
public void RecyclableList_Create()
|
||||||
{
|
{
|
||||||
using var list = new RecyclableList<object>();
|
using var list = new RecyclableList<object>();
|
||||||
DoNothing(list);
|
DoNothing(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableList_Create_WithCapacity()
|
public void RecyclableList_Create_WithCapacity()
|
||||||
{
|
{
|
||||||
using var list = new RecyclableList<object>(minBlockSize: 102_400, expectedItemsCount: TestObjectCount);
|
using var list = new RecyclableList<object>(minBlockSize: 102_400, expectedItemsCount: TestObjectCount);
|
||||||
DoNothing(list);
|
DoNothing(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableList_Add()
|
public void RecyclableList_Add()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
@@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableList_Add_WithCapacity()
|
public void RecyclableList_Add_WithCapacity()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
@@ -77,5 +77,17 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
var data = _testRecyclableList;
|
var data = _testRecyclableList;
|
||||||
DoNothing(data.LongCount);
|
DoNothing(data.LongCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void RecyclableList_Contains()
|
||||||
|
{
|
||||||
|
var data = TestObjects;
|
||||||
|
var list = TestObjectsAsRecyclableList;
|
||||||
|
var dataCount = 100_000;
|
||||||
|
for (var i = 0; i < dataCount; i++)
|
||||||
|
{
|
||||||
|
DoNothing(list.Contains(data[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,7 +433,7 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_longCount = targetBlockIdx * blockSize + targetItemIdx;
|
_longCount = (targetBlockIdx * blockSize) + targetItemIdx;
|
||||||
_lastBlockIndex = targetBlockIdx;
|
_lastBlockIndex = targetBlockIdx;
|
||||||
_nextItemIndex = targetItemIdx;
|
_nextItemIndex = targetItemIdx;
|
||||||
return;
|
return;
|
||||||
@@ -510,6 +510,7 @@ namespace Recyclable.Collections
|
|||||||
_longCount = 0;
|
_longCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool Contains(T item)
|
public bool Contains(T item)
|
||||||
{
|
{
|
||||||
if (_longCount == 0)
|
if (_longCount == 0)
|
||||||
@@ -517,101 +518,215 @@ namespace Recyclable.Collections
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int midPoint = _blockSize / 2;
|
int blockSize = _blockSize;
|
||||||
int lastBlockIndex = _lastBlockIndex;
|
int itemIndex = 0;
|
||||||
int itemIndex0 = 0;
|
|
||||||
int itemIndex1 = midPoint;
|
|
||||||
int blockIndex = 0;
|
int blockIndex = 0;
|
||||||
|
int lastBlockIndex = 0;
|
||||||
|
|
||||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||||
Span<T> blockArraySpan = new(memoryBlocksSpan[blockIndex]);
|
Span<T> blockArraySpan = new(memoryBlocksSpan[blockIndex]);
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
while (blockIndex <= lastBlockIndex)
|
while (blockIndex < lastBlockIndex)
|
||||||
{
|
{
|
||||||
if (item.Equals(blockArraySpan[itemIndex0++]))
|
if (item.Equals(blockArraySpan[itemIndex]))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Equals(blockArraySpan[itemIndex1++]))
|
if (itemIndex + 1 < blockSize && item.Equals(blockArraySpan[itemIndex + 1]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 2 < blockSize && item.Equals(blockArraySpan[itemIndex + 2]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 3 < blockSize && item.Equals(blockArraySpan[itemIndex + 3]))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemIndex0 == midPoint)
|
if (itemIndex + 4 < blockSize && item.Equals(blockArraySpan[itemIndex + 4]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 5 < blockSize && item.Equals(blockArraySpan[itemIndex + 5]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 6 < blockSize && item.Equals(blockArraySpan[itemIndex + 6]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 7 < blockSize && item.Equals(blockArraySpan[itemIndex + 7]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemIndex += 8;
|
||||||
|
if (itemIndex >= blockSize)
|
||||||
{
|
{
|
||||||
blockIndex++;
|
blockIndex++;
|
||||||
if (blockIndex == lastBlockIndex)
|
|
||||||
{
|
|
||||||
int nextItemIndex = _nextItemIndex;
|
|
||||||
if (nextItemIndex == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
midPoint = (nextItemIndex / 2) + (nextItemIndex % 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
blockArraySpan = new(memoryBlocksSpan[blockIndex]);
|
blockArraySpan = new(memoryBlocksSpan[blockIndex]);
|
||||||
itemIndex0 = 0;
|
itemIndex = 0;
|
||||||
itemIndex1 = midPoint;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockArraySpan = new(memoryBlocksSpan[lastBlockIndex]);
|
||||||
|
var nextItemIndex = _nextItemIndex;
|
||||||
|
while (itemIndex < nextItemIndex)
|
||||||
|
{
|
||||||
|
if (item.Equals(blockArraySpan[itemIndex]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 1 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 1]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 2 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 2]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 3 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 3]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 4 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 4]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 5 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 5]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 6 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 6]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 7 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 7]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemIndex += 8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
while (blockIndex < lastBlockIndex)
|
while (blockIndex < lastBlockIndex)
|
||||||
{
|
{
|
||||||
if (blockArraySpan[itemIndex0++] == null)
|
if (blockArraySpan[itemIndex] == null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockArraySpan[itemIndex1++] == null)
|
if (itemIndex + 1 < blockSize && blockArraySpan[itemIndex + 1] == null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemIndex0 == midPoint)
|
if (itemIndex + 2 < blockSize && blockArraySpan[itemIndex + 2] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 3 < blockSize && blockArraySpan[itemIndex + 3] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 4 < blockSize && blockArraySpan[itemIndex + 4] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 5 < blockSize && blockArraySpan[itemIndex + 5] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 6 < blockSize && blockArraySpan[itemIndex + 6] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 7 < blockSize && blockArraySpan[itemIndex + 7] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemIndex += 8;
|
||||||
|
if (itemIndex >= blockSize)
|
||||||
{
|
{
|
||||||
blockIndex++;
|
blockIndex++;
|
||||||
if (blockIndex == lastBlockIndex)
|
|
||||||
{
|
|
||||||
if (_nextItemIndex == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
midPoint = ((_nextItemIndex - 1) / 2) + ((_nextItemIndex - 1) % 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
blockArraySpan = new(memoryBlocksSpan[blockIndex]);
|
blockArraySpan = new(memoryBlocksSpan[blockIndex]);
|
||||||
itemIndex0 = 0;
|
itemIndex = 0;
|
||||||
itemIndex1 = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockArraySpan = new(memoryBlocksSpan[lastBlockIndex]);
|
||||||
|
var nextItemIndex = _nextItemIndex;
|
||||||
|
while (itemIndex < nextItemIndex)
|
||||||
|
{
|
||||||
|
if (blockArraySpan[itemIndex] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 1 < nextItemIndex && blockArraySpan[itemIndex + 1] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 2 < nextItemIndex && blockArraySpan[itemIndex + 2] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 3 < nextItemIndex && blockArraySpan[itemIndex + 3] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 4 < nextItemIndex && blockArraySpan[itemIndex + 4] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 5 < nextItemIndex && blockArraySpan[itemIndex + 5] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 6 < nextItemIndex && blockArraySpan[itemIndex + 6] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex + 7 < nextItemIndex && blockArraySpan[itemIndex + 7] == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemIndex += 8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
//for (memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++)
|
|
||||||
//{
|
|
||||||
// //T? found = Array.Find(memoryBlocksSpan[memoryBlockIndex], x => comparer.Equals(x, item));
|
|
||||||
// //if (found != null)
|
|
||||||
// //{
|
|
||||||
// // return true;
|
|
||||||
// //}
|
|
||||||
|
|
||||||
// blockArraySpan = new(memoryBlocksSpan[memoryBlockIndex]);
|
|
||||||
// for (itemIndex = 0; itemIndex < mainBlockSize; itemIndex++)
|
|
||||||
// {
|
|
||||||
// if (comparer.Equals(blockArraySpan[itemIndex], item))
|
|
||||||
// {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex);
|
public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex);
|
||||||
|
|||||||
@@ -85,6 +85,53 @@ namespace Recyclable.CollectionsTests
|
|||||||
_ = list.LongCount.Should().Be(0);
|
_ = list.LongCount.Should().Be(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContainsShouldFindAllItems()
|
||||||
|
{
|
||||||
|
// Prepare
|
||||||
|
var testData = _testData;
|
||||||
|
using var list = NewRecyclableList;
|
||||||
|
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
foreach (var item in testData)
|
||||||
|
{
|
||||||
|
// Validate
|
||||||
|
_ = list.Contains(item).Should().BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContainsShouldNotFindNonExistingItems()
|
||||||
|
{
|
||||||
|
// Prepare
|
||||||
|
var testData = _testData;
|
||||||
|
using var list = NewRecyclableList;
|
||||||
|
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
foreach (var item in testData)
|
||||||
|
{
|
||||||
|
// Validate
|
||||||
|
_ = list.Contains(-item).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_ = list.Contains(0).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContainsShouldNotFindAnythingWhenListEmpty()
|
||||||
|
{
|
||||||
|
// Prepare
|
||||||
|
using var list = new RecyclableList<int>();
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
_ = list.Contains(0).Should().BeFalse();
|
||||||
|
_ = list.Contains(1).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CopyToShouldCopyAllItems()
|
public void CopyToShouldCopyAllItems()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user