From 7c78b6f87ed4191175a5cf0db3dda696b30b83bc Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 22 Jan 2023 23:13:00 +0100 Subject: [PATCH 01/57] RecyclableArrayList variables names standardized a little with RecyclableList. --- Recyclable.Collections/RecyclableArrayList.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index b811fbd..4d8a23b 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -247,41 +247,41 @@ namespace Recyclable.Collections return; } - int capacity = _capacity; - int targetIndex = _count; + int targetItemIdx = _count; Span memorySpan; if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { - _ = EnsureCapacity(targetIndex + requiredAdditionalCapacity); + _ = EnsureCapacity(targetItemIdx + requiredAdditionalCapacity); memorySpan = new(_memoryBlock); foreach (var item in source) { - memorySpan[targetIndex++] = item; + memorySpan[targetItemIdx++] = item; } - _count = targetIndex; + _count = targetItemIdx; return; } int i; using var enumerator = source.GetEnumerator(); + int capacity = _capacity; memorySpan = new(_memoryBlock); if (enumerator.MoveNext()) { - int available = capacity - targetIndex; + int available = capacity - targetItemIdx; while (true) { - if (targetIndex + growByCount > capacity) + if (targetItemIdx + growByCount > capacity) { capacity = EnsureCapacity(capacity + growByCount); memorySpan = new(_memoryBlock); - available = capacity - targetIndex; + available = capacity - targetItemIdx; } for (i = 0; i < available; i++) { - memorySpan[targetIndex++] = enumerator.Current; + memorySpan[targetItemIdx++] = enumerator.Current; if (!enumerator.MoveNext()) { break; @@ -295,7 +295,7 @@ namespace Recyclable.Collections } } - _count = targetIndex; + _count = targetItemIdx; } public void Clear() From f94e91c45d8b6b200cbd26332acaeef0f7486916 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 22 Jan 2023 23:16:21 +0100 Subject: [PATCH 02/57] RecyclableList.AddRange(IEnumerable) made similar in behavior / optimizations to RecyclableArrayList. Variables named made similar. --- Recyclable.Collections/RecyclableList.cs | 193 +++++++++++++++++++---- 1 file changed, 163 insertions(+), 30 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 7d098d3..9e3e4dc 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -11,6 +11,7 @@ namespace Recyclable.Collections private static readonly ArrayPool _blockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyBlockArray = new T[0][]; private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + private readonly int _blockSize; protected T[][] _memoryBlocks; @@ -57,36 +58,47 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, in int blockSize, in long newSize) + protected static T[][] SetNewLength(in T[][]? source, in int blockSize, in long newCapacity) { ArrayPool arrayPool = _arrayPool; ArrayPool blockArrayPool = _blockArrayPool; var sourceBlockCount = source?.Length ?? 0; - int requiredBlockCount = (int)(newSize / blockSize) + (newSize % blockSize > 0 ? 1 : 0); + int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); if (requiredBlockCount == sourceBlockCount) { return source!; } + //else if (requiredBlockCount < sourceBlockCount) + //{ + // requiredBlockCount = requiredBlockCount > 0 ? requiredBlockCount : 1; + // while (requiredBlockCount < sourceBlockCount) + // { + // requiredBlockCount *= 2; + // } + //} Span newArraySpan; T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool); switch (requiredBlockCount >= sourceBlockCount) { case true: - if (sourceBlockCount > 0) + switch (sourceBlockCount > 0) { - Memory sourceMemory = new(source); - Memory newArrayMemory = new(newMemoryBlocks); - sourceMemory.CopyTo(newArrayMemory); - source!.ReturnToPool(arrayPool); - newArraySpan = new Span(newMemoryBlocks)[sourceBlockCount..]; - } - else - { - newArraySpan = new Span(newMemoryBlocks); + case true: + Memory sourceMemory = new(source); + Memory newArrayMemory = new(newMemoryBlocks); + sourceMemory.CopyTo(newArrayMemory); + source!.ReturnToPool(arrayPool); + newArraySpan = new Span(newMemoryBlocks)[sourceBlockCount..]; + break; + + case false: + newArraySpan = new Span(newMemoryBlocks); + break; } - for (int i = 0; i < newArraySpan.Length; i++) + int uninitializedBlocksCount = newArraySpan.Length; + for (int i = 0; i < uninitializedBlocksCount; i++) { newArraySpan[i] = blockSize.RentArrayFromPool(blockArrayPool); } @@ -98,9 +110,10 @@ namespace Recyclable.Collections newArraySpan = new Span(newMemoryBlocks); sourceSpan.CopyTo(newArraySpan); sourceSpan = new Span(source)[newArraySpan.Length..]; - for (int i = 0; i < sourceSpan.Length; i++) + int sourceLength = sourceSpan.Length; + for (int i = 0; i < sourceLength; i++) { - sourceSpan[i].ReturnToPool(_blockArrayPool); + sourceSpan[i].ReturnToPool(blockArrayPool); } source!.ReturnToPool(arrayPool); @@ -174,7 +187,7 @@ namespace Recyclable.Collections public T this[long index] { get => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize]; - set => new Span(_memoryBlocks[(int)(index / _blockSize)])[(int)index % _blockSize] = value; + set => new Span(_memoryBlocks[(int)(index / _blockSize)])[(int)(index % _blockSize)] = value; } public T this[int index] @@ -193,7 +206,7 @@ namespace Recyclable.Collections _ = EnsureCapacity(requiredCapacity); } - _memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item; + _memoryBlocks[(int)(oldCount / blockSize)][(int)(oldCount % blockSize)] = item; _longCount++; } @@ -210,15 +223,21 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlockSpan = new Span(_memoryBlocks); - var targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); - for (long i = 0; i < sourceItemsCount; i++) + var memoryBlocksSpan = new Span(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); + for (var i = 0L; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; if (targetItemIdx == blockSize) { targetBlockIdx++; - targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); targetItemIdx = 0; } } @@ -239,15 +258,21 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlockSpan = new Span(_memoryBlocks); - var targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + var memoryBlocksSpan = new Span(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); for (int i = 0; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; if (targetItemIdx == blockSize) { targetBlockIdx++; - targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); targetItemIdx = 0; } } @@ -268,15 +293,21 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlockSpan = new Span(_memoryBlocks); - var targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + var memoryBlocksSpan = new Span(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); for (int i = 0; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; if (targetItemIdx == blockSize) { targetBlockIdx++; - targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); targetItemIdx = 0; } } @@ -284,12 +315,114 @@ namespace Recyclable.Collections _longCount = targetCapacity; } - private static void AddRange(RecyclableList destination, IEnumerable source) + private void AddRange(RecyclableList destination, IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { - foreach (var item in source) + if (source is T[] sourceArray) { - destination.Add(item); + AddRange(sourceArray); + return; } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + long capacity = _capacity; + long oldLongCount = _longCount; + int blockSize = _blockSize; + int targetItemIdx = (int)(oldLongCount % blockSize); + int targetBlockIdx = (int)(targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0); + + Span memoryBlocksSpan; + Span blockArraySpan; + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + _ = EnsureCapacity(targetItemIdx + requiredAdditionalCapacity); + memoryBlocksSpan = new(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx]); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetItemIdx = 0; + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx]); + } + } + + _longCount = targetItemIdx; + return; + } + + long i; + using var enumerator = source.GetEnumerator(); + + if (enumerator.MoveNext()) + { + long available = capacity - targetItemIdx; + memoryBlocksSpan = new(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (targetItemIdx + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memoryBlocksSpan = new(_memoryBlocks); + memoryBlocksCount = memoryBlocksSpan.Length; + available = capacity - targetItemIdx; + } + + blockArraySpan = memoryBlocksSpan[targetBlockIdx]; + for (i = 0; i < available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetItemIdx = 0; + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx]); + } + } + + if (i < available) + { + break; + } + } + } + + _longCount = targetItemIdx; } public void Clear() From 7aec77e674316f79b4fd470eed3d2dc7b3b15296 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 22 Jan 2023 23:17:02 +0100 Subject: [PATCH 03/57] Recyclable list initializations fixed to pass the no. of items, not block size. --- .../RecyclableCollectionsBenchmarks.RecyclableList.cs | 2 +- .../RecyclableCollectionsBenchmarks.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index e253cd5..00d836b 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -35,7 +35,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(dataCount); + using var list = new RecyclableList(initialCapacity: dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs index 9ab8cd9..2dc5bec 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs @@ -18,8 +18,8 @@ namespace Recyclable.Collections.Benchmarks { Console.WriteLine("******* GLOBAL SETUP RAISED *******"); _testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); - _testRecyclableArrayList = new(TestObjects, TestObjectCount); - _testRecyclableList = new(TestObjects, TestObjectCount); + _testRecyclableArrayList = new(TestObjects, initialCapacity: TestObjectCount); + _testRecyclableList = new(TestObjects, expectedItemsCount: TestObjectCount); _testArray = TestObjects.ToArray(); _testObjectsAsList = TestObjects.ToList(); _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); From 1691f07fbeb133d45740908cfbcabe55318f0225 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 22 Jan 2023 23:45:54 +0100 Subject: [PATCH 04/57] RecyclableList.AddRange will only call EnsureCapacity, if there is a need for it. Small source code cleanup. --- Recyclable.Collections/RecyclableList.cs | 45 +++++------------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 9e3e4dc..0765cb4 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,5 +1,4 @@ -using System; -using System.Buffers; +using System.Buffers; using System.Collections; using System.Runtime.CompilerServices; @@ -68,14 +67,6 @@ namespace Recyclable.Collections { return source!; } - //else if (requiredBlockCount < sourceBlockCount) - //{ - // requiredBlockCount = requiredBlockCount > 0 ? requiredBlockCount : 1; - // while (requiredBlockCount < sourceBlockCount) - // { - // requiredBlockCount *= 2; - // } - //} Span newArraySpan; T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool); @@ -150,9 +141,7 @@ namespace Recyclable.Collections return newCapacity; } -#pragma warning disable CS8618 // _memoryBlocks will be initialized when the 1st item is added public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) -#pragma warning restore CS8618 { _blockSize = blockSize; if (initialCapacity > 0) @@ -166,9 +155,7 @@ namespace Recyclable.Collections } } -#pragma warning disable CS8618 // _memoryBlocks will be initialized when the 1st item is added public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) -#pragma warning restore CS8618 { _blockSize = blockSize; if (expectedItemsCount > 0) @@ -351,7 +338,13 @@ namespace Recyclable.Collections Span blockArraySpan; if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { - _ = EnsureCapacity(targetItemIdx + requiredAdditionalCapacity); + + int requiredCapacity = targetItemIdx + requiredAdditionalCapacity; + if (capacity < requiredCapacity) + { + _ = EnsureCapacity(requiredCapacity); + } + memoryBlocksSpan = new(_memoryBlocks); var memoryBlocksCount = memoryBlocksSpan.Length; blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx]); @@ -480,28 +473,6 @@ namespace Recyclable.Collections } } - //private static RecyclableArrayList SetupMemoryBlocks(RecyclableList owner, int blockSize, long? totalItemsCount) - //{ - // if (blockSize <= 0) - // { - // return new(); - // } - - // totalItemsCount ??= 8 * blockSize; - // int additionalArray = totalItemsCount % blockSize > 0 ? 1 : 0; - // int memoryBlockCount = (int)(totalItemsCount.Value / blockSize).LimitTo(int.MaxValue) + additionalArray; - // RecyclableArrayList memoryBlocks = new(memoryBlockCount); - // for (var blockIdx = 0; blockIdx < memoryBlockCount; blockIdx++) - // { - // memoryBlocks[blockIdx] = RentArrayFromPool(blockSize); - // } - - // memoryBlocks.Count = memoryBlockCount; - // owner.Capacity = totalItemsCount.Value; - // return memoryBlocks; - //} - - public void Dispose() { if (_capacity > 0) From cbac777e439e237baffdd622e716e810ab4cd1b8 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 22 Jan 2023 23:46:20 +0100 Subject: [PATCH 05/57] Using cleanup --- .../RecyclableCollectionsBenchmarks.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs index 2dc5bec..8aabc86 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs @@ -1,9 +1,4 @@ using BenchmarkDotNet.Attributes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Recyclable.Collections.Benchmarks { From 64be78d1c6330ed73fc6dd5d7c20dee3464879b2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 23 Jan 2023 00:15:25 +0100 Subject: [PATCH 06/57] Benchmark list updated for current runs. --- Recyclable.Collections.Benchmarks/Program.cs | 36 +++++++++++++++---- ...llectionsBenchmarks.RecyclableArrayList.cs | 8 ++--- ...bleCollectionsBenchmarks.RecyclableList.cs | 4 +-- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index a25e11f..882322a 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -1,16 +1,38 @@ -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); -//BenchmarkRunner.Run(); +BenchmarkRunner.Run( + ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator) +); + + //BenchmarkRunner.Run(); -var benchmark = new RecyclableCollectionsBenchmarks(); -benchmark.Setup(); -//benchmark.RecyclableArrayList_AddRange_WithCapacity(); -//benchmark.RecyclableArrayList_Add_WithCapacity(); -benchmark.RecyclableList_Add_WithCapacity(); +//var benchmark = new RecyclableCollectionsBenchmarks(); +//benchmark.Setup(); +//foreach (var _ in Enumerable.Range(1, 1000)) +//{ +// //benchmark.RecyclableArrayList_AddRange_WithCapacity(); +// //benchmark.RecyclableArrayList_Add_WithCapacity(); + +// //benchmark.RecyclableList_Create(); +// benchmark.RecyclableList_Create_WithCapacity(); +// //benchmark.RecyclableList_Add(); +// //benchmark.RecyclableList_Add_WithCapacity(); +//} + +//benchmark.Cleanup(); + +// benchmark.Setup(); +// benchmark.Cleanup(); + +//benchmark.Setup(); +//benchmark.Cleanup(); + +//} //BenchmarkRunner.Run(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 49945f1..e970042 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Create() { using var list = new RecyclableArrayList(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Create_WithCapacity() { using var list = new RecyclableArrayList(TestObjectCount); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 00d836b..4e63eaf 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -14,7 +14,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void RecyclableList_Create_WithCapacity() { - using var list = new RecyclableList(initialCapacity: TestObjectCount); + using var list = new RecyclableList(blockSize: 102_400, initialCapacity: TestObjectCount); DoNothing(list); } @@ -35,7 +35,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(initialCapacity: dataCount); + using var list = new RecyclableList(blockSize: 102_400, initialCapacity: dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); From 30dfd973367a0d63c788839461cb7ad06e2e6c95 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 23 Jan 2023 00:16:02 +0100 Subject: [PATCH 07/57] ReleaseBlock method removed. --- Recyclable.Collections/RecyclableList.cs | 55 ++++++++---------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 0765cb4..6dd787a 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -47,7 +47,8 @@ namespace Recyclable.Collections if ((list._capacity * blockSize) - oldCountMinus1 == blockSize) { T[][] memoryBlocks = list._memoryBlocks; - ReleaseBlock(list, memoryBlocks, blockSize, memoryBlocks.Length - 1); + memoryBlocks[^1].ReturnToPool(_blockArrayPool); + list._capacity -= blockSize; } } @@ -168,7 +169,7 @@ namespace Recyclable.Collections _memoryBlocks = _emptyBlockArray; } - AddRange(this, source); + AddRange(source); } public T this[long index] @@ -302,7 +303,7 @@ namespace Recyclable.Collections _longCount = targetCapacity; } - private void AddRange(RecyclableList destination, IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + private void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { if (source is T[] sourceArray) { @@ -420,29 +421,16 @@ namespace Recyclable.Collections public void Clear() { - try + ArrayPool blockArrayPool = _blockArrayPool; + Span memoryBlocksSpan = new(_memoryBlocks); + int memoryBlocksCount = memoryBlocksSpan.Length; + for (var toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) { - // Remove in reversed order for performance savings - var toRemoveIdx = _memoryBlocks.Length - 1; - while (toRemoveIdx >= 0) - { - try - { - ReleaseBlock(this, _memoryBlocks, _blockSize, toRemoveIdx); - } - catch (Exception) - { - // We want to try returning as many arrays, as possible, before - // the list is cleared. - } + memoryBlocksSpan[toRemoveIdx].ReturnToPool(blockArrayPool); + } - toRemoveIdx--; - } - } - finally - { - _longCount = 0; - } + _capacity = 0; + _longCount = 0; } public bool Contains(T item) => _memoryBlocks.Any(x => x.Contains(item)); @@ -455,24 +443,17 @@ namespace Recyclable.Collections public void Insert(int index, T item) => throw new NotSupportedException(); public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer); public bool Remove(T item) => throw new NotSupportedException(); - public void RemoveBlock(int index) => ReleaseBlock(this, _memoryBlocks, _blockSize, index); + public void RemoveBlock(int index) + { + _memoryBlocks[index].ReturnToPool(_blockArrayPool); + _capacity -= _blockSize; + } + public void RemoveAt(int index) => RemoveAt(this, index); public void RemoveAt(long index) => RemoveAt(this, index); IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - private static void ReleaseBlock(RecyclableList owner, in T[][] memoryBlocks, int blockSize, int blockIndex) - { - try - { - memoryBlocks[blockIndex].ReturnToPool(_blockArrayPool); - } - finally - { - owner._capacity -= blockSize; - } - } - public void Dispose() { if (_capacity > 0) From f45b6aac9819d73a282d528812c90af27fcc4f22 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 23 Jan 2023 01:22:41 +0100 Subject: [PATCH 08/57] RecyclableList performance optimizations. --- Recyclable.Collections/RecyclableList.cs | 52 ++++++++++++++++++------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 6dd787a..0738e36 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -11,7 +11,7 @@ namespace Recyclable.Collections private static readonly T[][] _emptyBlockArray = new T[0][]; private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; - private readonly int _blockSize; + private int _blockSize; protected T[][] _memoryBlocks; private long _capacity; @@ -58,7 +58,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, in int blockSize, in long newCapacity) + protected static T[][] SetNewLength(in T[][]? source, int blockSize, in long newCapacity) { ArrayPool arrayPool = _arrayPool; ArrayPool blockArrayPool = _blockArrayPool; @@ -90,6 +90,14 @@ namespace Recyclable.Collections } int uninitializedBlocksCount = newArraySpan.Length; + if (uninitializedBlocksCount == requiredBlockCount) + { + newArraySpan[0] = blockSize.RentArrayFromPool(blockArrayPool); + blockSize = newArraySpan[0].Length; + newArraySpan = newArraySpan[1..]; + uninitializedBlocksCount--; + } + for (int i = 0; i < uninitializedBlocksCount; i++) { newArraySpan[i] = blockSize.RentArrayFromPool(blockArrayPool); @@ -117,8 +125,8 @@ namespace Recyclable.Collections { long oldCapacity = _capacity; ref T[][] memory = ref _memoryBlocks; - long newCapacity; + switch (oldCapacity > 0) { case true: @@ -137,6 +145,12 @@ namespace Recyclable.Collections int blockSize = _blockSize; memory = SetNewLength(memory, blockSize, newCapacity); + if (oldCapacity == 0 && newCapacity > 0) + { + blockSize = memory[0].Length; + _blockSize = blockSize; + } + newCapacity = memory.Length * blockSize; _capacity = newCapacity; return newCapacity; @@ -144,28 +158,42 @@ namespace Recyclable.Collections public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) { - _blockSize = blockSize; - if (initialCapacity > 0) + switch (initialCapacity > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, initialCapacity.Value); - _capacity = _memoryBlocks.Length * blockSize; - } - else - { - _memoryBlocks = _emptyBlockArray; + case true: + _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, initialCapacity.Value); + if (_memoryBlocks.Length > 0) + { + blockSize = _memoryBlocks[0].Length; + } + + _blockSize = blockSize; + _capacity = _memoryBlocks.Length * blockSize; + break; + + case false: + _blockSize = blockSize; + _memoryBlocks = _emptyBlockArray; + break; } } public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { - _blockSize = blockSize; if (expectedItemsCount > 0) { _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + if (_memoryBlocks.Length > 0) + { + blockSize = _memoryBlocks[0].Length; + } + + _blockSize = blockSize; _capacity = _memoryBlocks.Length * blockSize; } else { + _blockSize = blockSize; _memoryBlocks = _emptyBlockArray; } From 43c2c6f13a573f1e3941364cf56f312ce378ef2e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 23 Jan 2023 01:40:11 +0100 Subject: [PATCH 09/57] if converted to switch for performance improvements. --- Recyclable.Collections/RecyclableList.cs | 28 +++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 0738e36..e61bad4 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -180,21 +180,23 @@ namespace Recyclable.Collections public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { - if (expectedItemsCount > 0) + switch (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); - if (_memoryBlocks.Length > 0) - { - blockSize = _memoryBlocks[0].Length; - } + case true: + _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + if (_memoryBlocks.Length > 0) + { + blockSize = _memoryBlocks[0].Length; + } - _blockSize = blockSize; - _capacity = _memoryBlocks.Length * blockSize; - } - else - { - _blockSize = blockSize; - _memoryBlocks = _emptyBlockArray; + _blockSize = blockSize; + _capacity = _memoryBlocks.Length * blockSize; + break; + + case false: + _blockSize = blockSize; + _memoryBlocks = _emptyBlockArray; + break; } AddRange(source); From 9db490d8e9fed454f6ecaf8e51cbb5f7a0918761 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 24 Jan 2023 02:10:26 +0100 Subject: [PATCH 10/57] Fixed problem with RecyclableList.RemoveAt returning to the pool always the same block --- Recyclable.Collections/RecyclableList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index e61bad4..e12fff9 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -47,7 +47,7 @@ namespace Recyclable.Collections if ((list._capacity * blockSize) - oldCountMinus1 == blockSize) { T[][] memoryBlocks = list._memoryBlocks; - memoryBlocks[^1].ReturnToPool(_blockArrayPool); + memoryBlocks[list.BlockCount - 1].ReturnToPool(_blockArrayPool); list._capacity -= blockSize; } } From 032908bc7dc01d3a6ff7b534fcc000092e3fbd2a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 24 Jan 2023 22:41:43 +0100 Subject: [PATCH 11/57] _lastBlockIndex & _lastItemIndex fields added for performance gain. Tiny cleanup. --- Recyclable.Collections/RecyclableList.cs | 52 ++++++++++++++++++++---- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index e12fff9..efe009f 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -12,6 +12,8 @@ namespace Recyclable.Collections private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private int _blockSize; + private int _lastBlockIndex; + private int _lastItemIndex; protected T[][] _memoryBlocks; private long _capacity; @@ -35,14 +37,25 @@ namespace Recyclable.Collections private static void RemoveAt(RecyclableList list, long index) { - long oldCount = list._longCount; - long oldCountMinus1 = oldCount - 1; + long oldCountMinus1 = list._longCount - 1; if (index != oldCountMinus1) { ThrowArgumentOutOfRangeException(); } list._longCount--; + switch (list._lastItemIndex) + { + case 0: + list._lastBlockIndex--; + list._lastItemIndex = list._blockSize; + break; + + default: + list._lastItemIndex--; + break; + } + int blockSize = list._blockSize; if ((list._capacity * blockSize) - oldCountMinus1 == blockSize) { @@ -216,15 +229,19 @@ namespace Recyclable.Collections public void Add(T item) { - int blockSize = _blockSize; - long oldCount = _longCount; - long requiredCapacity = oldCount + 1; + long requiredCapacity = _longCount + 1; if (_capacity < requiredCapacity) { _ = EnsureCapacity(requiredCapacity); } - _memoryBlocks[(int)(oldCount / blockSize)][(int)(oldCount % blockSize)] = item; + _memoryBlocks[_lastBlockIndex][_lastItemIndex++] = item; + if (_lastItemIndex == _blockSize) + { + _lastBlockIndex++; + _lastItemIndex = 0; + } + _longCount++; } @@ -261,6 +278,8 @@ namespace Recyclable.Collections } _longCount = targetCapacity; + _lastBlockIndex = targetBlockIdx - 1; + _lastItemIndex = targetItemIdx; } public void AddRange(in List items) @@ -296,6 +315,8 @@ namespace Recyclable.Collections } _longCount = targetCapacity; + _lastBlockIndex = targetBlockIdx - 1; + _lastItemIndex = targetItemIdx; } public void AddRange(in IList items) @@ -331,6 +352,8 @@ namespace Recyclable.Collections } _longCount = targetCapacity; + _lastBlockIndex = targetBlockIdx - 1; + _lastItemIndex = targetItemIdx; } private void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) @@ -363,7 +386,7 @@ namespace Recyclable.Collections long oldLongCount = _longCount; int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); - int targetBlockIdx = (int)(targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0); + int targetBlockIdx = targetItemIdx / blockSize + (targetItemIdx > 0 ? 1 : 0); Span memoryBlocksSpan; Span blockArraySpan; @@ -396,6 +419,8 @@ namespace Recyclable.Collections } _longCount = targetItemIdx; + _lastBlockIndex = targetBlockIdx - 1; + _lastItemIndex = targetItemIdx; return; } @@ -447,6 +472,8 @@ namespace Recyclable.Collections } _longCount = targetItemIdx; + _lastBlockIndex = targetBlockIdx - 1; + _lastItemIndex = targetItemIdx; } public void Clear() @@ -460,6 +487,8 @@ namespace Recyclable.Collections } _capacity = 0; + _lastBlockIndex = 0; + _lastItemIndex = 0; _longCount = 0; } @@ -477,6 +506,15 @@ namespace Recyclable.Collections { _memoryBlocks[index].ReturnToPool(_blockArrayPool); _capacity -= _blockSize; + int lastBlockIndex = _lastBlockIndex; + if (lastBlockIndex == index) + { + _lastItemIndex = 0; + } + else if (lastBlockIndex > index) + { + _lastBlockIndex--; + } } public void RemoveAt(int index) => RemoveAt(this, index); From f9b2d82629777ea2f08e74f577aa675bc634de90 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 25 Jan 2023 22:48:37 +0100 Subject: [PATCH 12/57] RecyclableList.Clear, .RemoveAt, .RemoveBlock, .SetNewLength, Dispose performance improvements. --- Recyclable.Collections/RecyclableList.cs | 105 ++++++++++++++++++----- 1 file changed, 82 insertions(+), 23 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index efe009f..56588e4 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -8,7 +8,8 @@ namespace Recyclable.Collections { private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly ArrayPool _blockArrayPool = ArrayPool.Create(); - private static readonly T[][] _emptyBlockArray = new T[0][]; + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private int _blockSize; @@ -60,7 +61,11 @@ namespace Recyclable.Collections if ((list._capacity * blockSize) - oldCountMinus1 == blockSize) { T[][] memoryBlocks = list._memoryBlocks; - memoryBlocks[list.BlockCount - 1].ReturnToPool(_blockArrayPool); + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(memoryBlocks[list.BlockCount - 1]); + } + list._capacity -= blockSize; } } @@ -83,7 +88,10 @@ namespace Recyclable.Collections } Span newArraySpan; - T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool); + T[][] newMemoryBlocks = requiredBlockCount > RecyclableDefaults.MinPooledArrayLength + ? arrayPool.Rent(requiredBlockCount) + : new T[requiredBlockCount][]; + switch (requiredBlockCount >= sourceBlockCount) { case true: @@ -93,7 +101,11 @@ namespace Recyclable.Collections Memory sourceMemory = new(source); Memory newArrayMemory = new(newMemoryBlocks); sourceMemory.CopyTo(newArrayMemory); - source!.ReturnToPool(arrayPool); + if (sourceMemory.Length >= RecyclableDefaults.MinPooledArrayLength) + { + arrayPool.Return(source!); + } + newArraySpan = new Span(newMemoryBlocks)[sourceBlockCount..]; break; @@ -105,15 +117,39 @@ namespace Recyclable.Collections int uninitializedBlocksCount = newArraySpan.Length; if (uninitializedBlocksCount == requiredBlockCount) { - newArraySpan[0] = blockSize.RentArrayFromPool(blockArrayPool); - blockSize = newArraySpan[0].Length; + switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + case true: + newArraySpan[0] = blockArrayPool.Rent(blockSize); + blockSize = newArraySpan[0].Length; + break; + + case false: + newArraySpan[0] = new T[blockSize]; + break; + } + newArraySpan = newArraySpan[1..]; uninitializedBlocksCount--; } - for (int i = 0; i < uninitializedBlocksCount; i++) + switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) { - newArraySpan[i] = blockSize.RentArrayFromPool(blockArrayPool); + case true: + for (int i = 0; i < uninitializedBlocksCount; i++) + { + newArraySpan[i] = blockArrayPool.Rent(blockSize); + } + + break; + + case false: + for (int i = 0; i < uninitializedBlocksCount; i++) + { + newArraySpan[i] = new T[blockSize]; + } + + break; } return newMemoryBlocks; @@ -124,12 +160,19 @@ namespace Recyclable.Collections sourceSpan.CopyTo(newArraySpan); sourceSpan = new Span(source)[newArraySpan.Length..]; int sourceLength = sourceSpan.Length; - for (int i = 0; i < sourceLength; i++) + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) { - sourceSpan[i].ReturnToPool(blockArrayPool); + for (int i = 0; i < sourceLength; i++) + { + blockArrayPool.Return(sourceSpan[i]); + } + } + + if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) + { + arrayPool.Return(source!); } - source!.ReturnToPool(arrayPool); return newMemoryBlocks; } } @@ -186,7 +229,7 @@ namespace Recyclable.Collections case false: _blockSize = blockSize; - _memoryBlocks = _emptyBlockArray; + _memoryBlocks = _emptyMemoryBlocksArray; break; } } @@ -208,7 +251,7 @@ namespace Recyclable.Collections case false: _blockSize = blockSize; - _memoryBlocks = _emptyBlockArray; + _memoryBlocks = _emptyMemoryBlocksArray; break; } @@ -478,12 +521,15 @@ namespace Recyclable.Collections public void Clear() { - ArrayPool blockArrayPool = _blockArrayPool; - Span memoryBlocksSpan = new(_memoryBlocks); - int memoryBlocksCount = memoryBlocksSpan.Length; - for (var toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { - memoryBlocksSpan[toRemoveIdx].ReturnToPool(blockArrayPool); + Span memoryBlocksSpan = new(_memoryBlocks); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (var toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx]); + } } _capacity = 0; @@ -504,14 +550,23 @@ namespace Recyclable.Collections public bool Remove(T item) => throw new NotSupportedException(); public void RemoveBlock(int index) { - _memoryBlocks[index].ReturnToPool(_blockArrayPool); + switch (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + case true: + _blockArrayPool.Return(_memoryBlocks[index]); + break; + + case false: + _memoryBlocks[index] = _emptyBlockArray; + break; + } + _capacity -= _blockSize; - int lastBlockIndex = _lastBlockIndex; - if (lastBlockIndex == index) + if (_lastBlockIndex == index) { _lastItemIndex = 0; } - else if (lastBlockIndex > index) + else if (_lastBlockIndex > index) { _lastBlockIndex--; } @@ -528,7 +583,11 @@ namespace Recyclable.Collections { Clear(); _capacity = 0; - _memoryBlocks.ReturnToPool(_arrayPool); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _arrayPool.Return(_memoryBlocks); + } + GC.SuppressFinalize(this); } } From c283cb562cbbf160e47d61d24d078172565c5402 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 27 Jan 2023 22:31:42 +0100 Subject: [PATCH 13/57] Read.me extended & updated. --- README.md | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 973068b..cad1ada 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,12 @@ This is the direct equivalent of `List` class, except that the arrays are tak The architecture of the classes included in this project is driven primarily by performance & memory footprints. The following decisions were made to support it. ## Code duplication -There is high level of code duplication in each of the classes. When you look at them, you will see they are alike & some of the code could be extracted to extension methods. It has been done & has proved to perform worse. For this reason, properties & methods expected much of the code is duplicated to get the most out of it. +There is high level of code duplication in each of the classes. When you look at them, you will see they are alike & some of the code could be extracted to extension methods. It has been done & has proven to perform worse. For this reason, properties & methods expected to be on the hot-path, duplicate much of the code to get the most out of it. -## Common base `ILongList` -Having a common base class for all the classes looks very attractive. But it is also proved to perform worse, if you start making properties & methods virtual & there are subsequent calls to `base`. For this reason, each of the classes is the root class & there are hardly any virtual methods & properties. All classes are meant to be `final`, but I've not marked them as `sealed`. If you feel that you'd benefit from it, you're welcomed to inherit from them. +## Common base - `ILongList` +Having a common base class for all the classes looks very attractive. But it is also proved to perform worse, if you start making properties & methods virtual & there are subsequent calls to `base`. For this reason, each of the classes is the root class & there are hardly any virtual methods & properties. All classes are meant to be `final`, but I've not marked them as `sealed`. If you feel that you'd benefit from it, you're welcomed to inherit from them. The protected stuff is there for your use. -If you desire to have a common base for calls, each of the classes implements `IList` & `ILongList` interfaces. `ILongList` is a new interface, made alike `IList`, but unlocking full support for `long` indexing & use. Considering the characteristics of the classes I feel that the generic name is justified, instead of `IRecyclableList` what you could expect. +If you desire to have a common base for calls, each of the classes implements `IList` & `ILongList` interfaces. `ILongList` is a new interface, made alike `IList`, but unlocking full support for `long` indexing & use. Considering the characteristics of the classes I feel that the generic name is justified, instead of `IRecyclableList` what you could expect. ## Protection against inccorrect use & state Having protection against invalid state of the object is an important part of software engineering. The classes included in this project provide the same level of protection as the regular `array` & `List` class. They will raise exceptions when invalid index is given. But there is no additional protection that the content & state of the objects internally used by these classes are valid. You can break them, if you want to. @@ -62,19 +62,32 @@ This is by design, for the best performance. Initially there have been checks in If you foresee the overflowing as an issue, you're welcomed to propose an improvement & file a PR with proposed solution. +# Auto-properties & new language features +When you look at the code you may note, that it looks like in early c# days. Where the new language features yield the same or better performance, they are & will be used. + +However, many of the new language features have proved to provide worse performance at some point in my testing. It includes auto-properties, for which I could see getters & setters generated in IL, instead of fields. Because all the classes are expected to be on the hot-paths, I've eliminated most of them. + +If you know & can show it in your benchmarks that the code can be simplified without impacting the performance, feel free to file a PR with proposed changes. Please include benchmark results from your testing to speed up things. + # Thread Safety The `public static` members of the classes are thread safe. Any instance members are not guaraneteed to be thread safe. You need to implement locking mechanism to safely use the classes in a multi-threading environment. # Use All the classes included in this package are meant to be direct replacements of their corresponding system classes. +* `IList` ⇨ `ILongList` * `List` ⇨ `RecyclableList` -* `SortedList` ⇨ `RecyclableSortedList` * `Queue` ⇨ `RecyclableQueue` * `PriorityQueue` ⇨ `RecyclableSortedList` +* `SortedList` ⇨ `RecyclableSortedList` * `Stack` ⇨ `RecyclableStack` -* `IList` ⇨ `ILongList` Examples of each of the above classes are provided below. Please refer automated tests for more. + +## `RecyclableArrayList` +It's the closest equivalent to `List` in regards to functionality. It supports items' removal & delivers the best performance. But it's limited to `int` range, tough. If you find that acceptable, my recommendation is to use this class as the direct replacement for `List`. + +Do note, that it implements `ILongList` interface, too. This is to allow having common code base regardless, if you use the basic `RecyclableArrayList` or it's `long`-range version - `RecyclableList`. By doing that you can switch between them with highly limited changes in code. + ## `RecyclableList` ```CSharp @@ -110,4 +123,10 @@ public void RecyclableListExample() list.Clear(); } -``` \ No newline at end of file +``` +## `RecyclableArrayList` + +## `RecyclableArrayList` + +## `RecyclableArrayList` + From 4fcf3f23620363c4bbb46b34d212e01eda219f87 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 27 Jan 2023 22:33:14 +0100 Subject: [PATCH 14/57] public MemoryBlocks property added to allow more efficient use. --- Recyclable.Collections/RecyclableList.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 56588e4..31dc4ad 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -14,7 +14,7 @@ namespace Recyclable.Collections private int _blockSize; private int _lastBlockIndex; - private int _lastItemIndex; + private int _lastItemIndex; protected T[][] _memoryBlocks; private long _capacity; @@ -270,6 +270,8 @@ namespace Recyclable.Collections set => new Span(_memoryBlocks[index / _blockSize])[index % _blockSize] = value; } + public T[][] MemoryBlocks { get => _memoryBlocks; } + public void Add(T item) { long requiredCapacity = _longCount + 1; From d4440134c86fbebd83e1b7600ba54475aa0ba842 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 27 Jan 2023 23:32:28 +0100 Subject: [PATCH 15/57] `ref` removed. I seem to get better & more stable benchmarks without it. --- Recyclable.Collections/RecyclableList.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 31dc4ad..a12819d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -180,7 +180,7 @@ namespace Recyclable.Collections protected long EnsureCapacity(in long requestedCapacity) { long oldCapacity = _capacity; - ref T[][] memory = ref _memoryBlocks; + T[][] memory = _memoryBlocks; long newCapacity; switch (oldCapacity > 0) @@ -209,6 +209,7 @@ namespace Recyclable.Collections newCapacity = memory.Length * blockSize; _capacity = newCapacity; + _memoryBlocks = memory; return newCapacity; } From b9b4c40cf930e1bbe8eafb8f2dd92b7362e0fe4f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 27 Jan 2023 23:48:56 +0100 Subject: [PATCH 16/57] Performance improvement. Removing unnecessary var. --- Recyclable.Collections/RecyclableList.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index a12819d..8f0cc12 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -199,15 +199,13 @@ namespace Recyclable.Collections break; } - int blockSize = _blockSize; - memory = SetNewLength(memory, blockSize, newCapacity); + memory = SetNewLength(memory, _blockSize, newCapacity); if (oldCapacity == 0 && newCapacity > 0) { - blockSize = memory[0].Length; - _blockSize = blockSize; + _blockSize = memory[0].Length; } - newCapacity = memory.Length * blockSize; + newCapacity = memory.Length * _blockSize; _capacity = newCapacity; _memoryBlocks = memory; return newCapacity; From 35ccb12efc0c015c87cbe12205602dc62ac17855 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 14:05:30 +0100 Subject: [PATCH 17/57] Performance improvements --- Recyclable.Collections/RecyclableList.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 8f0cc12..8507cbe 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -179,14 +179,13 @@ namespace Recyclable.Collections protected long EnsureCapacity(in long requestedCapacity) { - long oldCapacity = _capacity; T[][] memory = _memoryBlocks; long newCapacity; - switch (oldCapacity > 0) + switch (_capacity > 0) { case true: - newCapacity = oldCapacity; + newCapacity = _capacity; while (newCapacity < requestedCapacity) { newCapacity *= 2; @@ -200,7 +199,7 @@ namespace Recyclable.Collections } memory = SetNewLength(memory, _blockSize, newCapacity); - if (oldCapacity == 0 && newCapacity > 0) + if (newCapacity > 0 && _capacity == 0) { _blockSize = memory[0].Length; } From 680776f2594617ac4583d7c80f2f2e1c7a86c365 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 14:18:34 +0100 Subject: [PATCH 18/57] Performance improvement - Removing unneeded check. There is no case the new block count == current block count. --- Recyclable.Collections/RecyclableList.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 8507cbe..04f1125 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -82,10 +82,6 @@ namespace Recyclable.Collections ArrayPool blockArrayPool = _blockArrayPool; var sourceBlockCount = source?.Length ?? 0; int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); - if (requiredBlockCount == sourceBlockCount) - { - return source!; - } Span newArraySpan; T[][] newMemoryBlocks = requiredBlockCount > RecyclableDefaults.MinPooledArrayLength From 10717c4bd4a096624b3b78b5eb2b08b9e21ff4b5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 14:19:31 +0100 Subject: [PATCH 19/57] Source code cleanup --- Recyclable.Collections/RecyclableList.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 04f1125..5e8cb31 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -206,12 +206,12 @@ namespace Recyclable.Collections return newCapacity; } - public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) + public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { - switch (initialCapacity > 0) + switch (expectedItemsCount > 0) { case true: - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, initialCapacity.Value); + _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); if (_memoryBlocks.Length > 0) { blockSize = _memoryBlocks[0].Length; From c72b18ad1d1f05a3caac1c0d8c7d9da505843536 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 14:19:31 +0100 Subject: [PATCH 20/57] Source code cleanup --- .../RecyclableCollectionsBenchmarks.RecyclableList.cs | 4 ++-- Recyclable.Collections/RecyclableList.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 4e63eaf..4bf6795 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -14,7 +14,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void RecyclableList_Create_WithCapacity() { - using var list = new RecyclableList(blockSize: 102_400, initialCapacity: TestObjectCount); + using var list = new RecyclableList(blockSize: 102_400, expectedItemsCount: TestObjectCount); DoNothing(list); } @@ -35,7 +35,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(blockSize: 102_400, initialCapacity: dataCount); + using var list = new RecyclableList(blockSize: 102_400, expectedItemsCount: dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 04f1125..5e8cb31 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -206,12 +206,12 @@ namespace Recyclable.Collections return newCapacity; } - public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) + public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { - switch (initialCapacity > 0) + switch (expectedItemsCount > 0) { case true: - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, initialCapacity.Value); + _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); if (_memoryBlocks.Length > 0) { blockSize = _memoryBlocks[0].Length; From 41c9eccd81a044a3f5bb140adc32600e5b06b13d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 14:28:51 +0100 Subject: [PATCH 21/57] Adjusting const to ^2 --- Recyclable.Collections/RecyclableDefaults.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index b7ea187..968c170 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -4,6 +4,6 @@ { public const int BlockSize = 10_240; public const int Capacity = 1; - public const int MinPooledArrayLength = 100; + public const int MinPooledArrayLength = 128; } } From 3ce5c52d547fe0a0b8c0243e6fc5304a870acaad Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 14:32:19 +0100 Subject: [PATCH 22/57] Adjusting default block size to what .Net currently returns to avoid sub-optimal execution paths. --- Recyclable.Collections/RecyclableDefaults.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 968c170..9012a0e 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -2,7 +2,7 @@ { public class RecyclableDefaults { - public const int BlockSize = 10_240; + public const int BlockSize = 16_384; public const int Capacity = 1; public const int MinPooledArrayLength = 128; } From 315acb5a68b4cb1b4cf7a28dedcc73fb4cc0d4bf Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 15:02:50 +0100 Subject: [PATCH 23/57] Fixing condition for taking array from the pool --- Recyclable.Collections/RecyclableList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 5e8cb31..af833bf 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -84,7 +84,7 @@ namespace Recyclable.Collections int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); Span newArraySpan; - T[][] newMemoryBlocks = requiredBlockCount > RecyclableDefaults.MinPooledArrayLength + T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? arrayPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; From 6da67ede0d2eee908990c8b1708a78fce74a70f8 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 22:40:48 +0100 Subject: [PATCH 24/57] RecyclableArrayList.Add marked for aggresive inlining for the best performance. --- Recyclable.Collections/RecyclableArrayList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 4d8a23b..6779dc8 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -153,7 +153,7 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } = false; - //[MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { int requiredCapacity = _count + 1; From 3d6536beea861c47cc3b5c2bbd6d78f41853c7c3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 22:49:10 +0100 Subject: [PATCH 25/57] RecyclableList.Add marked for aggressive inlining for the best performance --- Recyclable.Collections/RecyclableList.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index af833bf..fc4e9b4 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -266,6 +266,7 @@ namespace Recyclable.Collections public T[][] MemoryBlocks { get => _memoryBlocks; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { long requiredCapacity = _longCount + 1; From 3cfe0b7e79d9728cf5d2c711b6e479cedaec33b5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 23:04:55 +0100 Subject: [PATCH 26/57] We'll now use Span instead of memory to eliminate 1 more local var. --- Recyclable.Collections/RecyclableList.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index fc4e9b4..eb7a97c 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -83,6 +83,7 @@ namespace Recyclable.Collections var sourceBlockCount = source?.Length ?? 0; int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); + Span sourceSpan; Span newArraySpan; T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? arrayPool.Rent(requiredBlockCount) @@ -94,15 +95,15 @@ namespace Recyclable.Collections switch (sourceBlockCount > 0) { case true: - Memory sourceMemory = new(source); - Memory newArrayMemory = new(newMemoryBlocks); - sourceMemory.CopyTo(newArrayMemory); - if (sourceMemory.Length >= RecyclableDefaults.MinPooledArrayLength) + sourceSpan = new Span(source); + newArraySpan = new Span(newMemoryBlocks); + sourceSpan.CopyTo(newArraySpan); + if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) { arrayPool.Return(source!); } - newArraySpan = new Span(newMemoryBlocks)[sourceBlockCount..]; + newArraySpan = newArraySpan[sourceBlockCount..]; break; case false: @@ -151,7 +152,7 @@ namespace Recyclable.Collections return newMemoryBlocks; case false: - var sourceSpan = new Span(source)[..requiredBlockCount]; + sourceSpan = new Span(source)[..requiredBlockCount]; newArraySpan = new Span(newMemoryBlocks); sourceSpan.CopyTo(newArraySpan); sourceSpan = new Span(source)[newArraySpan.Length..]; From ea1d7d91baf52fdd8084044ba3e8329b8bc63135 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 23:17:30 +0100 Subject: [PATCH 27/57] Solutions from RecyclableList.SetNewLength migrated to RecyclableList.SetNewLength. --- Recyclable.Collections/RecyclableArrayList.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 6779dc8..794d55b 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -21,25 +21,36 @@ namespace Recyclable.Collections { ArrayPool arrayPool = _arrayPool; var sourceLength = source?.Length ?? 0; - T[] newMemoryBlock = newSize.RentArrayFromPool(arrayPool); + Span sourceSpan, newArraySpan; + T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength + ? arrayPool.Rent(newSize) + : new T[newSize]; + switch (newSize >= sourceLength) { case true: if (sourceLength > 0) { - Memory sourceMemory = new(source); - Memory newArrayMemory = new(newMemoryBlock); - sourceMemory.CopyTo(newArrayMemory); - source!.ReturnToPool(arrayPool); + sourceSpan = new Span(source); + newArraySpan = new Span(newMemoryBlock); + sourceSpan.CopyTo(newArraySpan); + if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) + { + arrayPool.Return(source!); + } } return newMemoryBlock; case false: - var sourceSpan = new Span(source)[..newSize]; - Span newArraySpan = new(newMemoryBlock); + sourceSpan = new Span(source)[..newSize]; + newArraySpan = new(newMemoryBlock); sourceSpan.CopyTo(newArraySpan); - source!.ReturnToPool(arrayPool); + if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) + { + arrayPool.Return(source!); + } + return newMemoryBlock; } } From 06ef3d4fa4d33277de7cc128a52d87c7f13da60f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 23:19:26 +0100 Subject: [PATCH 28/57] Tiny cleanup --- Recyclable.Collections/RecyclableList.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index eb7a97c..9d732ee 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -83,8 +83,7 @@ namespace Recyclable.Collections var sourceBlockCount = source?.Length ?? 0; int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); - Span sourceSpan; - Span newArraySpan; + Span sourceSpan, newArraySpan; T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? arrayPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; From 8b7c4407d50eede5ff60d4803fa05a918a7af762 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 23:48:28 +0100 Subject: [PATCH 29/57] Performance improvement - removed unneeded case. We never hit it in our benchmarks or tests. --- Recyclable.Collections/RecyclableList.cs | 123 +++++++++-------------- 1 file changed, 49 insertions(+), 74 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 9d732ee..b6dd393 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -88,89 +88,64 @@ namespace Recyclable.Collections ? arrayPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; - switch (requiredBlockCount >= sourceBlockCount) + switch (sourceBlockCount > 0) { case true: - switch (sourceBlockCount > 0) - { - case true: - sourceSpan = new Span(source); - newArraySpan = new Span(newMemoryBlocks); - sourceSpan.CopyTo(newArraySpan); - if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) - { - arrayPool.Return(source!); - } - - newArraySpan = newArraySpan[sourceBlockCount..]; - break; - - case false: - newArraySpan = new Span(newMemoryBlocks); - break; - } - - int uninitializedBlocksCount = newArraySpan.Length; - if (uninitializedBlocksCount == requiredBlockCount) - { - switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - case true: - newArraySpan[0] = blockArrayPool.Rent(blockSize); - blockSize = newArraySpan[0].Length; - break; - - case false: - newArraySpan[0] = new T[blockSize]; - break; - } - - newArraySpan = newArraySpan[1..]; - uninitializedBlocksCount--; - } - - switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - case true: - for (int i = 0; i < uninitializedBlocksCount; i++) - { - newArraySpan[i] = blockArrayPool.Rent(blockSize); - } - - break; - - case false: - for (int i = 0; i < uninitializedBlocksCount; i++) - { - newArraySpan[i] = new T[blockSize]; - } - - break; - } - - return newMemoryBlocks; - - case false: - sourceSpan = new Span(source)[..requiredBlockCount]; + sourceSpan = new Span(source); newArraySpan = new Span(newMemoryBlocks); sourceSpan.CopyTo(newArraySpan); - sourceSpan = new Span(source)[newArraySpan.Length..]; - int sourceLength = sourceSpan.Length; - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - for (int i = 0; i < sourceLength; i++) - { - blockArrayPool.Return(sourceSpan[i]); - } - } - - if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) + if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) { arrayPool.Return(source!); } - return newMemoryBlocks; + newArraySpan = newArraySpan[sourceBlockCount..]; + break; + + case false: + newArraySpan = new Span(newMemoryBlocks); + break; } + + int uninitializedBlocksCount = newArraySpan.Length; + if (uninitializedBlocksCount == requiredBlockCount) + { + switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + case true: + newArraySpan[0] = blockArrayPool.Rent(blockSize); + blockSize = newArraySpan[0].Length; + break; + + case false: + newArraySpan[0] = new T[blockSize]; + break; + } + + newArraySpan = newArraySpan[1..]; + uninitializedBlocksCount--; + } + + switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + case true: + for (int i = 0; i < uninitializedBlocksCount; i++) + { + newArraySpan[i] = blockArrayPool.Rent(blockSize); + } + + break; + + case false: + for (int i = 0; i < uninitializedBlocksCount; i++) + { + newArraySpan[i] = new T[blockSize]; + } + + break; + } + + return newMemoryBlocks; } protected long EnsureCapacity(in long requestedCapacity) From 1b3c24bf74882d529c8a78ca73f9cb61a7e93eae Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 28 Jan 2023 23:48:28 +0100 Subject: [PATCH 30/57] Performance improvement - removed unneeded case. We never hit it in our benchmarks or tests. We'll use Memory instead of Span again, where applicable. --- Recyclable.Collections/RecyclableArrayList.cs | 36 ++--- Recyclable.Collections/RecyclableList.cs | 123 +++++++----------- 2 files changed, 59 insertions(+), 100 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 794d55b..581916f 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -21,38 +21,22 @@ namespace Recyclable.Collections { ArrayPool arrayPool = _arrayPool; var sourceLength = source?.Length ?? 0; - Span sourceSpan, newArraySpan; T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength ? arrayPool.Rent(newSize) : new T[newSize]; - switch (newSize >= sourceLength) + if (sourceLength > 0) { - case true: - if (sourceLength > 0) - { - sourceSpan = new Span(source); - newArraySpan = new Span(newMemoryBlock); - sourceSpan.CopyTo(newArraySpan); - if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) - { - arrayPool.Return(source!); - } - } - - return newMemoryBlock; - - case false: - sourceSpan = new Span(source)[..newSize]; - newArraySpan = new(newMemoryBlock); - sourceSpan.CopyTo(newArraySpan); - if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) - { - arrayPool.Return(source!); - } - - return newMemoryBlock; + var sourceSpan = new Memory(source); + var newArraySpan = new Memory(newMemoryBlock); + sourceSpan.CopyTo(newArraySpan); + if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) + { + arrayPool.Return(source!); + } } + + return newMemoryBlock; } protected int EnsureCapacity(in int requestedCapacity) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 9d732ee..b6dd393 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -88,89 +88,64 @@ namespace Recyclable.Collections ? arrayPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; - switch (requiredBlockCount >= sourceBlockCount) + switch (sourceBlockCount > 0) { case true: - switch (sourceBlockCount > 0) - { - case true: - sourceSpan = new Span(source); - newArraySpan = new Span(newMemoryBlocks); - sourceSpan.CopyTo(newArraySpan); - if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) - { - arrayPool.Return(source!); - } - - newArraySpan = newArraySpan[sourceBlockCount..]; - break; - - case false: - newArraySpan = new Span(newMemoryBlocks); - break; - } - - int uninitializedBlocksCount = newArraySpan.Length; - if (uninitializedBlocksCount == requiredBlockCount) - { - switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - case true: - newArraySpan[0] = blockArrayPool.Rent(blockSize); - blockSize = newArraySpan[0].Length; - break; - - case false: - newArraySpan[0] = new T[blockSize]; - break; - } - - newArraySpan = newArraySpan[1..]; - uninitializedBlocksCount--; - } - - switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - case true: - for (int i = 0; i < uninitializedBlocksCount; i++) - { - newArraySpan[i] = blockArrayPool.Rent(blockSize); - } - - break; - - case false: - for (int i = 0; i < uninitializedBlocksCount; i++) - { - newArraySpan[i] = new T[blockSize]; - } - - break; - } - - return newMemoryBlocks; - - case false: - sourceSpan = new Span(source)[..requiredBlockCount]; + sourceSpan = new Span(source); newArraySpan = new Span(newMemoryBlocks); sourceSpan.CopyTo(newArraySpan); - sourceSpan = new Span(source)[newArraySpan.Length..]; - int sourceLength = sourceSpan.Length; - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - for (int i = 0; i < sourceLength; i++) - { - blockArrayPool.Return(sourceSpan[i]); - } - } - - if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) + if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) { arrayPool.Return(source!); } - return newMemoryBlocks; + newArraySpan = newArraySpan[sourceBlockCount..]; + break; + + case false: + newArraySpan = new Span(newMemoryBlocks); + break; } + + int uninitializedBlocksCount = newArraySpan.Length; + if (uninitializedBlocksCount == requiredBlockCount) + { + switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + case true: + newArraySpan[0] = blockArrayPool.Rent(blockSize); + blockSize = newArraySpan[0].Length; + break; + + case false: + newArraySpan[0] = new T[blockSize]; + break; + } + + newArraySpan = newArraySpan[1..]; + uninitializedBlocksCount--; + } + + switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + case true: + for (int i = 0; i < uninitializedBlocksCount; i++) + { + newArraySpan[i] = blockArrayPool.Rent(blockSize); + } + + break; + + case false: + for (int i = 0; i < uninitializedBlocksCount; i++) + { + newArraySpan[i] = new T[blockSize]; + } + + break; + } + + return newMemoryBlocks; } protected long EnsureCapacity(in long requestedCapacity) From 96f4912cd34299a70d360ddbb3b3c1d9ad2c955b Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 13:57:30 +0100 Subject: [PATCH 31/57] Unnecessary case removed. --- Recyclable.Collections/RecyclableList.cs | 25 +++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index b6dd393..c81d400 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -83,28 +83,21 @@ namespace Recyclable.Collections var sourceBlockCount = source?.Length ?? 0; int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); - Span sourceSpan, newArraySpan; T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? arrayPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; - switch (sourceBlockCount > 0) + var newArraySpan = new Span(newMemoryBlocks); + if (sourceBlockCount > 0) { - case true: - sourceSpan = new Span(source); - newArraySpan = new Span(newMemoryBlocks); - sourceSpan.CopyTo(newArraySpan); - if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) - { - arrayPool.Return(source!); - } + var sourceSpan = new Span(source); + sourceSpan.CopyTo(newArraySpan); + if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + { + arrayPool.Return(source!); + } - newArraySpan = newArraySpan[sourceBlockCount..]; - break; - - case false: - newArraySpan = new Span(newMemoryBlocks); - break; + newArraySpan = newArraySpan[sourceBlockCount..]; } int uninitializedBlocksCount = newArraySpan.Length; From a9012dc25db5bff02cf8f82c8768df837a6c45bb Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 14:09:41 +0100 Subject: [PATCH 32/57] Tiny cleanup. Declaration moved closer to usage. --- Recyclable.Collections/RecyclableList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index c81d400..adf7a6d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -79,7 +79,6 @@ namespace Recyclable.Collections protected static T[][] SetNewLength(in T[][]? source, int blockSize, in long newCapacity) { ArrayPool arrayPool = _arrayPool; - ArrayPool blockArrayPool = _blockArrayPool; var sourceBlockCount = source?.Length ?? 0; int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); @@ -100,6 +99,7 @@ namespace Recyclable.Collections newArraySpan = newArraySpan[sourceBlockCount..]; } + ArrayPool blockArrayPool = _blockArrayPool; int uninitializedBlocksCount = newArraySpan.Length; if (uninitializedBlocksCount == requiredBlockCount) { From 16acdff58a2daa7fe27f7598b73b33e35b88515a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 14:13:31 +0100 Subject: [PATCH 33/57] Code readability - newArraySpan renamed => newMemoryBlocksSpan to match naming convention --- Recyclable.Collections/RecyclableList.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index adf7a6d..9c77b6d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -86,36 +86,36 @@ namespace Recyclable.Collections ? arrayPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; - var newArraySpan = new Span(newMemoryBlocks); + var newMemoryBlocksSpan = new Span(newMemoryBlocks); if (sourceBlockCount > 0) { var sourceSpan = new Span(source); - sourceSpan.CopyTo(newArraySpan); + sourceSpan.CopyTo(newMemoryBlocksSpan); if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) { arrayPool.Return(source!); } - newArraySpan = newArraySpan[sourceBlockCount..]; + newMemoryBlocksSpan = newMemoryBlocksSpan[sourceBlockCount..]; } ArrayPool blockArrayPool = _blockArrayPool; - int uninitializedBlocksCount = newArraySpan.Length; + int uninitializedBlocksCount = newMemoryBlocksSpan.Length; if (uninitializedBlocksCount == requiredBlockCount) { switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) { case true: - newArraySpan[0] = blockArrayPool.Rent(blockSize); - blockSize = newArraySpan[0].Length; + newMemoryBlocksSpan[0] = blockArrayPool.Rent(blockSize); + blockSize = newMemoryBlocksSpan[0].Length; break; case false: - newArraySpan[0] = new T[blockSize]; + newMemoryBlocksSpan[0] = new T[blockSize]; break; } - newArraySpan = newArraySpan[1..]; + newMemoryBlocksSpan = newMemoryBlocksSpan[1..]; uninitializedBlocksCount--; } @@ -124,7 +124,7 @@ namespace Recyclable.Collections case true: for (int i = 0; i < uninitializedBlocksCount; i++) { - newArraySpan[i] = blockArrayPool.Rent(blockSize); + newMemoryBlocksSpan[i] = blockArrayPool.Rent(blockSize); } break; @@ -132,7 +132,7 @@ namespace Recyclable.Collections case false: for (int i = 0; i < uninitializedBlocksCount; i++) { - newArraySpan[i] = new T[blockSize]; + newMemoryBlocksSpan[i] = new T[blockSize]; } break; From 6270f79291f0db291df56d85008a63d114363db8 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 14:19:59 +0100 Subject: [PATCH 34/57] Switches converted back to IFs. They are IF's in IL anyway. --- Recyclable.Collections/RecyclableList.cs | 153 ++++++++++------------- 1 file changed, 68 insertions(+), 85 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 9c77b6d..ab2204f 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -45,16 +45,14 @@ namespace Recyclable.Collections } list._longCount--; - switch (list._lastItemIndex) + if (list._lastItemIndex <= 0) { - case 0: - list._lastBlockIndex--; - list._lastItemIndex = list._blockSize; - break; - - default: - list._lastItemIndex--; - break; + list._lastBlockIndex--; + list._lastItemIndex = list._blockSize; + } + else + { + list._lastItemIndex--; } int blockSize = list._blockSize; @@ -103,39 +101,33 @@ namespace Recyclable.Collections int uninitializedBlocksCount = newMemoryBlocksSpan.Length; if (uninitializedBlocksCount == requiredBlockCount) { - switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) { - case true: - newMemoryBlocksSpan[0] = blockArrayPool.Rent(blockSize); - blockSize = newMemoryBlocksSpan[0].Length; - break; - - case false: - newMemoryBlocksSpan[0] = new T[blockSize]; - break; + newMemoryBlocksSpan[0] = blockArrayPool.Rent(blockSize); + blockSize = newMemoryBlocksSpan[0].Length; + } + else + { + newMemoryBlocksSpan[0] = new T[blockSize]; } newMemoryBlocksSpan = newMemoryBlocksSpan[1..]; uninitializedBlocksCount--; } - switch (blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) { - case true: - for (int i = 0; i < uninitializedBlocksCount; i++) - { - newMemoryBlocksSpan[i] = blockArrayPool.Rent(blockSize); - } - - break; - - case false: - for (int i = 0; i < uninitializedBlocksCount; i++) - { - newMemoryBlocksSpan[i] = new T[blockSize]; - } - - break; + for (int i = 0; i < uninitializedBlocksCount; i++) + { + newMemoryBlocksSpan[i] = blockArrayPool.Rent(blockSize); + } + } + else + { + for (int i = 0; i < uninitializedBlocksCount; i++) + { + newMemoryBlocksSpan[i] = new T[blockSize]; + } } return newMemoryBlocks; @@ -146,20 +138,17 @@ namespace Recyclable.Collections T[][] memory = _memoryBlocks; long newCapacity; - switch (_capacity > 0) + if (_capacity > 0) { - case true: - newCapacity = _capacity; - while (newCapacity < requestedCapacity) - { - newCapacity *= 2; - } - - break; - - case false: - newCapacity = requestedCapacity; - break; + newCapacity = _capacity; + while (newCapacity < requestedCapacity) + { + newCapacity *= 2; + } + } + else + { + newCapacity = requestedCapacity; } memory = SetNewLength(memory, _blockSize, newCapacity); @@ -176,45 +165,41 @@ namespace Recyclable.Collections public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { - switch (expectedItemsCount > 0) + if (expectedItemsCount > 0) { - case true: - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); - if (_memoryBlocks.Length > 0) - { - blockSize = _memoryBlocks[0].Length; - } + _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + if (_memoryBlocks.Length > 0) + { + blockSize = _memoryBlocks[0].Length; + } - _blockSize = blockSize; - _capacity = _memoryBlocks.Length * blockSize; - break; - - case false: - _blockSize = blockSize; - _memoryBlocks = _emptyMemoryBlocksArray; - break; + _blockSize = blockSize; + _capacity = _memoryBlocks.Length * blockSize; + } + else + { + _blockSize = blockSize; + _memoryBlocks = _emptyMemoryBlocksArray; } } public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { - switch (expectedItemsCount > 0) + if (expectedItemsCount > 0) { - case true: - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); - if (_memoryBlocks.Length > 0) - { - blockSize = _memoryBlocks[0].Length; - } + _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + if (_memoryBlocks.Length > 0) + { + blockSize = _memoryBlocks[0].Length; + } - _blockSize = blockSize; - _capacity = _memoryBlocks.Length * blockSize; - break; - - case false: - _blockSize = blockSize; - _memoryBlocks = _emptyMemoryBlocksArray; - break; + _blockSize = blockSize; + _capacity = _memoryBlocks.Length * blockSize; + } + else + { + _blockSize = blockSize; + _memoryBlocks = _emptyMemoryBlocksArray; } AddRange(source); @@ -515,15 +500,13 @@ namespace Recyclable.Collections public bool Remove(T item) => throw new NotSupportedException(); public void RemoveBlock(int index) { - switch (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { - case true: - _blockArrayPool.Return(_memoryBlocks[index]); - break; - - case false: - _memoryBlocks[index] = _emptyBlockArray; - break; + _blockArrayPool.Return(_memoryBlocks[index]); + } + else + { + _memoryBlocks[index] = _emptyBlockArray; } _capacity -= _blockSize; From a3335bf4e7ad3150588122dacb0d06523908238a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 14:20:59 +0100 Subject: [PATCH 35/57] IF's inverted to match expected most frequent scenarios. --- Recyclable.Collections/RecyclableList.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index ab2204f..9439072 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -45,14 +45,14 @@ namespace Recyclable.Collections } list._longCount--; - if (list._lastItemIndex <= 0) + if (list._lastItemIndex > 0) { - list._lastBlockIndex--; - list._lastItemIndex = list._blockSize; + list._lastItemIndex--; } else { - list._lastItemIndex--; + list._lastBlockIndex--; + list._lastItemIndex = list._blockSize; } int blockSize = list._blockSize; @@ -80,9 +80,9 @@ namespace Recyclable.Collections var sourceBlockCount = source?.Length ?? 0; int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); - T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength - ? arrayPool.Rent(requiredBlockCount) - : new T[requiredBlockCount][]; + T[][] newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? new T[requiredBlockCount][] + : arrayPool.Rent(requiredBlockCount); var newMemoryBlocksSpan = new Span(newMemoryBlocks); if (sourceBlockCount > 0) @@ -500,13 +500,13 @@ namespace Recyclable.Collections public bool Remove(T item) => throw new NotSupportedException(); public void RemoveBlock(int index) { - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) { - _blockArrayPool.Return(_memoryBlocks[index]); + _memoryBlocks[index] = _emptyBlockArray; } else { - _memoryBlocks[index] = _emptyBlockArray; + _blockArrayPool.Return(_memoryBlocks[index]); } _capacity -= _blockSize; From 326999c682207159cb8952850025d74bd3b60a87 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 14:48:30 +0100 Subject: [PATCH 36/57] Performance improvement - reuse local var --- Recyclable.Collections/RecyclableList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 9439072..ad5101c 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -45,6 +45,7 @@ namespace Recyclable.Collections } list._longCount--; + int blockSize = list._blockSize; if (list._lastItemIndex > 0) { list._lastItemIndex--; @@ -52,10 +53,9 @@ namespace Recyclable.Collections else { list._lastBlockIndex--; - list._lastItemIndex = list._blockSize; + list._lastItemIndex = blockSize; } - int blockSize = list._blockSize; if ((list._capacity * blockSize) - oldCountMinus1 == blockSize) { T[][] memoryBlocks = list._memoryBlocks; From b01dc63165d3e1689c6c71c33b2f97ee98458b28 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 14:50:00 +0100 Subject: [PATCH 37/57] Too early typecasting fixed. --- Recyclable.Collections/RecyclableList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index ad5101c..68dfce5 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -490,7 +490,7 @@ namespace Recyclable.Collections public bool Contains(T item) => _memoryBlocks.Any(x => x.Contains(item)); - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, (int)LongCount % _blockSize, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, (int)(LongCount % _blockSize), array, arrayIndex); public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); From 463ad3a36568dbb830892313b63b53bdeb27944a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 14:50:47 +0100 Subject: [PATCH 38/57] Code cleanup - renaming blockSize => minBlockSize to better reflect the use. --- Recyclable.Collections/RecyclableList.cs | 42 ++++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 68dfce5..9566f90 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -74,11 +74,11 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, int blockSize, in long newCapacity) + protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, in long newCapacity) { ArrayPool arrayPool = _arrayPool; var sourceBlockCount = source?.Length ?? 0; - int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); + int requiredBlockCount = (int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0); T[][] newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength ? new T[requiredBlockCount][] @@ -101,32 +101,32 @@ namespace Recyclable.Collections int uninitializedBlocksCount = newMemoryBlocksSpan.Length; if (uninitializedBlocksCount == requiredBlockCount) { - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { - newMemoryBlocksSpan[0] = blockArrayPool.Rent(blockSize); - blockSize = newMemoryBlocksSpan[0].Length; + newMemoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = newMemoryBlocksSpan[0].Length; } else { - newMemoryBlocksSpan[0] = new T[blockSize]; + newMemoryBlocksSpan[0] = new T[minBlockSize]; } newMemoryBlocksSpan = newMemoryBlocksSpan[1..]; uninitializedBlocksCount--; } - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { for (int i = 0; i < uninitializedBlocksCount; i++) { - newMemoryBlocksSpan[i] = blockArrayPool.Rent(blockSize); + newMemoryBlocksSpan[i] = blockArrayPool.Rent(minBlockSize); } } else { for (int i = 0; i < uninitializedBlocksCount; i++) { - newMemoryBlocksSpan[i] = new T[blockSize]; + newMemoryBlocksSpan[i] = new T[minBlockSize]; } } @@ -163,42 +163,42 @@ namespace Recyclable.Collections return newCapacity; } - public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value); if (_memoryBlocks.Length > 0) { - blockSize = _memoryBlocks[0].Length; + minBlockSize = _memoryBlocks[0].Length; } - _blockSize = blockSize; - _capacity = _memoryBlocks.Length * blockSize; + _blockSize = minBlockSize; + _capacity = _memoryBlocks.Length * minBlockSize; } else { - _blockSize = blockSize; + _blockSize = minBlockSize; _memoryBlocks = _emptyMemoryBlocksArray; } } - public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value); if (_memoryBlocks.Length > 0) { - blockSize = _memoryBlocks[0].Length; + minBlockSize = _memoryBlocks[0].Length; } - _blockSize = blockSize; - _capacity = _memoryBlocks.Length * blockSize; + _blockSize = minBlockSize; + _capacity = _memoryBlocks.Length * minBlockSize; } else { - _blockSize = blockSize; + _blockSize = minBlockSize; _memoryBlocks = _emptyMemoryBlocksArray; } From ea29108731456c1b0f72293f14dae3a21cf817f6 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 14:50:47 +0100 Subject: [PATCH 39/57] Code cleanup - renaming blockSize => minBlockSize to better reflect the use. --- ...bleCollectionsBenchmarks.RecyclableList.cs | 4 +- Recyclable.Collections/RecyclableList.cs | 42 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 4bf6795..b661bc2 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -14,7 +14,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void RecyclableList_Create_WithCapacity() { - using var list = new RecyclableList(blockSize: 102_400, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(minBlockSize: 102_400, expectedItemsCount: TestObjectCount); DoNothing(list); } @@ -35,7 +35,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(blockSize: 102_400, expectedItemsCount: dataCount); + using var list = new RecyclableList(minBlockSize: 102_400, expectedItemsCount: dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 68dfce5..9566f90 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -74,11 +74,11 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, int blockSize, in long newCapacity) + protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, in long newCapacity) { ArrayPool arrayPool = _arrayPool; var sourceBlockCount = source?.Length ?? 0; - int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); + int requiredBlockCount = (int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0); T[][] newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength ? new T[requiredBlockCount][] @@ -101,32 +101,32 @@ namespace Recyclable.Collections int uninitializedBlocksCount = newMemoryBlocksSpan.Length; if (uninitializedBlocksCount == requiredBlockCount) { - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { - newMemoryBlocksSpan[0] = blockArrayPool.Rent(blockSize); - blockSize = newMemoryBlocksSpan[0].Length; + newMemoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = newMemoryBlocksSpan[0].Length; } else { - newMemoryBlocksSpan[0] = new T[blockSize]; + newMemoryBlocksSpan[0] = new T[minBlockSize]; } newMemoryBlocksSpan = newMemoryBlocksSpan[1..]; uninitializedBlocksCount--; } - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { for (int i = 0; i < uninitializedBlocksCount; i++) { - newMemoryBlocksSpan[i] = blockArrayPool.Rent(blockSize); + newMemoryBlocksSpan[i] = blockArrayPool.Rent(minBlockSize); } } else { for (int i = 0; i < uninitializedBlocksCount; i++) { - newMemoryBlocksSpan[i] = new T[blockSize]; + newMemoryBlocksSpan[i] = new T[minBlockSize]; } } @@ -163,42 +163,42 @@ namespace Recyclable.Collections return newCapacity; } - public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value); if (_memoryBlocks.Length > 0) { - blockSize = _memoryBlocks[0].Length; + minBlockSize = _memoryBlocks[0].Length; } - _blockSize = blockSize; - _capacity = _memoryBlocks.Length * blockSize; + _blockSize = minBlockSize; + _capacity = _memoryBlocks.Length * minBlockSize; } else { - _blockSize = blockSize; + _blockSize = minBlockSize; _memoryBlocks = _emptyMemoryBlocksArray; } } - public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) { if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value); if (_memoryBlocks.Length > 0) { - blockSize = _memoryBlocks[0].Length; + minBlockSize = _memoryBlocks[0].Length; } - _blockSize = blockSize; - _capacity = _memoryBlocks.Length * blockSize; + _blockSize = minBlockSize; + _capacity = _memoryBlocks.Length * minBlockSize; } else { - _blockSize = blockSize; + _blockSize = minBlockSize; _memoryBlocks = _emptyMemoryBlocksArray; } From 71544f8a47aeb0f6c3798241302e83cb94d08f43 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 18:05:27 +0100 Subject: [PATCH 40/57] Finished RecyclableList implementation of array pool overriding. --- Recyclable.Collections/RecyclableList.cs | 45 ++++++++++++++---------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 9566f90..68d0135 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -6,8 +6,8 @@ namespace Recyclable.Collections { public class RecyclableList : IDisposable, IList { - private static readonly ArrayPool _arrayPool = ArrayPool.Create(); - private static readonly ArrayPool _blockArrayPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; private static readonly T[] _emptyBlockArray = new T[0]; private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; @@ -15,6 +15,8 @@ namespace Recyclable.Collections private int _blockSize; private int _lastBlockIndex; private int _lastItemIndex; + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; protected T[][] _memoryBlocks; private long _capacity; @@ -36,7 +38,7 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } = false; public int BlockCount => _memoryBlocks.Length; - private static void RemoveAt(RecyclableList list, long index) + private static void RemoveAt(RecyclableList list, long index, ArrayPool blockArrayPool) { long oldCountMinus1 = list._longCount - 1; if (index != oldCountMinus1) @@ -61,7 +63,7 @@ namespace Recyclable.Collections T[][] memoryBlocks = list._memoryBlocks; if (blockSize >= RecyclableDefaults.MinPooledArrayLength) { - _blockArrayPool.Return(memoryBlocks[list.BlockCount - 1]); + blockArrayPool.Return(memoryBlocks[list.BlockCount - 1]); } list._capacity -= blockSize; @@ -74,15 +76,14 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, in long newCapacity) + protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, in long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { - ArrayPool arrayPool = _arrayPool; var sourceBlockCount = source?.Length ?? 0; int requiredBlockCount = (int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0); T[][] newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength ? new T[requiredBlockCount][] - : arrayPool.Rent(requiredBlockCount); + : memoryBlocksPool.Rent(requiredBlockCount); var newMemoryBlocksSpan = new Span(newMemoryBlocks); if (sourceBlockCount > 0) @@ -91,13 +92,12 @@ namespace Recyclable.Collections sourceSpan.CopyTo(newMemoryBlocksSpan); if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) { - arrayPool.Return(source!); + memoryBlocksPool.Return(source!); } newMemoryBlocksSpan = newMemoryBlocksSpan[sourceBlockCount..]; } - ArrayPool blockArrayPool = _blockArrayPool; int uninitializedBlocksCount = newMemoryBlocksSpan.Length; if (uninitializedBlocksCount == requiredBlockCount) { @@ -151,7 +151,7 @@ namespace Recyclable.Collections newCapacity = requestedCapacity; } - memory = SetNewLength(memory, _blockSize, newCapacity); + memory = SetNewLength(memory, _blockSize, newCapacity, _memoryBlocksPool, _blockArrayPool); if (newCapacity > 0 && _capacity == 0) { _blockSize = memory[0].Length; @@ -163,11 +163,16 @@ namespace Recyclable.Collections return newCapacity; } - public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -183,11 +188,15 @@ namespace Recyclable.Collections } } - public RecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) + public RecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -379,7 +388,7 @@ namespace Recyclable.Collections long oldLongCount = _longCount; int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); - int targetBlockIdx = targetItemIdx / blockSize + (targetItemIdx > 0 ? 1 : 0); + int targetBlockIdx = (targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0); Span memoryBlocksSpan; Span blockArraySpan; @@ -520,8 +529,8 @@ namespace Recyclable.Collections } } - public void RemoveAt(int index) => RemoveAt(this, index); - public void RemoveAt(long index) => RemoveAt(this, index); + public void RemoveAt(int index) => RemoveAt(this, index, _blockArrayPool); + public void RemoveAt(long index) => RemoveAt(this, index, _blockArrayPool); IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); @@ -533,7 +542,7 @@ namespace Recyclable.Collections _capacity = 0; if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) { - _arrayPool.Return(_memoryBlocks); + _memoryBlocksPool.Return(_memoryBlocks); } GC.SuppressFinalize(this); From 4f920cf89a405af1877b12658f4d62394795049a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 21:26:22 +0100 Subject: [PATCH 41/57] RecyclableList.Count redirected to _longCount for better performance --- Recyclable.Collections/RecyclableList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 68d0135..1a64aec 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -26,7 +26,7 @@ namespace Recyclable.Collections protected set => _capacity = value; } - public int Count => (int)LongCount; + public int Count => (int)_longCount; protected long _longCount; public long LongCount From d07c77c906cf3cfec6382c1a1e801a91985108be Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 21:29:11 +0100 Subject: [PATCH 42/57] Unnecessary field initializer removed --- Recyclable.Collections/RecyclableList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 1a64aec..cdb6a3e 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -35,7 +35,7 @@ namespace Recyclable.Collections set => _longCount = value; } - public bool IsReadOnly { get; } = false; + public bool IsReadOnly { get; } public int BlockCount => _memoryBlocks.Length; private static void RemoveAt(RecyclableList list, long index, ArrayPool blockArrayPool) From 650a63828fde5ca56b85b1167b9a7cb548e9391e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 21:30:15 +0100 Subject: [PATCH 43/57] Unnecessary _capacity assignment removed. It's already set by RecyclableList.Clear. --- Recyclable.Collections/RecyclableList.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index cdb6a3e..8a34ca5 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -539,7 +539,6 @@ namespace Recyclable.Collections if (_capacity > 0) { Clear(); - _capacity = 0; if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) { _memoryBlocksPool.Return(_memoryBlocks); From b24adc6d93f03d3ef35b4d6d32d03f59d75d45fa Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 21:31:25 +0100 Subject: [PATCH 44/57] Unnecessary local var removed. --- Recyclable.Collections/RecyclableList.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 8a34ca5..ff4ee94 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -231,10 +231,9 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { - long requiredCapacity = _longCount + 1; - if (_capacity < requiredCapacity) + if (_capacity < _longCount + 1) { - _ = EnsureCapacity(requiredCapacity); + _ = EnsureCapacity(_longCount + 1); } _memoryBlocks[_lastBlockIndex][_lastItemIndex++] = item; From 7f90fcc96952b41abbd4c7592e68cbff5dbd270f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 21:32:04 +0100 Subject: [PATCH 45/57] Tiny cleanup & readability improvement --- Recyclable.Collections/RecyclableList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index ff4ee94..74721a5 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -242,7 +242,7 @@ namespace Recyclable.Collections _lastBlockIndex++; _lastItemIndex = 0; } - + _longCount++; } @@ -260,7 +260,7 @@ namespace Recyclable.Collections int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); var memoryBlocksSpan = new Span(_memoryBlocks); - var memoryBlocksCount = memoryBlocksSpan.Length; + int memoryBlocksCount = memoryBlocksSpan.Length; var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); for (var i = 0L; i < sourceItemsCount; i++) { From 85fcf69c755e84210f7132c9397d02d3895a11eb Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 21:34:14 +0100 Subject: [PATCH 46/57] Local int vars converted to stackalloc'ed array for performance measures. Recyclable.Add benchmarks yield 0.42 - 0.45 vs List.Add --- Recyclable.Collections/RecyclableList.cs | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 74721a5..d0b8e28 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -78,28 +78,35 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, in long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { - var sourceBlockCount = source?.Length ?? 0; - int requiredBlockCount = (int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0); + const int sourceBlockCount = 0, requiredBlockCount = 1, uninitializedBlocksCount = 2, i = 3; - T[][] newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength - ? new T[requiredBlockCount][] - : memoryBlocksPool.Rent(requiredBlockCount); + Span localInts = stackalloc int[4] + { + source?.Length ?? 0, // sourceBlockCount + (int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0), // requiredBlockCount + 0, // uninitializedBlocksCount + 0, // i + }; + + T[][] newMemoryBlocks = localInts[requiredBlockCount] < RecyclableDefaults.MinPooledArrayLength + ? new T[localInts[requiredBlockCount]][] + : memoryBlocksPool.Rent(localInts[requiredBlockCount]); var newMemoryBlocksSpan = new Span(newMemoryBlocks); - if (sourceBlockCount > 0) + if (localInts[sourceBlockCount] > 0) { var sourceSpan = new Span(source); sourceSpan.CopyTo(newMemoryBlocksSpan); - if (sourceSpan.Length >= RecyclableDefaults.MinPooledArrayLength) + if (localInts[sourceBlockCount] >= RecyclableDefaults.MinPooledArrayLength) { memoryBlocksPool.Return(source!); } - newMemoryBlocksSpan = newMemoryBlocksSpan[sourceBlockCount..]; + newMemoryBlocksSpan = newMemoryBlocksSpan[localInts[sourceBlockCount]..]; } - int uninitializedBlocksCount = newMemoryBlocksSpan.Length; - if (uninitializedBlocksCount == requiredBlockCount) + localInts[uninitializedBlocksCount] = newMemoryBlocksSpan.Length; + if (localInts[uninitializedBlocksCount] == localInts[requiredBlockCount]) { if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { @@ -112,21 +119,21 @@ namespace Recyclable.Collections } newMemoryBlocksSpan = newMemoryBlocksSpan[1..]; - uninitializedBlocksCount--; + localInts[uninitializedBlocksCount]--; } if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { - for (int i = 0; i < uninitializedBlocksCount; i++) + for (localInts[i] = 0; localInts[i] < localInts[uninitializedBlocksCount]; localInts[i]++) { - newMemoryBlocksSpan[i] = blockArrayPool.Rent(minBlockSize); + newMemoryBlocksSpan[localInts[i]] = blockArrayPool.Rent(minBlockSize); } } else { - for (int i = 0; i < uninitializedBlocksCount; i++) + for (localInts[i] = 0; localInts[i] < localInts[uninitializedBlocksCount]; localInts[i]++) { - newMemoryBlocksSpan[i] = new T[minBlockSize]; + newMemoryBlocksSpan[localInts[i]] = new T[minBlockSize]; } } From 32fcc1ad04e3b47c9502b74e66d7a7a147f4fc7f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 21:36:30 +0100 Subject: [PATCH 47/57] Tiny code readability improvement --- Recyclable.Collections/RecyclableList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index d0b8e28..e8f393c 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -92,10 +92,10 @@ namespace Recyclable.Collections ? new T[localInts[requiredBlockCount]][] : memoryBlocksPool.Rent(localInts[requiredBlockCount]); - var newMemoryBlocksSpan = new Span(newMemoryBlocks); + Span newMemoryBlocksSpan = new(newMemoryBlocks); if (localInts[sourceBlockCount] > 0) { - var sourceSpan = new Span(source); + Span sourceSpan = new(source); sourceSpan.CopyTo(newMemoryBlocksSpan); if (localInts[sourceBlockCount] >= RecyclableDefaults.MinPooledArrayLength) { From c923383a7186d33cbacc0839281665447769b2fd Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 29 Jan 2023 21:36:30 +0100 Subject: [PATCH 48/57] Tiny code readability improvement --- Recyclable.Collections/RecyclableList.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index d0b8e28..de708f3 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -92,10 +92,10 @@ namespace Recyclable.Collections ? new T[localInts[requiredBlockCount]][] : memoryBlocksPool.Rent(localInts[requiredBlockCount]); - var newMemoryBlocksSpan = new Span(newMemoryBlocks); + Span newMemoryBlocksSpan = new(newMemoryBlocks); if (localInts[sourceBlockCount] > 0) { - var sourceSpan = new Span(source); + Span sourceSpan = new(source); sourceSpan.CopyTo(newMemoryBlocksSpan); if (localInts[sourceBlockCount] >= RecyclableDefaults.MinPooledArrayLength) { @@ -266,9 +266,9 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlocksSpan = new Span(_memoryBlocks); + Span memoryBlocksSpan = new(_memoryBlocks); int memoryBlocksCount = memoryBlocksSpan.Length; - var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); + Span targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]); for (var i = 0L; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; @@ -303,9 +303,9 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlocksSpan = new Span(_memoryBlocks); + Span memoryBlocksSpan = new(_memoryBlocks); var memoryBlocksCount = memoryBlocksSpan.Length; - var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); + Span targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]); for (int i = 0; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; @@ -340,9 +340,9 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlocksSpan = new Span(_memoryBlocks); + Span memoryBlocksSpan = new(_memoryBlocks); var memoryBlocksCount = memoryBlocksSpan.Length; - var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); + Span targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]); for (int i = 0; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; From 30c9a6cedc681bd208b67a6f87f537e472b4a19c Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 31 Jan 2023 02:21:42 +0100 Subject: [PATCH 49/57] RecyclableListExtensions.CopyTo fixed --- Recyclable.Collections/RecyclableList.cs | 2 +- .../RecyclableListExtensions.cs | 45 +++++++------------ 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index de708f3..434615f 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -505,7 +505,7 @@ namespace Recyclable.Collections public bool Contains(T item) => _memoryBlocks.Any(x => x.Contains(item)); - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, (int)(LongCount % _blockSize), array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 2799c22..e873729 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -15,41 +15,26 @@ namespace Recyclable.Collections } } - public static void CopyTo(this T[][] arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + public static void CopyTo(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex) { - Span arrayMemory = destinationArray.AsSpan(); - arrayMemory = arrayMemory[destinationArrayIndex..]; - int startingArrayIndex = (int)(startingIndex / blockSize); - startingIndex %= blockSize; - if (arrays.LongLength > 0) + Memory sourceBlockMemory; + Memory destinationArrayMemory; + int startingBlockIndex = (int)(startingIndex / blockSize); + int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount - (itemsCount / blockSize) > 0 ? 1 : 0)) - 1; + Span sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex - startingBlockIndex + 1); + destinationArrayMemory = new Memory(destinationArray, destinationArrayIndex, destinationArray.Length); + int memoryBlockIndex; + for (memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++) { - ReadOnlySpan sourceMemory = arrays[0].AsSpan(); - var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); - if (arrays.LongLength == 1) - { - maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); - } - - sourceMemory = sourceMemory[(int)startingIndex..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); - arrayMemory = arrayMemory[maxToCopy..]; + sourceBlockMemory = new(sourceMemoryBlocksSpan[memoryBlockIndex], 0, blockSize); + sourceBlockMemory.CopyTo(destinationArrayMemory); + destinationArrayMemory = destinationArrayMemory[blockSize..]; } - for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Length - 1 && arrayMemory.Length > 0; arrayIdx++) + if (itemsCount % blockSize > 0) { - ReadOnlySpan sourceMemory = arrays[arrayIdx].AsSpan(); - var maxToCopy = Math.Min(blockSize, arrayMemory.Length); - sourceMemory = sourceMemory[..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); - arrayMemory = arrayMemory[maxToCopy..]; - } - - if (arrays.Length > 1 && arrayMemory.Length > 0) - { - ReadOnlySpan sourceMemory = arrays[^1].AsSpan(); - var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length); - sourceMemory = sourceMemory[..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); + sourceBlockMemory = new(sourceMemoryBlocksSpan[^1], 0, (int)(itemsCount % blockSize)); + sourceBlockMemory.CopyTo(destinationArrayMemory); } } From 55dc1459c7c963452f9878a2af493735e9a0ceee Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 31 Jan 2023 20:22:04 +0100 Subject: [PATCH 50/57] Incorrect indexes fixed. --- Recyclable.Collections/RecyclableList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 434615f..f1b650b 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -426,8 +426,8 @@ namespace Recyclable.Collections } } - _longCount = targetItemIdx; - _lastBlockIndex = targetBlockIdx - 1; + _longCount = targetBlockIdx * blockSize + targetItemIdx; + _lastBlockIndex = targetBlockIdx; _lastItemIndex = targetItemIdx; return; } From 2dc6d74b0b69003a52d49267dde7bd245569ec3a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 31 Jan 2023 20:23:28 +0100 Subject: [PATCH 51/57] Fix to use the new _blockSize when we add capacity --- Recyclable.Collections/RecyclableList.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index f1b650b..2b6a58f 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -405,6 +405,9 @@ namespace Recyclable.Collections if (capacity < requiredCapacity) { _ = EnsureCapacity(requiredCapacity); + blockSize = _blockSize; + targetBlockIdx = (targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0); + targetItemIdx = (int)(oldLongCount % blockSize); } memoryBlocksSpan = new(_memoryBlocks); From f3381942bc6d7f40728283627a1baaef905ff6c7 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 31 Jan 2023 20:24:30 +0100 Subject: [PATCH 52/57] Tiny code standardization --- Recyclable.Collections/RecyclableList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 2b6a58f..710af38 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -494,7 +494,7 @@ namespace Recyclable.Collections Span memoryBlocksSpan = new(_memoryBlocks); int memoryBlocksCount = memoryBlocksSpan.Length; ArrayPool blockArrayPool = _blockArrayPool; - for (var toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) { blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx]); } From 91abf1a61427ed5414f808d6c9b35b606e36b4d2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 1 Feb 2023 23:29:23 +0100 Subject: [PATCH 53/57] RecyclableList.Contains reworked - first version --- .../BenchmarkBase.cs | 2 + Recyclable.Collections.Benchmarks/Program.cs | 46 +++--- .../RecyclableCollectionsBenchmarks.cs | 2 + Recyclable.Collections/RecyclableList.cs | 136 ++++++++++++++++-- 4 files changed, 154 insertions(+), 32 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index 3c10498..8fd7802 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -21,6 +21,8 @@ namespace Recyclable.Collections.Benchmarks protected RecyclableArrayList? _testObjectsAsRecyclableArrayList; protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); + protected RecyclableList? _testObjectsAsRecyclableList; + protected RecyclableList TestObjectsAsRecyclableList => _testObjectsAsRecyclableList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); protected IEnumerable TestObjectsAsIEnumerable => Enumerable.Range(1, TestObjectCount).Select(static x => new object()); protected static void DoNothing(T obj) diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 882322a..e0e3068 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -4,28 +4,40 @@ using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); -BenchmarkRunner.Run( - ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator) -); - - //BenchmarkRunner.Run(); -//var benchmark = new RecyclableCollectionsBenchmarks(); -//benchmark.Setup(); -//foreach (var _ in Enumerable.Range(1, 1000)) -//{ -// //benchmark.RecyclableArrayList_AddRange_WithCapacity(); -// //benchmark.RecyclableArrayList_Add_WithCapacity(); -// //benchmark.RecyclableList_Create(); -// benchmark.RecyclableList_Create_WithCapacity(); -// //benchmark.RecyclableList_Add(); -// //benchmark.RecyclableList_Add_WithCapacity(); -//} -//benchmark.Cleanup(); +internal class Program +{ + void RunBenchmark() + { + var benchmark = new RecyclableCollectionsBenchmarks(); + benchmark.Setup(); + foreach (var _ in Enumerable.Range(1, 1000)) + { + benchmark.RecyclableList_Contains(); + //benchmark.RecyclableArrayList_AddRange_WithCapacity(); + //benchmark.RecyclableArrayList_Add_WithCapacity(); + //benchmark.RecyclableList_Create(); + //benchmark.RecyclableList_Create_WithCapacity(); + //benchmark.RecyclableList_Add(); + //benchmark.RecyclableList_Add_WithCapacity(); + } + + benchmark.Cleanup(); + } + + private static void Main(string[] args) + { + BenchmarkRunner.Run( + ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary) + ); + + //RunBenchmark(); + } +} // benchmark.Setup(); // benchmark.Cleanup(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs index 8aabc86..3c21495 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs @@ -18,6 +18,7 @@ namespace Recyclable.Collections.Benchmarks _testArray = TestObjects.ToArray(); _testObjectsAsList = TestObjects.ToList(); _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); + _testObjectsAsRecyclableList = TestObjects.ToRecyclableList(); } [GlobalCleanup] @@ -28,6 +29,7 @@ namespace Recyclable.Collections.Benchmarks _testRecyclableList?.Dispose(); TestObjectsAsRecyclableArrayList?.Dispose(); _testObjectsAsRecyclableArrayList = default; + _testObjectsAsRecyclableList = default; _testObjectsAsList = default; _testObjects = default; } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 710af38..ca70c76 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,6 +1,10 @@ using System.Buffers; using System.Collections; +using System.Linq; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Security.Cryptography.X509Certificates; namespace Recyclable.Collections { @@ -14,7 +18,7 @@ namespace Recyclable.Collections private int _blockSize; private int _lastBlockIndex; - private int _lastItemIndex; + private int _nextItemIndex; protected ArrayPool _memoryBlocksPool; protected ArrayPool _blockArrayPool; protected T[][] _memoryBlocks; @@ -48,14 +52,14 @@ namespace Recyclable.Collections list._longCount--; int blockSize = list._blockSize; - if (list._lastItemIndex > 0) + if (list._nextItemIndex > 0) { - list._lastItemIndex--; + list._nextItemIndex--; } else { list._lastBlockIndex--; - list._lastItemIndex = blockSize; + list._nextItemIndex = blockSize; } if ((list._capacity * blockSize) - oldCountMinus1 == blockSize) @@ -243,11 +247,11 @@ namespace Recyclable.Collections _ = EnsureCapacity(_longCount + 1); } - _memoryBlocks[_lastBlockIndex][_lastItemIndex++] = item; - if (_lastItemIndex == _blockSize) + _memoryBlocks[_lastBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) { _lastBlockIndex++; - _lastItemIndex = 0; + _nextItemIndex = 0; } _longCount++; @@ -287,7 +291,7 @@ namespace Recyclable.Collections _longCount = targetCapacity; _lastBlockIndex = targetBlockIdx - 1; - _lastItemIndex = targetItemIdx; + _nextItemIndex = targetItemIdx; } public void AddRange(in List items) @@ -324,7 +328,7 @@ namespace Recyclable.Collections _longCount = targetCapacity; _lastBlockIndex = targetBlockIdx - 1; - _lastItemIndex = targetItemIdx; + _nextItemIndex = targetItemIdx; } public void AddRange(in IList items) @@ -361,7 +365,7 @@ namespace Recyclable.Collections _longCount = targetCapacity; _lastBlockIndex = targetBlockIdx - 1; - _lastItemIndex = targetItemIdx; + _nextItemIndex = targetItemIdx; } private void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) @@ -431,7 +435,7 @@ namespace Recyclable.Collections _longCount = targetBlockIdx * blockSize + targetItemIdx; _lastBlockIndex = targetBlockIdx; - _lastItemIndex = targetItemIdx; + _nextItemIndex = targetItemIdx; return; } @@ -484,7 +488,7 @@ namespace Recyclable.Collections _longCount = targetItemIdx; _lastBlockIndex = targetBlockIdx - 1; - _lastItemIndex = targetItemIdx; + _nextItemIndex = targetItemIdx; } public void Clear() @@ -502,11 +506,113 @@ namespace Recyclable.Collections _capacity = 0; _lastBlockIndex = 0; - _lastItemIndex = 0; + _nextItemIndex = 0; _longCount = 0; } - public bool Contains(T item) => _memoryBlocks.Any(x => x.Contains(item)); + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + int midPoint = _blockSize / 2; + int lastBlockIndex = _lastBlockIndex; + int itemIndex0 = 0; + int itemIndex1 = midPoint; + int blockIndex = 0; + + Span memoryBlocksSpan = new(_memoryBlocks); + Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); + if (item != null) + { + while (blockIndex <= lastBlockIndex) + { + if (item.Equals(blockArraySpan[itemIndex0++])) + { + return true; + } + + if (item.Equals(blockArraySpan[itemIndex1++])) + { + return true; + } + + if (itemIndex0 == midPoint) + { + blockIndex++; + if (blockIndex == lastBlockIndex) + { + int nextItemIndex = _nextItemIndex; + if (nextItemIndex == 0) + { + return false; + } + + midPoint = (nextItemIndex / 2) + (nextItemIndex % 2); + } + + blockArraySpan = new(memoryBlocksSpan[blockIndex]); + itemIndex0 = 0; + itemIndex1 = midPoint; + } + } + } + else + { + while (blockIndex < lastBlockIndex) + { + if (blockArraySpan[itemIndex0++] == null) + { + return true; + } + + if (blockArraySpan[itemIndex1++] == null) + { + return true; + } + + if (itemIndex0 == midPoint) + { + blockIndex++; + if (blockIndex == lastBlockIndex) + { + if (_nextItemIndex == 0) + { + return false; + } + + midPoint = ((_nextItemIndex - 1) / 2) + ((_nextItemIndex - 1) % 2); + } + + blockArraySpan = new(memoryBlocksSpan[blockIndex]); + itemIndex0 = 0; + itemIndex1 = 0; + } + } + } + + 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); @@ -530,7 +636,7 @@ namespace Recyclable.Collections _capacity -= _blockSize; if (_lastBlockIndex == index) { - _lastItemIndex = 0; + _nextItemIndex = 0; } else if (_lastBlockIndex > index) { From 10ae7448759d056ddb380463ebc58be40025e13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Feb 2023 17:58:29 +0100 Subject: [PATCH 54/57] RecyclableList.Contains reworked for performance improvements. 69% vs List.Contains --- .../RecyclableCollectionsBenchmarks.List.cs | 12 + ...bleCollectionsBenchmarks.RecyclableList.cs | 20 +- Recyclable.Collections/RecyclableList.cs | 225 +++++++++++++----- .../RecyclableListTests.cs | 47 ++++ 4 files changed, 245 insertions(+), 59 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 2996574..ee76fc0 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -111,5 +111,17 @@ namespace Recyclable.Collections.Benchmarks var data = TestObjectsAsList; 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])); + } + } } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index b661bc2..6ca2add 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - [Benchmark] + //[Benchmark] public void RecyclableList_Create() { using var list = new RecyclableList(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableList_Create_WithCapacity() { using var list = new RecyclableList(minBlockSize: 102_400, expectedItemsCount: TestObjectCount); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableList_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_Add_WithCapacity() { var data = TestObjects; @@ -77,5 +77,17 @@ namespace Recyclable.Collections.Benchmarks var data = _testRecyclableList; 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])); + } + } } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index ca70c76..00cbd24 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -433,7 +433,7 @@ namespace Recyclable.Collections } } - _longCount = targetBlockIdx * blockSize + targetItemIdx; + _longCount = (targetBlockIdx * blockSize) + targetItemIdx; _lastBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; return; @@ -510,6 +510,7 @@ namespace Recyclable.Collections _longCount = 0; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(T item) { if (_longCount == 0) @@ -517,101 +518,215 @@ namespace Recyclable.Collections return false; } - int midPoint = _blockSize / 2; - int lastBlockIndex = _lastBlockIndex; - int itemIndex0 = 0; - int itemIndex1 = midPoint; + int blockSize = _blockSize; + int itemIndex = 0; int blockIndex = 0; + int lastBlockIndex = 0; Span memoryBlocksSpan = new(_memoryBlocks); Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); if (item != null) { - while (blockIndex <= lastBlockIndex) + while (blockIndex < lastBlockIndex) { - if (item.Equals(blockArraySpan[itemIndex0++])) + if (item.Equals(blockArraySpan[itemIndex])) { 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; } - 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++; - if (blockIndex == lastBlockIndex) - { - int nextItemIndex = _nextItemIndex; - if (nextItemIndex == 0) - { - return false; - } - - midPoint = (nextItemIndex / 2) + (nextItemIndex % 2); - } - blockArraySpan = new(memoryBlocksSpan[blockIndex]); - itemIndex0 = 0; - itemIndex1 = midPoint; + itemIndex = 0; } } + + 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 { while (blockIndex < lastBlockIndex) { - if (blockArraySpan[itemIndex0++] == null) + if (blockArraySpan[itemIndex] == null) { return true; } - if (blockArraySpan[itemIndex1++] == null) + if (itemIndex + 1 < blockSize && blockArraySpan[itemIndex + 1] == null) { 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++; - if (blockIndex == lastBlockIndex) - { - if (_nextItemIndex == 0) - { - return false; - } - - midPoint = ((_nextItemIndex - 1) / 2) + ((_nextItemIndex - 1) % 2); - } - blockArraySpan = new(memoryBlocksSpan[blockIndex]); - itemIndex0 = 0; - itemIndex1 = 0; + itemIndex = 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; - - //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); diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 7cff822..9a1fdc4 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -85,6 +85,53 @@ namespace Recyclable.CollectionsTests _ = 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(); + + // Validate + _ = list.Contains(0).Should().BeFalse(); + _ = list.Contains(1).Should().BeFalse(); + } + + [Fact] public void CopyToShouldCopyAllItems() { From f00849614f7e860229f5097a8e8de6ae2b48f833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Feb 2023 18:59:34 +0100 Subject: [PATCH 55/57] RecyclableList.IndexOf reworked for performance improvement. --- .../RecyclableCollectionsBenchmarks.List.cs | 16 +- ...bleCollectionsBenchmarks.RecyclableList.cs | 16 +- Recyclable.Collections/RecyclableList.cs | 224 +++++++++++++++++- 3 files changed, 247 insertions(+), 9 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index ee76fc0..1d4b6e5 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -112,16 +112,28 @@ namespace Recyclable.Collections.Benchmarks DoNothing(data.Count); } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Contains() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = 100_000; + var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[i])); } } + + [Benchmark(Baseline = true)] + public void List_IndexOf() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 6ca2add..1bf5c35 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -78,16 +78,28 @@ namespace Recyclable.Collections.Benchmarks DoNothing(data.LongCount); } - [Benchmark] + //[Benchmark] public void RecyclableList_Contains() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = 100_000; + var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[i])); } } + + [Benchmark] + public void RecyclableList_IndexOf() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 00cbd24..b650aa9 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,15 +1,12 @@ using System.Buffers; using System.Collections; -using System.Linq; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Security.Cryptography.X509Certificates; namespace Recyclable.Collections { public class RecyclableList : IDisposable, IList { + private const int ItemNotFoundIndex = -1; private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; @@ -733,7 +730,224 @@ namespace Recyclable.Collections public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(_longCount, item, _equalityComparer); + public int IndexOf(T item) + { + if (_longCount == 0) + { + return ItemNotFoundIndex; + } + + int blockSize = _blockSize; + int itemIndex = 0; + int blockIndex = 0; + int lastBlockIndex = 0; + + Span memoryBlocksSpan = new(_memoryBlocks); + Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); + if (item != null) + { + while (blockIndex < lastBlockIndex) + { + if (item.Equals(blockArraySpan[itemIndex])) + { + return itemIndex; + } + + if (itemIndex + 1 < blockSize && item.Equals(blockArraySpan[itemIndex + 1])) + { + return itemIndex + 1; + } + + if (itemIndex + 2 < blockSize && item.Equals(blockArraySpan[itemIndex + 2])) + { + return itemIndex + 2; + } + + if (itemIndex + 3 < blockSize && item.Equals(blockArraySpan[itemIndex + 3])) + { + return itemIndex + 3; + } + + if (itemIndex + 4 < blockSize && item.Equals(blockArraySpan[itemIndex + 4])) + { + return itemIndex + 4; + } + + if (itemIndex + 5 < blockSize && item.Equals(blockArraySpan[itemIndex + 5])) + { + return itemIndex + 5; + } + + if (itemIndex + 6 < blockSize && item.Equals(blockArraySpan[itemIndex + 6])) + { + return itemIndex + 6; + } + + if (itemIndex + 7 < blockSize && item.Equals(blockArraySpan[itemIndex + 7])) + { + return itemIndex + 7; + } + + itemIndex += 8; + if (itemIndex >= blockSize) + { + blockIndex++; + blockArraySpan = new(memoryBlocksSpan[blockIndex]); + itemIndex = 0; + } + } + + blockArraySpan = new(memoryBlocksSpan[lastBlockIndex]); + var nextItemIndex = _nextItemIndex; + while (itemIndex < nextItemIndex) + { + if (item.Equals(blockArraySpan[itemIndex])) + { + return itemIndex; + } + + if (itemIndex + 1 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 1])) + { + return itemIndex + 1; + } + + if (itemIndex + 2 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 2])) + { + return itemIndex + 2; + } + + if (itemIndex + 3 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 3])) + { + return itemIndex + 3; + } + + if (itemIndex + 4 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 4])) + { + return itemIndex + 4; + } + + if (itemIndex + 5 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 5])) + { + return itemIndex + 5; + } + + if (itemIndex + 6 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 6])) + { + return itemIndex + 6; + } + + if (itemIndex + 7 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 7])) + { + return itemIndex + 7; + } + + itemIndex += 8; + } + } + else + { + while (blockIndex < lastBlockIndex) + { + if (blockArraySpan[itemIndex] == null) + { + return itemIndex; + } + + if (itemIndex + 1 < blockSize && blockArraySpan[itemIndex + 1] == null) + { + return itemIndex + 1; + } + + if (itemIndex + 2 < blockSize && blockArraySpan[itemIndex + 2] == null) + { + return itemIndex + 2; + } + + if (itemIndex + 3 < blockSize && blockArraySpan[itemIndex + 3] == null) + { + return itemIndex + 3; + } + + if (itemIndex + 4 < blockSize && blockArraySpan[itemIndex + 4] == null) + { + return itemIndex + 4; + } + + if (itemIndex + 5 < blockSize && blockArraySpan[itemIndex + 5] == null) + { + return itemIndex + 5; + } + + if (itemIndex + 6 < blockSize && blockArraySpan[itemIndex + 6] == null) + { + return itemIndex + 6; + } + + if (itemIndex + 7 < blockSize && blockArraySpan[itemIndex + 7] == null) + { + return itemIndex + 7; + } + + itemIndex += 8; + if (itemIndex >= blockSize) + { + blockIndex++; + blockArraySpan = new(memoryBlocksSpan[blockIndex]); + itemIndex = 0; + } + } + + blockArraySpan = new(memoryBlocksSpan[lastBlockIndex]); + var nextItemIndex = _nextItemIndex; + while (itemIndex < nextItemIndex) + { + if (blockArraySpan[itemIndex] == null) + { + return itemIndex; + } + + if (itemIndex + 1 < nextItemIndex && blockArraySpan[itemIndex + 1] == null) + { + return itemIndex + 1; + } + + if (itemIndex + 2 < nextItemIndex && blockArraySpan[itemIndex + 2] == null) + { + return itemIndex + 2; + } + + if (itemIndex + 3 < nextItemIndex && blockArraySpan[itemIndex + 3] == null) + { + return itemIndex + 3; + } + + if (itemIndex + 4 < nextItemIndex && blockArraySpan[itemIndex + 4] == null) + { + return itemIndex + 4; + } + + if (itemIndex + 5 < nextItemIndex && blockArraySpan[itemIndex + 5] == null) + { + return itemIndex + 5; + } + + if (itemIndex + 6 < nextItemIndex && blockArraySpan[itemIndex + 6] == null) + { + return itemIndex + 6; + } + + if (itemIndex + 7 < nextItemIndex && blockArraySpan[itemIndex + 7] == null) + { + return itemIndex + 7; + } + + itemIndex += 8; + } + } + + return ItemNotFoundIndex; + } + public void Insert(int index, T item) => throw new NotSupportedException(); public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer); public bool Remove(T item) => throw new NotSupportedException(); From 743e88f0e67f2ce7b317e5cd16df345cf1616062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Feb 2023 19:49:28 +0100 Subject: [PATCH 56/57] RecyclableList.Contains & .IndexOf fixed. Tests modified to validate operations on multiple blocks. --- .../RecyclableCollectionsBenchmarks.List.cs | 4 +- ...bleCollectionsBenchmarks.RecyclableList.cs | 4 +- Recyclable.Collections/RecyclableList.cs | 70 +++++++++---------- .../RecyclableListTests.cs | 2 +- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 1d4b6e5..d1c3a12 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -117,7 +117,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = TestObjectCount; + var dataCount = 100_000; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[i])); @@ -129,7 +129,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = TestObjectCount; + var dataCount = 100_000; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 1bf5c35..b39689b 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -83,7 +83,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount; + var dataCount = 100_000; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[i])); @@ -95,7 +95,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount; + var dataCount = 100_000; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index b650aa9..8bb4db8 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -518,7 +518,7 @@ namespace Recyclable.Collections int blockSize = _blockSize; int itemIndex = 0; int blockIndex = 0; - int lastBlockIndex = 0; + int lastBlockIndex = _lastBlockIndex; Span memoryBlocksSpan = new(_memoryBlocks); Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); @@ -740,7 +740,7 @@ namespace Recyclable.Collections int blockSize = _blockSize; int itemIndex = 0; int blockIndex = 0; - int lastBlockIndex = 0; + int lastBlockIndex = _lastBlockIndex; Span memoryBlocksSpan = new(_memoryBlocks); Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); @@ -750,42 +750,42 @@ namespace Recyclable.Collections { if (item.Equals(blockArraySpan[itemIndex])) { - return itemIndex; + return (blockIndex * blockSize) + itemIndex; } if (itemIndex + 1 < blockSize && item.Equals(blockArraySpan[itemIndex + 1])) { - return itemIndex + 1; + return (blockIndex * blockSize) + itemIndex + 1; } if (itemIndex + 2 < blockSize && item.Equals(blockArraySpan[itemIndex + 2])) { - return itemIndex + 2; + return (blockIndex * blockSize) + itemIndex + 2; } if (itemIndex + 3 < blockSize && item.Equals(blockArraySpan[itemIndex + 3])) { - return itemIndex + 3; + return (blockIndex * blockSize) + itemIndex + 3; } if (itemIndex + 4 < blockSize && item.Equals(blockArraySpan[itemIndex + 4])) { - return itemIndex + 4; + return (blockIndex * blockSize) + itemIndex + 4; } if (itemIndex + 5 < blockSize && item.Equals(blockArraySpan[itemIndex + 5])) { - return itemIndex + 5; + return (blockIndex * blockSize) + itemIndex + 5; } if (itemIndex + 6 < blockSize && item.Equals(blockArraySpan[itemIndex + 6])) { - return itemIndex + 6; + return (blockIndex * blockSize) + itemIndex + 6; } if (itemIndex + 7 < blockSize && item.Equals(blockArraySpan[itemIndex + 7])) { - return itemIndex + 7; + return (blockIndex * blockSize) + itemIndex + 7; } itemIndex += 8; @@ -797,48 +797,48 @@ namespace Recyclable.Collections } } - blockArraySpan = new(memoryBlocksSpan[lastBlockIndex]); + blockArraySpan = new(memoryBlocksSpan[blockIndex]); var nextItemIndex = _nextItemIndex; while (itemIndex < nextItemIndex) { if (item.Equals(blockArraySpan[itemIndex])) { - return itemIndex; + return (blockIndex * blockSize) + itemIndex; } if (itemIndex + 1 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 1])) { - return itemIndex + 1; + return (blockIndex * blockSize) + itemIndex + 1; } if (itemIndex + 2 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 2])) { - return itemIndex + 2; + return (blockIndex * blockSize) + itemIndex + 2; } if (itemIndex + 3 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 3])) { - return itemIndex + 3; + return (blockIndex * blockSize) + itemIndex + 3; } if (itemIndex + 4 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 4])) { - return itemIndex + 4; + return (blockIndex * blockSize) + itemIndex + 4; } if (itemIndex + 5 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 5])) { - return itemIndex + 5; + return (blockIndex * blockSize) + itemIndex + 5; } if (itemIndex + 6 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 6])) { - return itemIndex + 6; + return (blockIndex * blockSize) + itemIndex + 6; } if (itemIndex + 7 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 7])) { - return itemIndex + 7; + return (blockIndex * blockSize) + itemIndex + 7; } itemIndex += 8; @@ -850,42 +850,42 @@ namespace Recyclable.Collections { if (blockArraySpan[itemIndex] == null) { - return itemIndex; + return (blockIndex * blockSize) + itemIndex; } if (itemIndex + 1 < blockSize && blockArraySpan[itemIndex + 1] == null) { - return itemIndex + 1; + return (blockIndex * blockSize) + itemIndex + 1; } if (itemIndex + 2 < blockSize && blockArraySpan[itemIndex + 2] == null) { - return itemIndex + 2; + return (blockIndex * blockSize) + itemIndex + 2; } if (itemIndex + 3 < blockSize && blockArraySpan[itemIndex + 3] == null) { - return itemIndex + 3; + return (blockIndex * blockSize) + itemIndex + 3; } if (itemIndex + 4 < blockSize && blockArraySpan[itemIndex + 4] == null) { - return itemIndex + 4; + return (blockIndex * blockSize) + itemIndex + 4; } if (itemIndex + 5 < blockSize && blockArraySpan[itemIndex + 5] == null) { - return itemIndex + 5; + return (blockIndex * blockSize) + itemIndex + 5; } if (itemIndex + 6 < blockSize && blockArraySpan[itemIndex + 6] == null) { - return itemIndex + 6; + return (blockIndex * blockSize) + itemIndex + 6; } if (itemIndex + 7 < blockSize && blockArraySpan[itemIndex + 7] == null) { - return itemIndex + 7; + return (blockIndex * blockSize) + itemIndex + 7; } itemIndex += 8; @@ -903,42 +903,42 @@ namespace Recyclable.Collections { if (blockArraySpan[itemIndex] == null) { - return itemIndex; + return (blockIndex * blockSize) + itemIndex; } if (itemIndex + 1 < nextItemIndex && blockArraySpan[itemIndex + 1] == null) { - return itemIndex + 1; + return (blockIndex * blockSize) + itemIndex + 1; } if (itemIndex + 2 < nextItemIndex && blockArraySpan[itemIndex + 2] == null) { - return itemIndex + 2; + return (blockIndex * blockSize) + itemIndex + 2; } if (itemIndex + 3 < nextItemIndex && blockArraySpan[itemIndex + 3] == null) { - return itemIndex + 3; + return (blockIndex * blockSize) + itemIndex + 3; } if (itemIndex + 4 < nextItemIndex && blockArraySpan[itemIndex + 4] == null) { - return itemIndex + 4; + return (blockIndex * blockSize) + itemIndex + 4; } if (itemIndex + 5 < nextItemIndex && blockArraySpan[itemIndex + 5] == null) { - return itemIndex + 5; + return (blockIndex * blockSize) + itemIndex + 5; } if (itemIndex + 6 < nextItemIndex && blockArraySpan[itemIndex + 6] == null) { - return itemIndex + 6; + return (blockIndex * blockSize) + itemIndex + 6; } if (itemIndex + 7 < nextItemIndex && blockArraySpan[itemIndex + 7] == null) { - return itemIndex + 7; + return (blockIndex * blockSize) + itemIndex + 7; } itemIndex += 8; diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 9a1fdc4..e89378a 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -8,7 +8,7 @@ namespace Recyclable.CollectionsTests private const int _totalObjectCount = 5_000; private static RecyclableList NewRecyclableList => CreateReversedRecyclableList(_testData); private static readonly IEnumerable _testData = Enumerable.Range(1, _totalObjectCount); - private static RecyclableList CreateReversedRecyclableList(IEnumerable source) => new(source); + private static RecyclableList CreateReversedRecyclableList(IEnumerable source) => new(source, 1024); [Fact] public void AddShouldAddItems() From 59defd435c18c7cda0addbb56341cc2a680a0aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Feb 2023 22:58:48 +0100 Subject: [PATCH 57/57] Benchmarks improved. Contains & IndexOf separated into 2 groups: first 1 000 items & last 1 000 items. --- Recyclable.Collections.Benchmarks/Program.cs | 2 +- .../RecyclableCollectionsBenchmarks.List.cs | 36 +- ...bleCollectionsBenchmarks.RecyclableList.cs | 34 +- Recyclable.Collections/RecyclableList.cs | 449 ++---------------- 4 files changed, 104 insertions(+), 417 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index e0e3068..c5afe52 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -16,7 +16,7 @@ internal class Program benchmark.Setup(); foreach (var _ in Enumerable.Range(1, 1000)) { - benchmark.RecyclableList_Contains(); + //benchmark.RecyclableList_Contains(); //benchmark.RecyclableArrayList_AddRange_WithCapacity(); //benchmark.RecyclableArrayList_Add_WithCapacity(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index d1c3a12..b6cb1a4 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -112,28 +112,52 @@ namespace Recyclable.Collections.Benchmarks DoNothing(data.Count); } - //[Benchmark(Baseline = true)] - public void List_Contains() + [Benchmark(Baseline = true)] + public void List_Contains_1_000_FirstItems() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = 100_000; + var dataCount = 1_000; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[i])); } } - [Benchmark(Baseline = true)] - public void List_IndexOf() + [Benchmark] + public void List_Contains_1_000_LastItems() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = 100_000; + var dataCount = 1_000; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = true)] + public void List_IndexOf_1_000_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = 1_000; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); } } + + //[Benchmark] + public void List_IndexOf_1_000_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = 1_000; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index b39689b..f8fb793 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -78,12 +78,12 @@ namespace Recyclable.Collections.Benchmarks DoNothing(data.LongCount); } - //[Benchmark] - public void RecyclableList_Contains() + [Benchmark] + public void RecyclableList_Contains_1_000_FirstItems() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = 100_000; + var dataCount = 1000; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[i])); @@ -91,15 +91,39 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void RecyclableList_IndexOf() + public void RecyclableList_Contains_1_000_LastItems() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = 100_000; + var dataCount = 1_000; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + //[Benchmark] + public void RecyclableList_IndexOf_1_000_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = 1000; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); } } + + //[Benchmark] + public void RecyclableList_IndexOf_1_000_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = 1_000; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 8bb4db8..5fecbe4 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -515,221 +515,27 @@ namespace Recyclable.Collections return false; } - int blockSize = _blockSize; - int itemIndex = 0; - int blockIndex = 0; - int lastBlockIndex = _lastBlockIndex; - + int itemIndex, blockIndex = 0, lastBlockIndex = _lastBlockIndex, blockSize = _blockSize; Span memoryBlocksSpan = new(_memoryBlocks); - Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); - if (item != null) + while (blockIndex < lastBlockIndex) { - while (blockIndex < lastBlockIndex) + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) { - if (item.Equals(blockArraySpan[itemIndex])) - { - return true; - } - - 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; - } - - 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++; - blockArraySpan = new(memoryBlocksSpan[blockIndex]); - itemIndex = 0; - } + return true; } - 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 - { - while (blockIndex < lastBlockIndex) - { - if (blockArraySpan[itemIndex] == null) - { - return true; - } - - if (itemIndex + 1 < blockSize && blockArraySpan[itemIndex + 1] == null) - { - return true; - } - - 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++; - blockArraySpan = new(memoryBlocksSpan[blockIndex]); - itemIndex = 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; - } + blockIndex++; } - return false; + return Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _nextItemIndex) >= 0; } public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) { if (_longCount == 0) @@ -737,219 +543,52 @@ namespace Recyclable.Collections return ItemNotFoundIndex; } - int blockSize = _blockSize; - int itemIndex = 0; - int blockIndex = 0; - int lastBlockIndex = _lastBlockIndex; - + int itemIndex, blockIndex = 0, lastBlockIndex = _lastBlockIndex, blockSize = _blockSize; Span memoryBlocksSpan = new(_memoryBlocks); - Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); - if (item != null) + while (blockIndex < lastBlockIndex) { - while (blockIndex < lastBlockIndex) + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) { - if (item.Equals(blockArraySpan[itemIndex])) - { - return (blockIndex * blockSize) + itemIndex; - } - - if (itemIndex + 1 < blockSize && item.Equals(blockArraySpan[itemIndex + 1])) - { - return (blockIndex * blockSize) + itemIndex + 1; - } - - if (itemIndex + 2 < blockSize && item.Equals(blockArraySpan[itemIndex + 2])) - { - return (blockIndex * blockSize) + itemIndex + 2; - } - - if (itemIndex + 3 < blockSize && item.Equals(blockArraySpan[itemIndex + 3])) - { - return (blockIndex * blockSize) + itemIndex + 3; - } - - if (itemIndex + 4 < blockSize && item.Equals(blockArraySpan[itemIndex + 4])) - { - return (blockIndex * blockSize) + itemIndex + 4; - } - - if (itemIndex + 5 < blockSize && item.Equals(blockArraySpan[itemIndex + 5])) - { - return (blockIndex * blockSize) + itemIndex + 5; - } - - if (itemIndex + 6 < blockSize && item.Equals(blockArraySpan[itemIndex + 6])) - { - return (blockIndex * blockSize) + itemIndex + 6; - } - - if (itemIndex + 7 < blockSize && item.Equals(blockArraySpan[itemIndex + 7])) - { - return (blockIndex * blockSize) + itemIndex + 7; - } - - itemIndex += 8; - if (itemIndex >= blockSize) - { - blockIndex++; - blockArraySpan = new(memoryBlocksSpan[blockIndex]); - itemIndex = 0; - } + return (blockIndex * blockSize) + itemIndex; } - blockArraySpan = new(memoryBlocksSpan[blockIndex]); - var nextItemIndex = _nextItemIndex; - while (itemIndex < nextItemIndex) - { - if (item.Equals(blockArraySpan[itemIndex])) - { - return (blockIndex * blockSize) + itemIndex; - } - - if (itemIndex + 1 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 1])) - { - return (blockIndex * blockSize) + itemIndex + 1; - } - - if (itemIndex + 2 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 2])) - { - return (blockIndex * blockSize) + itemIndex + 2; - } - - if (itemIndex + 3 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 3])) - { - return (blockIndex * blockSize) + itemIndex + 3; - } - - if (itemIndex + 4 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 4])) - { - return (blockIndex * blockSize) + itemIndex + 4; - } - - if (itemIndex + 5 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 5])) - { - return (blockIndex * blockSize) + itemIndex + 5; - } - - if (itemIndex + 6 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 6])) - { - return (blockIndex * blockSize) + itemIndex + 6; - } - - if (itemIndex + 7 < nextItemIndex && item.Equals(blockArraySpan[itemIndex + 7])) - { - return (blockIndex * blockSize) + itemIndex + 7; - } - - itemIndex += 8; - } - } - else - { - while (blockIndex < lastBlockIndex) - { - if (blockArraySpan[itemIndex] == null) - { - return (blockIndex * blockSize) + itemIndex; - } - - if (itemIndex + 1 < blockSize && blockArraySpan[itemIndex + 1] == null) - { - return (blockIndex * blockSize) + itemIndex + 1; - } - - if (itemIndex + 2 < blockSize && blockArraySpan[itemIndex + 2] == null) - { - return (blockIndex * blockSize) + itemIndex + 2; - } - - if (itemIndex + 3 < blockSize && blockArraySpan[itemIndex + 3] == null) - { - return (blockIndex * blockSize) + itemIndex + 3; - } - - if (itemIndex + 4 < blockSize && blockArraySpan[itemIndex + 4] == null) - { - return (blockIndex * blockSize) + itemIndex + 4; - } - - if (itemIndex + 5 < blockSize && blockArraySpan[itemIndex + 5] == null) - { - return (blockIndex * blockSize) + itemIndex + 5; - } - - if (itemIndex + 6 < blockSize && blockArraySpan[itemIndex + 6] == null) - { - return (blockIndex * blockSize) + itemIndex + 6; - } - - if (itemIndex + 7 < blockSize && blockArraySpan[itemIndex + 7] == null) - { - return (blockIndex * blockSize) + itemIndex + 7; - } - - itemIndex += 8; - if (itemIndex >= blockSize) - { - blockIndex++; - blockArraySpan = new(memoryBlocksSpan[blockIndex]); - itemIndex = 0; - } - } - - blockArraySpan = new(memoryBlocksSpan[lastBlockIndex]); - var nextItemIndex = _nextItemIndex; - while (itemIndex < nextItemIndex) - { - if (blockArraySpan[itemIndex] == null) - { - return (blockIndex * blockSize) + itemIndex; - } - - if (itemIndex + 1 < nextItemIndex && blockArraySpan[itemIndex + 1] == null) - { - return (blockIndex * blockSize) + itemIndex + 1; - } - - if (itemIndex + 2 < nextItemIndex && blockArraySpan[itemIndex + 2] == null) - { - return (blockIndex * blockSize) + itemIndex + 2; - } - - if (itemIndex + 3 < nextItemIndex && blockArraySpan[itemIndex + 3] == null) - { - return (blockIndex * blockSize) + itemIndex + 3; - } - - if (itemIndex + 4 < nextItemIndex && blockArraySpan[itemIndex + 4] == null) - { - return (blockIndex * blockSize) + itemIndex + 4; - } - - if (itemIndex + 5 < nextItemIndex && blockArraySpan[itemIndex + 5] == null) - { - return (blockIndex * blockSize) + itemIndex + 5; - } - - if (itemIndex + 6 < nextItemIndex && blockArraySpan[itemIndex + 6] == null) - { - return (blockIndex * blockSize) + itemIndex + 6; - } - - if (itemIndex + 7 < nextItemIndex && blockArraySpan[itemIndex + 7] == null) - { - return (blockIndex * blockSize) + itemIndex + 7; - } - - itemIndex += 8; - } + blockIndex++; } - return ItemNotFoundIndex; + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _nextItemIndex); + return itemIndex >= 0 + ? (blockIndex * blockSize) + itemIndex + : ItemNotFoundIndex; } public void Insert(int index, T item) => throw new NotSupportedException(); - public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer); + public long LongIndexOf(T item) + { + if (_longCount == 0) + { + return ItemNotFoundIndex; + } + + int itemIndex, blockIndex = 0, lastBlockIndex = _lastBlockIndex, blockSize = _blockSize; + Span memoryBlocksSpan = new(_memoryBlocks); + while (blockIndex < lastBlockIndex) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return ((long)blockIndex * blockSize) + itemIndex; + } + + blockIndex++; + } + + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _nextItemIndex); + return itemIndex >= 0 + ? ((long)blockIndex * blockSize) + itemIndex + : ItemNotFoundIndex; + } + public bool Remove(T item) => throw new NotSupportedException(); public void RemoveBlock(int index) {