RecyclableList.Contains reworked for performance improvements. 69% vs List.Contains

This commit is contained in:
Marek Lemańczyk committed 2023-02-05 17:58:29 +01:00
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]));
}
}
} }
} }
@@ -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]));
}
}
} }
} }
+170 -55
View File
@@ -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()
{ {