From 93b8dc5317fc7a2ce2b5bea34432b64d1ca615e5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 7 Jan 2023 20:52:01 +0100 Subject: [PATCH 001/305] Initial version of RecyclableBlockList implemented. Tests switched to xUnit in hope to avoid stability issues. --- .../DelegateVsComparerBenchmark.cs | 1 - .../ListVsLinkedListBenchmark.cs | 160 ++++++--- Recyclable.Collections.Benchmarks/Program.cs | 2 +- Recyclable.Collections/ListExtensions.cs | 93 +++++ .../Recyclable.Collections.csproj | 2 +- Recyclable.Collections/RecyclableBlockList.cs | 334 ++++++++++++++++++ Recyclable.Collections/RecyclableDefaults.cs | 1 + Recyclable.Collections/RecyclableList.cs | 96 +++-- .../RecyclableListExtensions.cs | 44 ++- Recyclable.Collections/RecyclableQueue.cs | 174 +++++---- .../Recyclable.CollectionsTests.csproj | 8 +- .../RecyclableBlockListTests.cs | 191 ++++++++++ .../RecyclableListTests.cs | 35 +- .../RecyclableQueueTests.cs | 25 +- .../RecyclableSortedListTests.cs | 11 +- .../RecyclableStackTests.cs | 18 +- Recyclable.CollectionsTests/Usings.cs | 2 +- 17 files changed, 969 insertions(+), 228 deletions(-) create mode 100644 Recyclable.Collections/RecyclableBlockList.cs create mode 100644 Recyclable.CollectionsTests/RecyclableBlockListTests.cs diff --git a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs index c438886..904f509 100644 --- a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs +++ b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs @@ -1,6 +1,5 @@ using BenchmarkDotNet.Attributes; using MiscUtil; -using MiscUtil.Extensions; namespace Recyclable.Collections.Benchmarks { diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs index 79d6b71..c43858e 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs @@ -5,9 +5,23 @@ namespace Recyclable.Collections.Benchmarks [MemoryDiagnoser] public class ListVsLinkedListBenchmark { - private const int _objectCount = 1_000_000; - private const int _blockSize = 100_000; - private static readonly object[] _testObjects = Enumerable.Range(1, _objectCount).Select(x => new object()).ToArray(); + //[Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + public int TestObjectCount = 1_000_000; + public int BlockSize => TestObjectCount switch + { + 0 => 1, + > 0 and <= 10 => TestObjectCount, + _ => TestObjectCount / 10 + }; + + private object[]? _testObjects; + + public ListVsLinkedListBenchmark() + { + _ = TestObjects; + } + + private object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); private static void DoNothing(T obj) { @@ -17,10 +31,12 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void Array() { - var list = new object[_testObjects.LongLength]; - for (var i = 0; i < _testObjects.Length; i++) + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new object[dataCount]; + for (var i = 0; i < dataCount; i++) { - list[i] = _testObjects[i]; + list[i] = data[i]; } for (var i = 0; i < list.LongLength; i++) @@ -29,18 +45,39 @@ namespace Recyclable.Collections.Benchmarks } } + [Benchmark] + public void RecyclableBlockList() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableBlockList(initialCapacity: dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + + for (var i = 0; i < list.LongCount; i++) + { + DoNothing(list[i]); + } + + list.Clear(); + } + [Benchmark(Baseline = true)] public void RecyclableList() { - using var list = new RecyclableList(_blockSize); - for (var i = 0; i < _testObjects.LongLength; i++) + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(BlockSize, initialCapacity: dataCount); + for (var i = 0; i < dataCount; i++) { - list.Add(_testObjects[i]); + list.Add(data[i]); } - for (var i = 0; i < list.Count; i++) + for (var i = 0; i < list.LongCount; i++) { - DoNothing(_testObjects[i]); + DoNothing(list[i]); } list.Clear(); @@ -49,10 +86,12 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void List() { - var list = new List(); - for (var i = 0; i < _testObjects.LongLength; i++) + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(capacity: dataCount); + for (var i = 0; i < dataCount; i++) { - list.Add(_testObjects[i]); + list.Add(data[i]); } for (var i = 0; i < list.Count; i++) @@ -66,10 +105,12 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void LinkedList() { + var data = TestObjects; + var dataCount = TestObjectCount; var list = new LinkedList(); - for (var i = 0; i < _testObjects.LongLength; i++) + for (var i = 0; i < dataCount; i++) { - _ = list.AddLast(_testObjects[i]); + _ = list.AddLast(data[i]); } foreach (var node in list) @@ -80,18 +121,20 @@ namespace Recyclable.Collections.Benchmarks list.Clear(); } - [Benchmark] + //[Benchmark] public void RecyclableQueue() { - var list = new RecyclableQueue(_blockSize); - for (var i = 0; i < _testObjects.LongLength; i++) + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new RecyclableQueue(BlockSize); + for (var i = 0; i < dataCount; i++) { - list.Enqueue(_testObjects[i]); + list.Enqueue(data[i]); } - foreach (var node in list) + while (list.LongCount > 0) { - DoNothing(node); + DoNothing(list.Dequeue()); } list.Clear(); @@ -100,15 +143,17 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void Queue() { - var list = new Queue(); - for (var i = 0; i < _testObjects.LongLength; i++) + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new Queue(capacity: dataCount); + for (var i = 0; i < dataCount; i++) { - list.Enqueue(_testObjects[i]); + list.Enqueue(data[i]); } - foreach (var node in list) + while (list.Count > 0) { - DoNothing(node); + DoNothing(list.Dequeue()); } list.Clear(); @@ -117,16 +162,17 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void PriorityQueue() { - var list = new PriorityQueue(); - for (var i = 0; i < _testObjects.LongLength; i++) + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new PriorityQueue(initialCapacity: dataCount); + for (var i = 0; i < dataCount; i++) { - list.Enqueue(_testObjects[i], i); + list.Enqueue(data[i], i); } while (list.Count > 0) { - var node = list.Dequeue(); - DoNothing(node); + DoNothing(list.Dequeue()); } list.Clear(); @@ -135,18 +181,20 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void RecyclableSortedList_InUpdateMode() { - var list = new RecyclableSortedList(_blockSize); + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new RecyclableSortedList(BlockSize); list.BeginUpdate(); - for (var i = 0; i < _testObjects.LongLength; i++) + for (var i = 0; i < dataCount; i++) { - list.Add(i, _testObjects[i]); + list.Add(i, data[i]); } list.EndUpdate(); for (var i = 0; i < list.LongCount; i++) { - DoNothing(_testObjects[i]); + DoNothing(list[i]); } list.Clear(); @@ -155,15 +203,17 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void RecyclableSortedList() { - var list = new RecyclableSortedList(_blockSize); - for (var i = 0; i < _testObjects.LongLength; i++) + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new RecyclableSortedList(BlockSize); + for (var i = 0; i < dataCount; i++) { - list.Add(i, _testObjects[i]); + list.Add(i, data[i]); } for (var i = 0; i < list.LongCount; i++) { - DoNothing(_testObjects[i]); + DoNothing(list[i]); } list.Clear(); @@ -172,15 +222,17 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void SortedList() { + var data = TestObjects; + var dataCount = TestObjectCount; var list = new SortedList(); - for (var i = 0; i < _testObjects.LongLength; i++) + for (var i = 0; i < dataCount; i++) { - list.Add(i, _testObjects[i]); + list.Add(i, data[i]); } for (var i = 0; i < list.Count; i++) { - DoNothing(_testObjects[i]); + DoNothing(list[i]); } list.Clear(); @@ -189,15 +241,17 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void RecyclableStack() { - var list = new RecyclableStack(_blockSize); - for (var i = 0; i < _testObjects.LongLength; i++) + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new RecyclableStack(BlockSize); + for (var i = 0; i < dataCount; i++) { - list.Push(_testObjects[i]); + list.Push(data[i]); } - foreach (var node in list) + while (list.LongCount > 0) { - DoNothing(node); + DoNothing(list.Pop()); } list.Clear(); @@ -206,15 +260,17 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void Stack() { + var data = TestObjects; + var dataCount = TestObjectCount; var list = new Stack(); - for (var i = 0; i < _testObjects.LongLength; i++) + for (var i = 0; i < dataCount; i++) { - list.Push(_testObjects[i]); + list.Push(data[i]); } - foreach (var node in list) + while (list.Count > 0) { - DoNothing(node); + DoNothing(list.Pop()); } list.Clear(); diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 68c401c..e35ca2b 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -5,4 +5,4 @@ BenchmarkRunner.Run(); //BenchmarkRunner.Run(); //var tests = new ListVsLinkedListBenchmark(); -//tests.RecyclableList(); \ No newline at end of file +//tests.RecyclableBlockList(); diff --git a/Recyclable.Collections/ListExtensions.cs b/Recyclable.Collections/ListExtensions.cs index ab8dafa..58f034b 100644 --- a/Recyclable.Collections/ListExtensions.cs +++ b/Recyclable.Collections/ListExtensions.cs @@ -6,6 +6,79 @@ namespace Recyclable.Collections // It was only beautified for .Net 6.0+. public static class ListExtensions { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains(this RecyclableBlockList arrays, T item) => arrays.Any(x => x.Contains(item)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyTo(this RecyclableBlockList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + { + Span arrayMemory = destinationArray.AsSpan(); + arrayMemory = arrayMemory[destinationArrayIndex..]; + int startingArrayIndex = (int)(startingIndex / blockSize); + startingIndex %= blockSize; + if (arrays.Count > 0) + { + ReadOnlySpan sourceMemory = arrays[0].AsSpan(); + var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); + if (arrays.Count == 1) + { + maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); + } + + sourceMemory = sourceMemory[(int)startingIndex..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + arrayMemory = arrayMemory[maxToCopy..]; + } + + for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Count - 1 && arrayMemory.Length > 0; arrayIdx++) + { + ReadOnlySpan sourceMemory = arrays[arrayIdx].AsSpan(); + var maxToCopy = Math.Min(blockSize, arrayMemory.Length); + sourceMemory = sourceMemory[..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + arrayMemory = arrayMemory[maxToCopy..]; + } + + if (arrays.Count > 1 && arrayMemory.Length > 0) + { + ReadOnlySpan sourceMemory = arrays[^1].AsSpan(); + var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length); + sourceMemory = sourceMemory[..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable Enumerate(this RecyclableBlockList arrays, int chunkSize, long totalCount) + { + long currentCount = 0; + for (var arrayIdx = 0; (arrayIdx < arrays.Count) && (currentCount < totalCount); arrayIdx++) + { + var array = arrays[arrayIdx]; + currentCount += Math.Min(chunkSize, totalCount); + switch (currentCount < totalCount) + { + case true: + for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) + { + yield return array[valueIdx]; + } + + break; + + case false: + var partialCount = (int)(totalCount % chunkSize); + int maxCount = partialCount > 0 ? partialCount : chunkSize; + for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) + { + yield return array[valueIdx]; + } + + break; + } + } + } + public static void InsertionSort(this IList values, int startIndex, int endIndex, IComparer comparer) { var left = startIndex; @@ -29,6 +102,26 @@ namespace Recyclable.Collections { values.InsertionSort(Comparer.Default); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long LongIndexOf(this RecyclableBlockList arrays, int blockSize, T item, IEqualityComparer comparer) + { + for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + { + var array = arrays[arrayIdx]; + Span arrayMemory = array.AsSpan(); + for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++) + { + if (comparer.Equals(arrayMemory[memoryIdx], item)) + { + return (arrayIdx * (long)blockSize) + memoryIdx; + } + } + } + + return -1; + } + public static void QuickSort(this IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) { var range = (startIndex, endIndex); diff --git a/Recyclable.Collections/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj index b2a40c8..c7aa76b 100644 --- a/Recyclable.Collections/Recyclable.Collections.csproj +++ b/Recyclable.Collections/Recyclable.Collections.csproj @@ -7,7 +7,7 @@ enable true Recyclable.Collections - 0.0.2 + 0.0.3 Marek Lemańczyk N/A Recyclable.Collections diff --git a/Recyclable.Collections/RecyclableBlockList.cs b/Recyclable.Collections/RecyclableBlockList.cs new file mode 100644 index 0000000..e2769fd --- /dev/null +++ b/Recyclable.Collections/RecyclableBlockList.cs @@ -0,0 +1,334 @@ +using System.Collections; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections +{ + public class RecyclableBlockList : IEnumerable, IDisposable + { + private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + private bool _disposedValue; + + + private void CopyItems(long startingIndex, long count, ref T[] destArray, long offset = 1) + { + for (long destItemIndex = startingIndex, sourceItemIndex = offset; destItemIndex < startingIndex + count; destItemIndex++, sourceItemIndex++) + { + destArray[destItemIndex] = _memory[sourceItemIndex]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T GetItem(in T[] arrays, in long index) => arrays[index]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void InternalRemoveAt(long index) + { + long oldCount = LongCount; + long oldCountMinus1 = oldCount - 1; + if (index == oldCountMinus1) + { + LongCount--; + return; + } + + if (index >= oldCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + CopyItems(index, oldCountMinus1, ref _memory); + + LongCount--; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long LongIndexOf(in T[] memory, in long itemCount, T? itemToFind, IEqualityComparer equalityComparer) + { + for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) + { + var item = memory[itemIdx]; + if (equalityComparer.Equals(item, itemToFind)) + { + return itemIdx; + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetItem(in T[] arrays, in long index, in T value) => arrays[index] = value; + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private T[] SetNewLength(long newSize) + { + var source = _memory; + T[] newArray = newSize.RentArrayFromPool(); + if (newArray.LongLength >= source.LongLength) + { + source.CopyTo(newArray, 0); + } + else + { + CopyItems(0, newArray.LongLength, ref newArray, 0); + } + + source.ReturnToPool(); + return newArray; + } + + protected T[] _memory = Array.Empty(); + + public RecyclableBlockList(long initialCapacity = RecyclableDefaults.Capacity) + { + //EnsureCapacity(capacity); + if (_capacity < initialCapacity) + { + long newCapacity; + switch (_capacity > 0) + { + case true: + newCapacity = _capacity * 2; + while (newCapacity < initialCapacity) + { + newCapacity *= 2; + } + + break; + + case false: + newCapacity = initialCapacity; + break; + } + + _capacity = newCapacity; + _memory = SetNewLength(newCapacity); + } + } + + public RecyclableBlockList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) + { + //EnsureCapacity(capacity); + if (_capacity < initialCapacity) + { + long newCapacity; + switch (_capacity > 0) + { + case true: + newCapacity = _capacity * 2; + while (newCapacity < initialCapacity) + { + newCapacity *= 2; + } + + break; + + case false: + newCapacity = initialCapacity; + break; + } + + _capacity = newCapacity; + _memory = SetNewLength(newCapacity); + } + + foreach (var item in source) + { + Add(item); + } + } + + public T this[long index] + { + get => GetItem(_memory, index); + set => SetItem(_memory, index, value); + } + + public T this[int index] + { + get => GetItem(_memory, index); + set => SetItem(_memory, index, value); + } + + private long _capacity; + public long Capacity + { + get => _capacity; + set + { + if (_capacity != value) + { + _memory = SetNewLength(value); + _capacity = value; + } + } + } + public int Count => (int)LongCount; + public long LongCount { get; set; } + public bool IsReadOnly { get; } = false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(in T item) + { + //EnsureCapacity(LongCount + 1); + long requestedCapacity = LongCount + 1; + if (_capacity < requestedCapacity) + { + long newCapacity; + switch (_capacity > 0) + { + case true: + newCapacity = _capacity * 2; + while (newCapacity < requestedCapacity) + { + newCapacity *= 2; + } + + break; + + case false: + newCapacity = requestedCapacity; + break; + } + + _capacity = newCapacity; + _memory = SetNewLength(newCapacity); + } + + _memory[LongCount] = item; + LongCount++; + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public void EnsureCapacity(long requestedCapacity) + //{ + // if (Capacity < requestedCapacity) + // { + // long newCapacity; + // switch (Capacity > 0) + // { + // case true: + // newCapacity = Capacity * 2; + // while (newCapacity < requestedCapacity) + // { + // newCapacity *= 2; + // } + + // break; + + // case false: + // newCapacity = requestedCapacity; + // break; + // } + + // _capacity = newCapacity; + // _memory = SetNewLength(newCapacity); + // } + //} + + public void Clear() + { + LongCount = 0; + } + + public bool Contains(T item) => _memory.Contains(item); + + public void CopyTo(T[] array, int arrayIndex) => _memory.CopyTo(array, arrayIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable Enumerate() + { + for (var i = 0; i < LongCount; i++) + { + yield return _memory[i]; + } + } + + public int IndexOf(T item) => (int)LongIndexOf(_memory, LongCount, item, _equalityComparer); + + public void Insert(int index, T item) + { + long requestedCapacity = LongCount + 1; + if (Capacity < requestedCapacity) + { + long newCapacity; + switch (Capacity > 0) + { + case true: + newCapacity = Capacity * 2; + while (newCapacity < requestedCapacity) + { + newCapacity *= 2; + } + + break; + + case false: + newCapacity = requestedCapacity; + break; + } + + _capacity = newCapacity; + _memory = SetNewLength(newCapacity); + } + + for (var toMoveIdx = LongCount - 1; toMoveIdx >= index; toMoveIdx--) + { + _memory[toMoveIdx + 1] = _memory[toMoveIdx]; + } + + _memory[index] = item; + LongCount++; + } + + public long LongIndexOf(T item) => LongIndexOf(_memory, LongCount, item, _equalityComparer); + + public bool Remove(T item) + { + var itemIdx = LongIndexOf(item); + if (itemIdx >= 0) + { + RemoveAt(itemIdx); + return true; + } + + return false; + } + + public void RemoveAt(int index) => InternalRemoveAt(index); + public void RemoveAt(long index) => InternalRemoveAt(index); + + public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Enumerate().GetEnumerator(); + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + Clear(); + _memory.ReturnToPool(); + Capacity = 0; + } + + _disposedValue = true; + } + } + + // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~RecyclableList() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index e726503..8c9b5d1 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -3,5 +3,6 @@ public class RecyclableDefaults { public const int BlockSize = 10_240; + public const int Capacity = 8; } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 3389241..e76f9d0 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -7,17 +7,38 @@ namespace Recyclable.Collections { private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; - private readonly List _arrays = new(); + private readonly RecyclableBlockList _memoryBlocks; private bool _disposedValue; - public RecyclableList(int blockSize = RecyclableDefaults.BlockSize) + public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) { BlockSize = blockSize; + initialCapacity ??= 8 * BlockSize; + int additionalArray = initialCapacity % BlockSize > 0 ? 1 : 0; + long targetCapacity = (initialCapacity.Value / BlockSize) + additionalArray; + _memoryBlocks = new(targetCapacity); + Capacity = initialCapacity.Value; + for (var blockIdx = 0; blockIdx < targetCapacity; blockIdx++) + { + _memoryBlocks.Add(BlockSize.RentArrayFromPool()); + } + + //MemoryBlocks.LongCount = MemoryBlocks.Capacity; } - public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize) + public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) { BlockSize = blockSize; + initialCapacity ??= 8 * BlockSize; + int additionalCapacity = initialCapacity % BlockSize > 0 ? 1 : 0; + long targetCapacity = (initialCapacity.Value / BlockSize) + additionalCapacity; + _memoryBlocks = new(targetCapacity); + Capacity = initialCapacity.Value; + for (var blockIdx = 0; blockIdx < targetCapacity; blockIdx++) + { + _memoryBlocks.Add(BlockSize.RentArrayFromPool()); + } + foreach (var item in source) { Add(item); @@ -27,42 +48,47 @@ namespace Recyclable.Collections public int BlockSize { get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetItem(List arrays, in int blockSize, in long index, in T value) + public static void SetItem(RecyclableBlockList arrays, in int blockSize, in long index, in T value) => arrays[(int)(index / blockSize)][index % blockSize] = value; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T GetItem(List arrays, in int blockSize, in long index) + public static T GetItem(RecyclableBlockList arrays, in int blockSize, in long index) => arrays[(int)(index / blockSize)][index % blockSize]; public T this[long index] { - get => GetItem(_arrays, BlockSize, index); - set => SetItem(_arrays, BlockSize, index, value); + //get => GetItem(MemoryBlocks, BlockSize, index); + //set => SetItem(MemoryBlocks, BlockSize, index, value); + get => _memoryBlocks[(int)(index / BlockSize)][index % BlockSize]; + set => _memoryBlocks[(int)(index / BlockSize)][index % BlockSize] = value; } public T this[int index] { - get => GetItem(_arrays, BlockSize, index); - set => SetItem(_arrays, BlockSize, index, value); + //get => GetItem(MemoryBlocks, BlockSize, index); + //set => SetItem(MemoryBlocks, BlockSize, index, value); + get => _memoryBlocks[(int)(index / BlockSize)][index % BlockSize]; + set => _memoryBlocks[(int)(index / BlockSize)][index % BlockSize] = value; } public long Capacity { get; set; } public int Count => (int)LongCount; public long LongCount { get; set; } public bool IsReadOnly { get; } = false; - public int BlockCount => _arrays.Count; + public long BlockCount => _memoryBlocks.LongCount; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { if (LongCount == Capacity) { - T[] newArray = BlockSize.RentArrayFromPool(); - _arrays.Add(newArray); + T[] newMemoryBlock = BlockSize.RentArrayFromPool(); + _memoryBlocks.Add(newMemoryBlock); Capacity += BlockSize; } - this[LongCount] = item; + SetItem(_memoryBlocks, BlockSize, LongCount, item); + //_arrays[_arrays.LongCount - 1][LongCount % BlockSize] = item; LongCount++; } @@ -71,11 +97,11 @@ namespace Recyclable.Collections try { // Remove in reversed order for performance savings - while (_arrays.Count > 0) + while (_memoryBlocks.Count > 0) { try { - RemoveBlock(_arrays.Count - 1); + RemoveBlock(_memoryBlocks.Count - 1); } catch (Exception) { @@ -87,29 +113,47 @@ namespace Recyclable.Collections finally { LongCount = 0; - _arrays.Clear(); + _memoryBlocks.Clear(); } } - public bool Contains(T item) => _arrays.Contains(item); + public bool Contains(T item) => _memoryBlocks.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => _arrays.CopyTo(0, BlockSize, (int)LongCount % BlockSize, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, BlockSize, (int)LongCount % BlockSize, array, arrayIndex); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerator GetEnumerator() => _arrays.Enumerate(BlockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(BlockSize, LongCount).GetEnumerator(); - public int IndexOf(T item) => (int)_arrays.LongIndexOf(BlockSize, item, _equalityComparer); + public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(BlockSize, item, _equalityComparer); public void Insert(int index, T item) => throw new NotSupportedException(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long LongIndexOf(T item) => _arrays.LongIndexOf(BlockSize, item, _equalityComparer); + public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(BlockSize, item, _equalityComparer); public bool Remove(T item) => throw new NotSupportedException(); - public void RemoveAt(int index) => throw new NotSupportedException(); + public void RemoveAt(int index) => RemoveAt(this, index); + public void RemoveAt(long index) => RemoveAt(this, index); - IEnumerator IEnumerable.GetEnumerator() => _arrays.Enumerate(BlockSize, LongCount).GetEnumerator(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void RemoveAt(RecyclableList list, long index) + { + if (index == list.LongCount - 1) + { + list.LongCount--; + if (list.Capacity - list.LongCount == list.BlockSize) + { + list.RemoveBlock(list.BlockCount - 1); + } + } + else + { + throw new NotSupportedException("Only removal of the last element is supported"); + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(BlockSize, LongCount).GetEnumerator(); protected virtual void Dispose(bool disposing) { @@ -138,12 +182,12 @@ namespace Recyclable.Collections GC.SuppressFinalize(this); } - public void RemoveBlock(int index) + public void RemoveBlock(long index) { try { - _arrays[index].ReturnToPool(); - _arrays.RemoveAt(index); + _memoryBlocks[index].ReturnToPool(); + _memoryBlocks.RemoveAt(index); } finally { diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 3a4e145..dbcde99 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -1,12 +1,15 @@ using System.Buffers; +using System.Buffers; using System.Runtime.CompilerServices; namespace Recyclable.Collections { public static class RecyclableListExtensions { + private const int _minPooledArraySize = 100; + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyTo(this List arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + public static void CopyTo(this RecyclableList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) { Span arrayMemory = destinationArray.AsSpan(); arrayMemory = arrayMemory[destinationArrayIndex..]; @@ -45,10 +48,10 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this List arrays, T item) => arrays.Any(x => x.Contains(item)); + public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Enumerate(this List arrays, int chunkSize, long totalCount) + public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) { long currentCount = 0; for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) @@ -83,7 +86,7 @@ namespace Recyclable.Collections => comparer.Compare(value, maxValue) > 0 ? maxValue : value; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this List arrays, int blockSize, T item, IEqualityComparer comparer) + public static long LongIndexOf(this RecyclableList arrays, int blockSize, T item, IEqualityComparer comparer) { for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) { @@ -101,12 +104,6 @@ namespace Recyclable.Collections return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ToArrayIndex(this long index, int blockSize) => (int)(index / blockSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long ToItemIndex(this long index, int blockSize) => index % blockSize; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); @@ -114,18 +111,27 @@ namespace Recyclable.Collections public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this int minSize) => ArrayPool.Shared.Rent(minSize); + public static T[] RentArrayFromPool(this int minSize) => RentArrayFromPool((long)minSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(this T[] array) => ArrayPool.Shared.Return(array); + public static T[] RentArrayFromPool(this long minSize) + { + return minSize switch + { + < _minPooledArraySize => new T[minSize], + <= int.MaxValue => ArrayPool.Shared.Rent((int)minSize), + _ => new T[minSize] + }; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetItem(this List arrays, in int blockSize, in long index, in T value) - => arrays[(int)(index / blockSize)][index % blockSize] = value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T GetItem(this List arrays, in int blockSize, in long index) - => arrays[(int)(index / blockSize)][index % blockSize]; - + public static void ReturnToPool(this T[] array) + { + long arrayLength = array.LongLength; + if (arrayLength is >= _minPooledArraySize and <= int.MaxValue) + { + ArrayPool.Shared.Return(array); + } + } } } diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 46b69eb..0a70082 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -1,5 +1,4 @@ -using System.Buffers; -using System.Collections; +using System.Collections; using System.Runtime.CompilerServices; namespace Recyclable.Collections @@ -7,25 +6,55 @@ namespace Recyclable.Collections public class RecyclableQueue : IRecyclableOwner, IList, IDisposable { private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; - private static readonly IComparer _comparer = Comparer.Default; private bool _disposedValue; + private RecyclableBlockList Memory { get; } - protected List Arrays { get; } - protected long GetAbsoluteIndex(long index) => index + RemovedCount; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T Dequeue(RecyclableQueue queue) + { + if (queue.LongCount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(queue), "There are no items in the queue"); + } + + var toRemove = queue[0]; + queue.RemovedCount++; + queue.LongCount--; + if (queue.Capacity - queue.LongCount == queue.BlockSize) + { + queue.RemoveBlock(0); + queue.RemovedCount -= queue.BlockSize; + } + + return toRemove; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryDequeue(RecyclableQueue queue, out T? item) + { + if (queue.LongCount > 0) + { + item = queue.Dequeue(); + return true; + } + + item = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static long GetAbsoluteIndex(long index, long removedCount) => index + removedCount; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected long GetRelativeIndex(long index) => index - RemovedCount; + protected long RemovedCount { get; set; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T[] RentArrayFromPool(int minSize) => ArrayPool.Shared.Rent(minSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(T[] array) => ArrayPool.Shared.Return(array); - public RecyclableQueue(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize) { BlockSize = blockSize; - Arrays = new(); + Memory = new(1); foreach (var item in source) { Enqueue(item); @@ -35,7 +64,7 @@ namespace Recyclable.Collections public RecyclableQueue(int blockSize = RecyclableDefaults.BlockSize) { BlockSize = blockSize; - Arrays = new(); + Memory = new(1); } public int BlockSize { get; protected set; } @@ -43,31 +72,47 @@ namespace Recyclable.Collections public int Count { - get => (int)LongCount.LimitTo(int.MaxValue, _comparer); + get => (int)LongCount; set => LongCount = value; } public long LongCount { get; set; } public bool IsReadOnly { get; } = false; - public T this[int index] { get => this[(long)index]; set => this[(long)index] = value; } + public T this[int index] + { + get => GetItem(index); + set => SetItem(index, value); + } + public T this[long index] { - get - { - long absoluteIndex = GetAbsoluteIndex(index); - int arrayIndex = absoluteIndex.ToArrayIndex(BlockSize); - long itemIndex = absoluteIndex.ToItemIndex(BlockSize); - return Arrays[arrayIndex][itemIndex]; - } + get => GetItem(index); + set => SetItem(index, value); + } - set - { - long absoluteIndex = GetAbsoluteIndex(index); - int arrayIndex = absoluteIndex.ToArrayIndex(BlockSize); - long itemIndex = absoluteIndex.ToItemIndex(BlockSize); - Arrays[arrayIndex][itemIndex] = value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToArrayIndex(long index, int blockSize) => (int)(index / blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ToItemIndex(long index, int blockSize) => index % blockSize; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetItem(long index, T value) + { + long absoluteIndex = GetAbsoluteIndex(index, RemovedCount); + int arrayIndex = ToArrayIndex(absoluteIndex, BlockSize); + long itemIndex = ToItemIndex(absoluteIndex, BlockSize); + Memory[arrayIndex][itemIndex] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private T GetItem(long index) + { + long absoluteIndex = GetAbsoluteIndex(index, RemovedCount); + int arrayIndex = ToArrayIndex(absoluteIndex, BlockSize); + long itemIndex = ToItemIndex(absoluteIndex, BlockSize); + return Memory[arrayIndex][itemIndex]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -75,33 +120,22 @@ namespace Recyclable.Collections { if (LongCount == Capacity) { - T[] newArray = BlockSize.RentArrayFromPool(); - Arrays.Add(newArray); - Capacity += BlockSize; + AddBlock(); } - var newIndex = LongCount; - // We don't want this[newIndex] set to raise the update event, because - // the count would be wrong. We need to increase it, first. - this[newIndex] = item; + SetItem(LongCount, item); LongCount++; } - - public T Dequeue() + public void AddBlock() { - var toRemove = this[0]; - RemovedCount++; - LongCount--; - if (Capacity - LongCount == BlockSize) - { - RemoveBlock(0); - RemovedCount -= BlockSize; - } - - return toRemove; + T[] newArray = BlockSize.RentArrayFromPool(); + Memory.Add(newArray); + Capacity += BlockSize; } + public T Dequeue() => Dequeue(this); + public void Add(T item) => Enqueue(item); public void Clear() @@ -109,11 +143,11 @@ namespace Recyclable.Collections try { // Remove in reversed order for performance savings - while (Arrays.Count > 0) + while (Memory.Count > 0) { try { - RemoveBlock(Arrays.Count - 1); + RemoveBlock(Memory.Count - 1); } catch (Exception) { @@ -126,46 +160,32 @@ namespace Recyclable.Collections { LongCount = 0; RemovedCount = 0; - Arrays.Clear(); + Memory.Clear(); } } - public bool Contains(T item) => Arrays.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => Arrays.CopyTo(RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerable Enumerate() => Arrays.Enumerate(BlockSize, LongCount); - public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public int IndexOf(T item) => (int)LongIndexOf(item).LimitTo(int.MaxValue, _comparer); - public long LongIndexOf(T item) => GetRelativeIndex(Arrays.LongIndexOf(BlockSize, item, _equalityComparer)); + public bool Contains(T item) => Memory.Contains(item); + public void CopyTo(T[] array, int arrayIndex) => Memory.CopyTo(RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex); + public IEnumerable Enumerate() => Memory.Enumerate(BlockSize, LongCount); + public IEnumerator GetEnumerator() => Memory.Enumerate(BlockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Memory.Enumerate(BlockSize, LongCount).GetEnumerator(); + public int IndexOf(T item) => (int)LongIndexOf(item); + public long LongIndexOf(T item) => GetRelativeIndex(Memory.LongIndexOf(BlockSize, item, _equalityComparer)); public void Insert(int index, T item) => throw new NotSupportedException(); public void RemoveAt(int index) => throw new NotSupportedException(); - - public bool TryDequeue(out T? item) - { - if (LongCount > 0) - { - item = Dequeue(); - return true; - } - - item = default; - return false; - } - - public bool Remove(T item) => throw new NotSupportedException(); + public bool TryDequeue(out T? item) => TryDequeue(this, out item); + public bool Remove(T item) => TryDequeue(out var _); public void RemoveBlock(int index) { try { - ReturnToPool(Arrays[index]); - Arrays.RemoveAt(index); + Memory[index].ReturnToPool(); + Memory.RemoveAt(index); } finally { Capacity -= BlockSize; - LongCount -= BlockSize; + //LongCount -= BlockSize; } } diff --git a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj index 12982dd..4a56fe7 100644 --- a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj +++ b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj @@ -11,12 +11,16 @@ - - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Recyclable.CollectionsTests/RecyclableBlockListTests.cs b/Recyclable.CollectionsTests/RecyclableBlockListTests.cs new file mode 100644 index 0000000..514d0fe --- /dev/null +++ b/Recyclable.CollectionsTests/RecyclableBlockListTests.cs @@ -0,0 +1,191 @@ +using FluentAssertions; +using Recyclable.Collections; + +namespace Recyclable.CollectionsTests +{ + public class RecyclableBlockListTests + { + private readonly static IEnumerable _testData = Enumerable.Range(1, 20); + + [Fact] + public void AddCountShouldSucceed() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableBlockList(); + + // Act + foreach (var item in testData) + { + list.Add(item); + _ = list.LongCount.Should().Be(item); + } + + // Validate + _ = list.Capacity.Should().Be(32); + _ = list.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + + [Fact] + public void InsertAtTheBeginningShouldMoveItems() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableBlockList(); + + // Act + foreach (var item in testData) + { + list.Insert(0, item); + _ = list.LongCount.Should().Be(item); + } + + // Validate + _ = list.Capacity.Should().Be(32); + _ = list.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData.Reverse()) + .And.BeEquivalentTo(testData); + } + + [Fact] + public void LongIndexOfShouldReturnCorrectIndexes() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableBlockList(testData); + + // Act & Validate + foreach (var item in testData) + { + // Act + var index = list.IndexOf(item); + + // Validate + _ = index.Should().Be(item - 1); + } + } + + [Fact] + public void RemoveFromTheBeginningShouldRemoveTheCorrectItem() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableBlockList(testData); + + // Act & Validate + for (var deleted = 1; deleted <= testData.LongLength; deleted++) + { + // Act + _ = list.Remove(deleted).Should().BeTrue(); + + // Validate + _ = list.Should().HaveCount(testData.Length - deleted) + .And.ContainInConsecutiveOrder(testData.Skip(deleted)) + .And.BeEquivalentTo(testData.Skip(deleted)); + } + } + + [Fact] + public void RemoveFromTheEndShouldRemoveTheCorrectItem() + { + // Prepare + var testData = _testData.ToArray(); + + // Act & Validate + var list = new RecyclableBlockList(testData); + for (var deleted = 1; deleted <= testData.LongLength; deleted++) + { + // Act + _ = list.Remove(testData[^deleted]).Should().BeTrue(); + + // Validate + _ = list.Should().HaveCount(testData.Length - deleted) + .And.ContainInConsecutiveOrder(testData.Take(testData.Length - deleted)) + .And.BeEquivalentTo(testData.Take(testData.Length - deleted)); + } + } + + [Fact] + public void RemoveAtFromTheBeginningShouldSucceed() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableBlockList(testData); + + // Act & Validate + for (var deleted = 1; deleted <= testData.LongLength; deleted++) + { + // Act + list.RemoveAt(0); + + // Validate + _ = list.Should().HaveCount(testData.Length - deleted) + .And.ContainInConsecutiveOrder(testData.Skip(deleted)) + .And.BeEquivalentTo(testData.Skip(deleted)); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().Be(32); + } + + [Fact] + public void RemoveAtFromTheEndShouldSucceed() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableBlockList(testData); + + // Act & Validate + for (var deleted = 1; deleted <= testData.LongLength; deleted++) + { + // Act + list.RemoveAt(list.LongCount - 1); + + // Validate + _ = list.Should().HaveCount(testData.Length - deleted) + .And.ContainInConsecutiveOrder(testData.Take(testData.Length - deleted)) + .And.BeEquivalentTo(testData.Take(testData.Length - deleted)); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().Be(32); + } + + [Fact] + public void ConstructorSourceShouldInitializeList() + { + // Prepare + var testData = _testData.ToArray(); + + // Act + var list = new RecyclableBlockList(testData); + + // Validate + _ = list.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + + [Fact] + public void EnumerateShouldYieldAllItemsInCorrectOrder() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableBlockList(testData); + + // Act + var yieldedItems = new List(); + foreach (var item in list) + { + yieldedItems.Add(item); + } + + // Validate + _ = yieldedItems.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + } +} diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 88ab0cb..43bef10 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -3,7 +3,6 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - [TestClass()] public class RecyclableListTests { private const int _totalObjectCount = 5_000; @@ -11,7 +10,7 @@ namespace Recyclable.CollectionsTests private static readonly IEnumerable _testData = Enumerable.Range(1, _totalObjectCount); private static RecyclableList CreateReversedRecyclableList(IEnumerable source) => new(source); - [TestMethod] + [Fact] public void AddShouldAddItems() { // Prepare @@ -33,7 +32,7 @@ namespace Recyclable.CollectionsTests } } - [TestMethod] + [Fact] public void AddShouldAcceptDuplicates() { // Prepare @@ -51,7 +50,7 @@ namespace Recyclable.CollectionsTests _ = list.Should().BeEquivalentTo(testNumbers); } - [TestMethod] + [Fact] public void ConstructorShouldAcceptDuplicates() { // Prepare @@ -65,7 +64,7 @@ namespace Recyclable.CollectionsTests _ = list.Should().BeEquivalentTo(testNumbers); } - [TestMethod] + [Fact] public void InitializeSortClearShouldSucceed() { var expectedList = _testData; @@ -86,7 +85,7 @@ namespace Recyclable.CollectionsTests _ = list.LongCount.Should().Be(0); } - [TestMethod] + [Fact] public void CopyToShouldCopyAllItems() { // Prepare @@ -98,7 +97,7 @@ namespace Recyclable.CollectionsTests _ = actual.Should().BeEquivalentTo(_testData); } - [TestMethod] + [Fact] public void GetEnumeratorShouldYieldAllItems() { // Prepare @@ -116,7 +115,7 @@ namespace Recyclable.CollectionsTests _ = actual.Should().BeEquivalentTo(_testData); } - [TestMethod] + [Fact] public void IndexOfShouldFindTheIndexes() { // Prepare @@ -130,41 +129,37 @@ namespace Recyclable.CollectionsTests } } - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] + [Fact] public void InsertShouldRaiseNotSupportedException() { // Prepare using var list = NewRecyclableList; // Act - list.Insert(0, -1); + _ = Assert.Throws(() => list.Insert(0, -1)); } - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] + [Fact] public void RemoveShouldRaiseNotSupportedException() { // Prepare using var list = NewRecyclableList; // Act - bool removed = list.Remove(_testData.First()); - _ = removed.Should().BeFalse(); + _ = Assert.Throws(() => list.Remove(_testData.First())); } - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] + [Fact] public void RemoveAtShouldRaiseNotSupportedException() { // Prepare using var list = NewRecyclableList; // Act - list.RemoveAt(0); + _ = Assert.Throws(() => list.RemoveAt(0)); } - [TestMethod] + [Fact] public void DisposeShouldSucceed() { // Prepare @@ -177,7 +172,7 @@ namespace Recyclable.CollectionsTests _ = list.LongCount.Should().Be(0L); } - [TestMethod] + [Fact] public void ConsecutiveDisposeShouldSucceed() { // Prepare diff --git a/Recyclable.CollectionsTests/RecyclableQueueTests.cs b/Recyclable.CollectionsTests/RecyclableQueueTests.cs index 29e3924..b09ed28 100644 --- a/Recyclable.CollectionsTests/RecyclableQueueTests.cs +++ b/Recyclable.CollectionsTests/RecyclableQueueTests.cs @@ -3,12 +3,11 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - [TestClass] public class RecyclableQueueTests { private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" }; - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenCreated() { // Act @@ -19,7 +18,7 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(_testData); } - [TestMethod] + [Fact] public void ShouldBeEmptyWhenNotInitialized() { // Act @@ -29,7 +28,7 @@ namespace Recyclable.CollectionsTests _ = list.Should().BeEmpty(); } - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenInitialized() { // Act @@ -48,7 +47,7 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(_testData); } - [TestMethod] + [Fact] public void ShouldBeEmptyAfterClear() { // Prepare @@ -61,7 +60,7 @@ namespace Recyclable.CollectionsTests _ = list.Should().BeEmpty(); } - [TestMethod] + [Fact] public void DequeueShouldReturnItemsInTheSameOrder() { // Prepare @@ -80,11 +79,11 @@ namespace Recyclable.CollectionsTests } // Validate - _ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData); + _ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData) + .And.BeEquivalentTo(_testData); } - [TestMethod] - [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Fact] public void DequeueShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound() { // Prepare @@ -96,10 +95,11 @@ namespace Recyclable.CollectionsTests _ = list.Dequeue(); } - _ = list.Dequeue(); + _ = Assert.Throws(() => list.Dequeue()); + _ = list.LongCount.Should().Be(0); } - [TestMethod] + [Fact] public void GetEnumeratorShouldYieldAllItemsInTheSameOrder() { // Prepare @@ -114,7 +114,8 @@ namespace Recyclable.CollectionsTests } // Validate - _ = actual.Should().ContainInConsecutiveOrder(_testData); + _ = actual.Should().ContainInConsecutiveOrder(_testData) + .And.BeEquivalentTo(_testData); } } } diff --git a/Recyclable.CollectionsTests/RecyclableSortedListTests.cs b/Recyclable.CollectionsTests/RecyclableSortedListTests.cs index 82aecb9..6f72aa2 100644 --- a/Recyclable.CollectionsTests/RecyclableSortedListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableSortedListTests.cs @@ -3,12 +3,11 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - [TestClass()] public class RecyclableSortedListTests { private static readonly (int, string)[] _testData = new[] { (5, "a"), (4, "d"), (3, "c"), (2, "b"), (1, "a") }; - [TestMethod] + [Fact] public void ShouldBeSortedWhenCreated() { // Act @@ -20,7 +19,7 @@ namespace Recyclable.CollectionsTests .And.BeInAscendingOrder((item) => item.Key); } - [TestMethod] + [Fact] public void ShouldBeEmptyWhenNotInitialized() { // Act @@ -30,7 +29,7 @@ namespace Recyclable.CollectionsTests _ = sortedList.Should().BeEmpty(); } - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenInitialized() { // Act @@ -48,7 +47,7 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(_testData); } - [TestMethod] + [Fact] public void ShouldBeSortedWhenUpdateEnds() { // Prepare @@ -71,7 +70,7 @@ namespace Recyclable.CollectionsTests _ = sortedList.Should().BeInAscendingOrder(x => x.Key); } - [TestMethod] + [Fact] public void ShouldBeEmptyAfterClear() { // Prepare diff --git a/Recyclable.CollectionsTests/RecyclableStackTests.cs b/Recyclable.CollectionsTests/RecyclableStackTests.cs index 0946615..9507043 100644 --- a/Recyclable.CollectionsTests/RecyclableStackTests.cs +++ b/Recyclable.CollectionsTests/RecyclableStackTests.cs @@ -3,12 +3,11 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - [TestClass] public class RecyclableStackTests { private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" }; - [TestMethod] + [Fact] public void GetEnumeratorShouldYieldAllItemsInReversedOrder() { // Prepare @@ -26,7 +25,7 @@ namespace Recyclable.CollectionsTests _ = actual.Should().ContainInConsecutiveOrder(_testData.Reverse()); } - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenCreated() { // Act @@ -37,7 +36,7 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(_testData); } - [TestMethod] + [Fact] public void ShouldBeEmptyWhenNotInitialized() { // Act @@ -47,7 +46,7 @@ namespace Recyclable.CollectionsTests _ = sortedList.Should().BeEmpty(); } - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenInitialized() { // Act @@ -66,7 +65,7 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(_testData); } - [TestMethod] + [Fact] public void ShouldBeEmptyAfterClear() { // Prepare @@ -79,7 +78,7 @@ namespace Recyclable.CollectionsTests _ = sortedList.Should().BeEmpty(); } - [TestMethod] + [Fact] public void PopShouldReturnItemsInReversedOrder() { // Prepare @@ -101,8 +100,7 @@ namespace Recyclable.CollectionsTests _ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData.Reverse()); } - [TestMethod] - [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Fact] public void PopShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound() { // Prepare @@ -114,7 +112,7 @@ namespace Recyclable.CollectionsTests _ = list.Dequeue(); } - _ = list.Dequeue(); + _ = Assert.Throws(() => list.Dequeue()); } } } diff --git a/Recyclable.CollectionsTests/Usings.cs b/Recyclable.CollectionsTests/Usings.cs index ab67c7e..8c927eb 100644 --- a/Recyclable.CollectionsTests/Usings.cs +++ b/Recyclable.CollectionsTests/Usings.cs @@ -1 +1 @@ -global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file +global using Xunit; \ No newline at end of file From 6246a38cbe3dec48cdc8361ce2f6d1d9a5a5d18c Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 7 Jan 2023 20:52:26 +0100 Subject: [PATCH 002/305] Tiny update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fee95be..9a9705b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Having protection against invalid state of the object is an important part of so This decision is driven by performance measures. Considering wide use of the lists on the hot-paths, each additional check & operation starts counting. I've decided to eliminate all the checks for the sake of performance. If you feel that you need additional protection, feel free to inherit or reuse any of the provided classes to provide that. -## `this[int index]` & `Count` may overflow, but... +## `this[int index]`, `Count`, `IndexOf(T item)` etc. may overflow, but... All the classes included in this package support `long` indexing and addressing. The `LongCount` property will always provide the total no. of items stored on the list. Since `Count` is just `LongCount` typecasted to `int`, its value may overflow. But `List` addressing is kind of currently limited to `int` ranges anyway in practical scenarios. As such `Count` overflowing shouldn't be an issue when using the classes with existing code. If you use `Count` in your loops, it's recommended to replace it with `LongCount`, tough. You don't need to make any changes in `foreach` or other iterator loops. They are expected to transparently use the new classes & yield all items, beyond `int.MaxValue` range. From 28b52c555d9529eb718328e6d46dd504940bf47a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 8 Jan 2023 00:14:14 +0100 Subject: [PATCH 003/305] Constructors simpliefied. GetItem/SetItem replaced with direct field use for performance gain. SetNewLength optimized for 32-bit long arrays. --- Recyclable.Collections/RecyclableBlockList.cs | 111 ++++++++---------- 1 file changed, 52 insertions(+), 59 deletions(-) diff --git a/Recyclable.Collections/RecyclableBlockList.cs b/Recyclable.Collections/RecyclableBlockList.cs index e2769fd..ab50098 100644 --- a/Recyclable.Collections/RecyclableBlockList.cs +++ b/Recyclable.Collections/RecyclableBlockList.cs @@ -61,17 +61,52 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining)] - private T[] SetNewLength(long newSize) + private T[] SetNewLength(in long newSize) { - var source = _memory; - T[] newArray = newSize.RentArrayFromPool(); - if (newArray.LongLength >= source.LongLength) + if (newSize == _capacity) { - source.CopyTo(newArray, 0); + return _memory; + } + + var source = _memory; + var sourceLength = source.LongLength; + var is32bit = newSize <= int.MaxValue; + T[] newArray; + if (is32bit) + { + var newSize32bit = (int)newSize; + newArray = newSize32bit.RentArrayFromPool(); + if (newSize32bit >= sourceLength) + { + if (sourceLength > 0) + { + Memory sourceMemory = new(source); + Memory newArrayMemory = new(newArray); + sourceMemory.CopyTo(newArrayMemory); + source.ReturnToPool(); + } + + return newArray; + } + else + { + Span sourceSpan = source.AsSpan(); + Span newArraySpan = newArray.AsSpan(); + sourceSpan = sourceSpan[..newSize32bit]; + sourceSpan.CopyTo(newArraySpan); + } } else { - CopyItems(0, newArray.LongLength, ref newArray, 0); + newArray = newSize.RentArrayFromPool(); + if (newSize >= sourceLength) + { + source.CopyTo(newArray, 0); + } + else + { + CopyItems(0, newArray.LongLength, ref newArray, 0); + } } source.ReturnToPool(); @@ -82,56 +117,14 @@ namespace Recyclable.Collections public RecyclableBlockList(long initialCapacity = RecyclableDefaults.Capacity) { - //EnsureCapacity(capacity); - if (_capacity < initialCapacity) - { - long newCapacity; - switch (_capacity > 0) - { - case true: - newCapacity = _capacity * 2; - while (newCapacity < initialCapacity) - { - newCapacity *= 2; - } - - break; - - case false: - newCapacity = initialCapacity; - break; - } - - _capacity = newCapacity; - _memory = SetNewLength(newCapacity); - } + _memory = SetNewLength(initialCapacity); + _capacity = initialCapacity; } public RecyclableBlockList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { - //EnsureCapacity(capacity); - if (_capacity < initialCapacity) - { - long newCapacity; - switch (_capacity > 0) - { - case true: - newCapacity = _capacity * 2; - while (newCapacity < initialCapacity) - { - newCapacity *= 2; - } - - break; - - case false: - newCapacity = initialCapacity; - break; - } - - _capacity = newCapacity; - _memory = SetNewLength(newCapacity); - } + _memory = SetNewLength(initialCapacity); + _capacity = initialCapacity; foreach (var item in source) { @@ -141,14 +134,14 @@ namespace Recyclable.Collections public T this[long index] { - get => GetItem(_memory, index); - set => SetItem(_memory, index, value); + get => _memory[index]; + set => _memory[index] = value; } public T this[int index] { - get => GetItem(_memory, index); - set => SetItem(_memory, index, value); + get => _memory[index]; + set => _memory[index] = value; } private long _capacity; @@ -192,8 +185,8 @@ namespace Recyclable.Collections break; } - _capacity = newCapacity; _memory = SetNewLength(newCapacity); + _capacity = newCapacity; } _memory[LongCount] = item; @@ -222,8 +215,8 @@ namespace Recyclable.Collections // break; // } - // _capacity = newCapacity; // _memory = SetNewLength(newCapacity); + // _capacity = newCapacity; // } //} @@ -269,8 +262,8 @@ namespace Recyclable.Collections break; } - _capacity = newCapacity; _memory = SetNewLength(newCapacity); + _capacity = newCapacity; } for (var toMoveIdx = LongCount - 1; toMoveIdx >= index; toMoveIdx--) From 8147c4ecbf3daefeef38baa12f8b271b741cc7fc Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 8 Jan 2023 02:03:14 +0100 Subject: [PATCH 004/305] Decoded on GetItem/SetItem performance. It's more stable & faster without them. --- Recyclable.Collections/RecyclableList.cs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index e76f9d0..0d3f93b 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -47,28 +47,16 @@ namespace Recyclable.Collections public int BlockSize { get; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetItem(RecyclableBlockList arrays, in int blockSize, in long index, in T value) - => arrays[(int)(index / blockSize)][index % blockSize] = value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T GetItem(RecyclableBlockList arrays, in int blockSize, in long index) - => arrays[(int)(index / blockSize)][index % blockSize]; - public T this[long index] { - //get => GetItem(MemoryBlocks, BlockSize, index); - //set => SetItem(MemoryBlocks, BlockSize, index, value); - get => _memoryBlocks[(int)(index / BlockSize)][index % BlockSize]; - set => _memoryBlocks[(int)(index / BlockSize)][index % BlockSize] = value; + get => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize]; + set => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize] = value; } public T this[int index] { - //get => GetItem(MemoryBlocks, BlockSize, index); - //set => SetItem(MemoryBlocks, BlockSize, index, value); - get => _memoryBlocks[(int)(index / BlockSize)][index % BlockSize]; - set => _memoryBlocks[(int)(index / BlockSize)][index % BlockSize] = value; + get => _memoryBlocks[index / _blockSize][index % _blockSize]; + set => _memoryBlocks[index / _blockSize][index % _blockSize] = value; } public long Capacity { get; set; } @@ -87,8 +75,7 @@ namespace Recyclable.Collections Capacity += BlockSize; } - SetItem(_memoryBlocks, BlockSize, LongCount, item); - //_arrays[_arrays.LongCount - 1][LongCount % BlockSize] = item; + memoryBlocks[oldCount / blockSize][oldCount % blockSize] = item; LongCount++; } From 880abd605eaab61784ec12ac5d7117bf72d2483a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 11 Jan 2023 21:21:42 +0100 Subject: [PATCH 005/305] RecyclableBlockList renamed to RecyclableArrayList for clarity. First set of optimizations and implementation. --- .../DelegateVsComparerBenchmark.cs | 26 +- .../ListVsLinkedListBenchmark.cs | 279 --------------- .../ListVsLinkedListBenchmarks.cs | 262 ++++++++++++++ Recyclable.Collections.Benchmarks/Program.cs | 10 +- .../RecyclableCollectionsBenchmarks.Array.cs | 52 +++ .../RecyclableCollectionsBenchmarks.List.cs | 83 +++++ ...llectionsBenchmarks.RecyclableArrayList.cs | 97 ++++++ ...bleCollectionsBenchmarks.RecyclableList.cs | 90 +++++ Recyclable.Collections/ListExtensions.cs | 8 +- Recyclable.Collections/RecyclableArrayList.cs | 220 ++++++++++++ Recyclable.Collections/RecyclableBlockList.cs | 327 ------------------ Recyclable.Collections/RecyclableDefaults.cs | 1 + Recyclable.Collections/RecyclableList.cs | 198 ++++++----- .../RecyclableListExtensions.cs | 159 +++++---- Recyclable.Collections/RecyclableQueue.cs | 2 +- ...stTests.cs => RecyclableArrayListTests.cs} | 26 +- 16 files changed, 1040 insertions(+), 800 deletions(-) delete mode 100644 Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs create mode 100644 Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs create mode 100644 Recyclable.Collections/RecyclableArrayList.cs delete mode 100644 Recyclable.Collections/RecyclableBlockList.cs rename Recyclable.CollectionsTests/{RecyclableBlockListTests.cs => RecyclableArrayListTests.cs} (86%) diff --git a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs index 904f509..2eb4204 100644 --- a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs +++ b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs @@ -84,7 +84,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void Comparer() { TestWithComparer(_comparer); @@ -99,7 +99,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void Func() { TestWithFunc((x, y) => (x - y) switch @@ -110,7 +110,7 @@ namespace Recyclable.Collections.Benchmarks }); } - [Benchmark] + //[Benchmark] public void StaticFunc() { TestWithFunc(static (x, y) => (x - y) switch @@ -130,7 +130,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void Delegate() { TestWithDelegate((x, y) => (x - y) switch @@ -141,7 +141,7 @@ namespace Recyclable.Collections.Benchmarks }); } - [Benchmark] + //[Benchmark] public void ListOfFuncs() { const long x = 2, y = 5; @@ -153,7 +153,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfLocalFuncs() { const long x = 2, y = 5; @@ -165,7 +165,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfComparers() { const long x = 2, y = 5; @@ -177,7 +177,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfClasses() { const long x = 2, y = 5; @@ -189,7 +189,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfStructs() { const long x = 2, y = 5; @@ -201,7 +201,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfBoxedStructs() { const long x = 2, y = 5; @@ -213,7 +213,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfUnboxedStructs() { const long x = 2, y = 5; @@ -225,7 +225,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfUnboxedReadOnlyStructs() { const long x = 2, y = 5; @@ -237,7 +237,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfDelegates() { const long x = 2, y = 5; diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs deleted file mode 100644 index c43858e..0000000 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs +++ /dev/null @@ -1,279 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks -{ - [MemoryDiagnoser] - public class ListVsLinkedListBenchmark - { - //[Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public int TestObjectCount = 1_000_000; - public int BlockSize => TestObjectCount switch - { - 0 => 1, - > 0 and <= 10 => TestObjectCount, - _ => TestObjectCount / 10 - }; - - private object[]? _testObjects; - - public ListVsLinkedListBenchmark() - { - _ = TestObjects; - } - - private object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); - - private static void DoNothing(T obj) - { - _ = obj; - } - - [Benchmark] - public void Array() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new object[dataCount]; - for (var i = 0; i < dataCount; i++) - { - list[i] = data[i]; - } - - for (var i = 0; i < list.LongLength; i++) - { - DoNothing(list[i]); - } - } - - [Benchmark] - public void RecyclableBlockList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableBlockList(initialCapacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - [Benchmark(Baseline = true)] - public void RecyclableList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableList(BlockSize, initialCapacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - [Benchmark] - public void List() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new List(capacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - - for (var i = 0; i < list.Count; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void LinkedList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new LinkedList(); - for (var i = 0; i < dataCount; i++) - { - _ = list.AddLast(data[i]); - } - - foreach (var node in list) - { - DoNothing(node); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableQueue() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new RecyclableQueue(BlockSize); - for (var i = 0; i < dataCount; i++) - { - list.Enqueue(data[i]); - } - - while (list.LongCount > 0) - { - DoNothing(list.Dequeue()); - } - - list.Clear(); - } - - //[Benchmark] - public void Queue() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new Queue(capacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Enqueue(data[i]); - } - - while (list.Count > 0) - { - DoNothing(list.Dequeue()); - } - - list.Clear(); - } - - //[Benchmark] - public void PriorityQueue() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new PriorityQueue(initialCapacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Enqueue(data[i], i); - } - - while (list.Count > 0) - { - DoNothing(list.Dequeue()); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableSortedList_InUpdateMode() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new RecyclableSortedList(BlockSize); - list.BeginUpdate(); - for (var i = 0; i < dataCount; i++) - { - list.Add(i, data[i]); - } - - list.EndUpdate(); - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableSortedList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new RecyclableSortedList(BlockSize); - for (var i = 0; i < dataCount; i++) - { - list.Add(i, data[i]); - } - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void SortedList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new SortedList(); - for (var i = 0; i < dataCount; i++) - { - list.Add(i, data[i]); - } - - for (var i = 0; i < list.Count; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableStack() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new RecyclableStack(BlockSize); - for (var i = 0; i < dataCount; i++) - { - list.Push(data[i]); - } - - while (list.LongCount > 0) - { - DoNothing(list.Pop()); - } - - list.Clear(); - } - - //[Benchmark] - public void Stack() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new Stack(); - for (var i = 0; i < dataCount; i++) - { - list.Push(data[i]); - } - - while (list.Count > 0) - { - DoNothing(list.Pop()); - } - - list.Clear(); - } - } -} diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs new file mode 100644 index 0000000..108f96b --- /dev/null +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -0,0 +1,262 @@ +namespace Recyclable.Collections.Benchmarks +{ + public class BenchmarkBase + { + //[Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + public const int TestObjectCount = 1_000_000; + public static int BlockSize = TestObjectCount switch + { + 0 => 1, + > 0 and <= 10 => TestObjectCount, + _ => TestObjectCount / 10 + }; + + protected object[]? _testObjects; + protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); + + protected static void DoNothing(T obj) + { + _ = obj; + } + + public BenchmarkBase() + { + _ = TestObjects; + } + } + + //[MemoryDiagnoser] + //public class ListVsLinkedListBenchmarks + //{ + // [Benchmark] + // public void RecyclableBlockList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // using var list = new RecyclableBlockList(initialCapacity: dataCount); + // for (long i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (long i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // [Benchmark(Baseline = true)] + // public void RecyclableList() + // { + // var data = TestObjects; + // long dataCount = TestObjectCount; + // using var list = new RecyclableList(BlockSize, totalItemsCount: dataCount); + // for (long i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (long i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void List() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new List(capacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (var i = 0; i < list.Count; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void LinkedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new LinkedList(); + // for (var i = 0; i < dataCount; i++) + // { + // _ = list.AddLast(data[i]); + // } + + // foreach (var node in list) + // { + // DoNothing(node); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableQueue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableQueue(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i]); + // } + + // while (list.LongCount > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void Queue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new Queue(capacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i]); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void PriorityQueue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new PriorityQueue(initialCapacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i], i); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableSortedList_InUpdateMode() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableSortedList(BlockSize); + // list.BeginUpdate(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // list.EndUpdate(); + + // for (var i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableSortedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableSortedList(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // for (var i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void SortedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new SortedList(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // for (var i = 0; i < list.Count; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableStack() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableStack(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Push(data[i]); + // } + + // while (list.LongCount > 0) + // { + // DoNothing(list.Pop()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void Stack() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new Stack(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Push(data[i]); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Pop()); + // } + + // list.Clear(); + // } + //} +} diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index e35ca2b..b55ba7a 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -1,8 +1,14 @@ using BenchmarkDotNet.Running; using Recyclable.Collections.Benchmarks; -BenchmarkRunner.Run(); +//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); + +BenchmarkRunner.Run(); + +//var test = new RecyclableCollectionsBenchmarks(); + + //BenchmarkRunner.Run(); //var tests = new ListVsLinkedListBenchmark(); -//tests.RecyclableBlockList(); +//tests.RecyclableList(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs new file mode 100644 index 0000000..b87abe3 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -0,0 +1,52 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + [MemoryDiagnoser] + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + private readonly object[] _testArray; + + //[Benchmark(Baseline = true)] + public void Array_Create_WithCapacity() + { + DoNothing(new object[TestObjectCount]); + } + + //[Benchmark] + public void Array_GetItem() + { + var data = _testArray; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void Array_SetItem() + { + var data = _testArray; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void Array_Count() + { + var data = _testArray; + DoNothing(data.Length); + } + + //[Benchmark] + public void Array_LongCount() + { + var data = _testArray; + DoNothing(data.LongLength); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs new file mode 100644 index 0000000..9766fc0 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -0,0 +1,83 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + private readonly List _testList; + + [Benchmark(Baseline = true)] + public void List_Create() + { + var list = new List(); + DoNothing(list); + } + + [Benchmark] + public void List_Create_0Capacity() + { + var list = new List(0); + DoNothing(list); + } + + [Benchmark] + public void List_Create_WithCapacity() + { + var list = new List(TestObjectCount); + DoNothing(list); + } + + //[Benchmark] + public void List_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void List_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void List_GetItem() + { + var data = _testList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void List_SetItem() + { + var data = _testList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void List_Count() + { + var data = _testList; + DoNothing(data.Count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs new file mode 100644 index 0000000..400b3f0 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -0,0 +1,97 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + private readonly RecyclableArrayList _testRecyclableArrayList; + + public RecyclableCollectionsBenchmarks() + { + _testRecyclableArrayList = new(TestObjects, TestObjectCount); + _testRecyclableList = new(TestObjects, TestObjectCount); + _testArray = TestObjects.ToArray(); + _testList = new(TestObjects); + } + + ~RecyclableCollectionsBenchmarks() + { + _testRecyclableArrayList?.Dispose(); + _testRecyclableList?.Dispose(); + } + + [Benchmark] + public void RecyclableArrayList_Create() + { + using var list = new RecyclableArrayList(); + DoNothing(list); + } + + [Benchmark] + public void RecyclableArrayList_Create_0Capacity() + { + using var list = new RecyclableArrayList(0); + DoNothing(list); + } + + [Benchmark] + public void RecyclableArrayList_Create_WithCapacity() + { + using var list = new RecyclableArrayList(TestObjectCount); + DoNothing(list); + } + + //[Benchmark] + public void RecyclableArrayList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void RecyclableArrayList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void RecyclableArrayList_GetItem() + { + var data = _testRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void RecyclableArrayList_SetItem() + { + var data = _testRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void RecyclableArrayList_Count() + { + var data = _testRecyclableArrayList; + DoNothing(data.Count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs new file mode 100644 index 0000000..57c760e --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -0,0 +1,90 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + private readonly RecyclableList _testRecyclableList; + + [Benchmark] + public void RecyclableList_Create() + { + using var list = new RecyclableList(); + DoNothing(list); + } + + [Benchmark] + public void RecyclableList_Create_0Capacity() + { + using var list = new RecyclableList(totalItemsCount: 0); + DoNothing(list); + } + + [Benchmark] + public void RecyclableList_Create_WithCapacity() + { + using var list = new RecyclableList(totalItemsCount: TestObjectCount); + DoNothing(list); + } + + //[Benchmark] + public void RecyclableList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void RecyclableList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void RecyclableList_GetItem() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void RecyclableList_SetItem() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void RecyclableList_Count() + { + var data = _testRecyclableList; + DoNothing(data.Count); + } + + //[Benchmark] + public void RecyclableList_LongCount() + { + var data = _testRecyclableList; + DoNothing(data.LongCount); + } + } +} diff --git a/Recyclable.Collections/ListExtensions.cs b/Recyclable.Collections/ListExtensions.cs index 58f034b..f9361d7 100644 --- a/Recyclable.Collections/ListExtensions.cs +++ b/Recyclable.Collections/ListExtensions.cs @@ -7,10 +7,10 @@ namespace Recyclable.Collections public static class ListExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this RecyclableBlockList arrays, T item) => arrays.Any(x => x.Contains(item)); + public static bool Contains(this RecyclableArrayList arrays, T item) => arrays.Any(x => x.Contains(item)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyTo(this RecyclableBlockList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + public static void CopyTo(this RecyclableArrayList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) { Span arrayMemory = destinationArray.AsSpan(); arrayMemory = arrayMemory[destinationArrayIndex..]; @@ -49,7 +49,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Enumerate(this RecyclableBlockList arrays, int chunkSize, long totalCount) + public static IEnumerable Enumerate(this RecyclableArrayList arrays, int chunkSize, long totalCount) { long currentCount = 0; for (var arrayIdx = 0; (arrayIdx < arrays.Count) && (currentCount < totalCount); arrayIdx++) @@ -104,7 +104,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this RecyclableBlockList arrays, int blockSize, T item, IEqualityComparer comparer) + public static long LongIndexOf(this RecyclableArrayList arrays, int blockSize, T item, IEqualityComparer comparer) { for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) { diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs new file mode 100644 index 0000000..f68b870 --- /dev/null +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -0,0 +1,220 @@ +using System.Collections; + +namespace Recyclable.Collections +{ + public class RecyclableArrayList : IEnumerable, IDisposable + { + private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + + protected T[] _memory; + + protected static T[] SetNewLength(in T[]? source, in int newSize) + { + var sourceLength = source?.Length ?? 0; + T[] newArray; + newArray = newSize.RentArrayFromPool(); + switch (newSize >= sourceLength) + { + case true: + if (sourceLength > 0) + { + Memory sourceMemory = new(source); + Memory newArrayMemory = new(newArray); + sourceMemory.CopyTo(newArrayMemory); + source!.ReturnToPool(); + } + + return newArray; + + case false: + Span sourceSpan = source.AsSpan(); + Span newArraySpan = newArray.AsSpan(); + sourceSpan = sourceSpan[..newSize]; + sourceSpan.CopyTo(newArraySpan); + source!.ReturnToPool(); + return newArray; + } + } + + protected void EnsureCapacity(in int requestedCapacity) + { + int newCapacity; + switch (_capacity > 0) + { + case true: + newCapacity = _capacity; + while (newCapacity < requestedCapacity) + { + newCapacity *= 2; + } + + break; + + case false: + newCapacity = requestedCapacity; + break; + } + + _memory = SetNewLength(_memory, newCapacity); + _capacity = _memory.Length; + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList() +#pragma warning restore CS8618 + { + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(int initialCapacity) +#pragma warning restore CS8618 + { + // _memory will be initialized when the 1st item is added + if (initialCapacity > 0) + { + _memory = SetNewLength(_memory, initialCapacity); + _capacity = initialCapacity; + } + } + + public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) + { + _memory = SetNewLength(_memory, initialCapacity); + _capacity = initialCapacity; + + foreach (var item in source) + { + Add(item); + } + } + + public T this[int index] + { + get => _memory[index]; + set => _memory[index] = value; + } + + internal int _capacity; + public int Capacity + { + get => _capacity; + set + { + if (_capacity != value) + { + _memory = SetNewLength(_memory, value); + _capacity = value; + } + } + } + public int Count { get; set; } + public bool IsReadOnly { get; } = false; + + public void Add(in T item) + { + int requestedCapacity = Count + 1; + if (_capacity < requestedCapacity) + { + EnsureCapacity(requestedCapacity); + } + + _memory[Count] = item; + Count++; + } + + public void Clear() + { + Count = 0; + } + + public bool Contains(T item) => _memory.Contains(item); + + public void CopyTo(T[] array, int arrayIndex) => _memory.CopyTo(array, arrayIndex); + + protected static IEnumerable Enumerate(RecyclableArrayList list) + { + for (var i = 0; i < list.Count; i++) + { + yield return list._memory[i]; + } + } + + public int IndexOf(T itemToFind) + { + var memory = _memory; + int itemCount = Count; + var equalityComparer = _equalityComparer; + for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) + { + var item = memory[itemIdx]; + if (equalityComparer.Equals(item, itemToFind)) + { + return itemIdx; + } + } + + return -1; + } + + public void Insert(int index, T item) + { + int requestedCapacity = Count + 1; + if (Capacity < requestedCapacity) + { + EnsureCapacity(requestedCapacity); + } + + for (var toMoveIdx = Count - 1; toMoveIdx >= index; toMoveIdx--) + { + _memory[toMoveIdx + 1] = _memory[toMoveIdx]; + } + + _memory[index] = item; + Count++; + } + + public bool Remove(T itemToRemove) + { + var itemIdx = IndexOf(itemToRemove); + if (itemIdx >= 0) + { + RemoveAt(itemIdx); + return true; + } + + return false; + } + public void RemoveAt(int index) + { + int oldCount = Count; + int oldCountMinus1 = oldCount - 1; + if (index == oldCountMinus1) + { + Count--; + return; + } + + if (index >= oldCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + _memory.CopyItems(index, oldCountMinus1, ref _memory); + Count--; + } + + public IEnumerator GetEnumerator() => Enumerate(this).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Enumerate(this).GetEnumerator(); + + public void Dispose() + { + if (Capacity > 0) + { + Clear(); + Capacity = 0; + _memory.ReturnToPool(); + GC.SuppressFinalize(this); + } + } + } +} diff --git a/Recyclable.Collections/RecyclableBlockList.cs b/Recyclable.Collections/RecyclableBlockList.cs deleted file mode 100644 index ab50098..0000000 --- a/Recyclable.Collections/RecyclableBlockList.cs +++ /dev/null @@ -1,327 +0,0 @@ -using System.Collections; -using System.Runtime.CompilerServices; - -namespace Recyclable.Collections -{ - public class RecyclableBlockList : IEnumerable, IDisposable - { - private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; - private bool _disposedValue; - - - private void CopyItems(long startingIndex, long count, ref T[] destArray, long offset = 1) - { - for (long destItemIndex = startingIndex, sourceItemIndex = offset; destItemIndex < startingIndex + count; destItemIndex++, sourceItemIndex++) - { - destArray[destItemIndex] = _memory[sourceItemIndex]; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T GetItem(in T[] arrays, in long index) => arrays[index]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void InternalRemoveAt(long index) - { - long oldCount = LongCount; - long oldCountMinus1 = oldCount - 1; - if (index == oldCountMinus1) - { - LongCount--; - return; - } - - if (index >= oldCount) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - CopyItems(index, oldCountMinus1, ref _memory); - - LongCount--; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static long LongIndexOf(in T[] memory, in long itemCount, T? itemToFind, IEqualityComparer equalityComparer) - { - for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) - { - var item = memory[itemIdx]; - if (equalityComparer.Equals(item, itemToFind)) - { - return itemIdx; - } - } - - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetItem(in T[] arrays, in long index, in T value) => arrays[index] = value; - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private T[] SetNewLength(in long newSize) - { - if (newSize == _capacity) - { - return _memory; - } - - var source = _memory; - var sourceLength = source.LongLength; - var is32bit = newSize <= int.MaxValue; - T[] newArray; - if (is32bit) - { - var newSize32bit = (int)newSize; - newArray = newSize32bit.RentArrayFromPool(); - if (newSize32bit >= sourceLength) - { - if (sourceLength > 0) - { - Memory sourceMemory = new(source); - Memory newArrayMemory = new(newArray); - sourceMemory.CopyTo(newArrayMemory); - source.ReturnToPool(); - } - - return newArray; - } - else - { - Span sourceSpan = source.AsSpan(); - Span newArraySpan = newArray.AsSpan(); - sourceSpan = sourceSpan[..newSize32bit]; - sourceSpan.CopyTo(newArraySpan); - } - } - else - { - newArray = newSize.RentArrayFromPool(); - if (newSize >= sourceLength) - { - source.CopyTo(newArray, 0); - } - else - { - CopyItems(0, newArray.LongLength, ref newArray, 0); - } - } - - source.ReturnToPool(); - return newArray; - } - - protected T[] _memory = Array.Empty(); - - public RecyclableBlockList(long initialCapacity = RecyclableDefaults.Capacity) - { - _memory = SetNewLength(initialCapacity); - _capacity = initialCapacity; - } - - public RecyclableBlockList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) - { - _memory = SetNewLength(initialCapacity); - _capacity = initialCapacity; - - foreach (var item in source) - { - Add(item); - } - } - - public T this[long index] - { - get => _memory[index]; - set => _memory[index] = value; - } - - public T this[int index] - { - get => _memory[index]; - set => _memory[index] = value; - } - - private long _capacity; - public long Capacity - { - get => _capacity; - set - { - if (_capacity != value) - { - _memory = SetNewLength(value); - _capacity = value; - } - } - } - public int Count => (int)LongCount; - public long LongCount { get; set; } - public bool IsReadOnly { get; } = false; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(in T item) - { - //EnsureCapacity(LongCount + 1); - long requestedCapacity = LongCount + 1; - if (_capacity < requestedCapacity) - { - long newCapacity; - switch (_capacity > 0) - { - case true: - newCapacity = _capacity * 2; - while (newCapacity < requestedCapacity) - { - newCapacity *= 2; - } - - break; - - case false: - newCapacity = requestedCapacity; - break; - } - - _memory = SetNewLength(newCapacity); - _capacity = newCapacity; - } - - _memory[LongCount] = item; - LongCount++; - } - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public void EnsureCapacity(long requestedCapacity) - //{ - // if (Capacity < requestedCapacity) - // { - // long newCapacity; - // switch (Capacity > 0) - // { - // case true: - // newCapacity = Capacity * 2; - // while (newCapacity < requestedCapacity) - // { - // newCapacity *= 2; - // } - - // break; - - // case false: - // newCapacity = requestedCapacity; - // break; - // } - - // _memory = SetNewLength(newCapacity); - // _capacity = newCapacity; - // } - //} - - public void Clear() - { - LongCount = 0; - } - - public bool Contains(T item) => _memory.Contains(item); - - public void CopyTo(T[] array, int arrayIndex) => _memory.CopyTo(array, arrayIndex); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerable Enumerate() - { - for (var i = 0; i < LongCount; i++) - { - yield return _memory[i]; - } - } - - public int IndexOf(T item) => (int)LongIndexOf(_memory, LongCount, item, _equalityComparer); - - public void Insert(int index, T item) - { - long requestedCapacity = LongCount + 1; - if (Capacity < requestedCapacity) - { - long newCapacity; - switch (Capacity > 0) - { - case true: - newCapacity = Capacity * 2; - while (newCapacity < requestedCapacity) - { - newCapacity *= 2; - } - - break; - - case false: - newCapacity = requestedCapacity; - break; - } - - _memory = SetNewLength(newCapacity); - _capacity = newCapacity; - } - - for (var toMoveIdx = LongCount - 1; toMoveIdx >= index; toMoveIdx--) - { - _memory[toMoveIdx + 1] = _memory[toMoveIdx]; - } - - _memory[index] = item; - LongCount++; - } - - public long LongIndexOf(T item) => LongIndexOf(_memory, LongCount, item, _equalityComparer); - - public bool Remove(T item) - { - var itemIdx = LongIndexOf(item); - if (itemIdx >= 0) - { - RemoveAt(itemIdx); - return true; - } - - return false; - } - - public void RemoveAt(int index) => InternalRemoveAt(index); - public void RemoveAt(long index) => InternalRemoveAt(index); - - public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => Enumerate().GetEnumerator(); - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - Clear(); - _memory.ReturnToPool(); - Capacity = 0; - } - - _disposedValue = true; - } - } - - // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~RecyclableList() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 8c9b5d1..09e80ce 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -4,5 +4,6 @@ { public const int BlockSize = 10_240; public const int Capacity = 8; + public const int MinPooledArrayLength = 100; } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 0d3f93b..1c49bdd 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,51 +1,102 @@ -using System.Collections; -using System.Runtime.CompilerServices; +using System.Buffers; +using System.Collections; namespace Recyclable.Collections { public class RecyclableList : IDisposable, IList { private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + private const int _minPooledArraySize = 100; + private readonly int _blockSize; + private readonly RecyclableArrayList _memoryBlocks; - private readonly RecyclableBlockList _memoryBlocks; - private bool _disposedValue; - - public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) + private static void AddCapacity(RecyclableList list, RecyclableArrayList memoryBlocks, int additionalCapacity) { - BlockSize = blockSize; - initialCapacity ??= 8 * BlockSize; - int additionalArray = initialCapacity % BlockSize > 0 ? 1 : 0; - long targetCapacity = (initialCapacity.Value / BlockSize) + additionalArray; - _memoryBlocks = new(targetCapacity); - Capacity = initialCapacity.Value; - for (var blockIdx = 0; blockIdx < targetCapacity; blockIdx++) - { - _memoryBlocks.Add(BlockSize.RentArrayFromPool()); - } - - //MemoryBlocks.LongCount = MemoryBlocks.Capacity; + T[] newMemoryBlock = RentArrayFromPool(additionalCapacity); + memoryBlocks.Add(newMemoryBlock); + list.Capacity += additionalCapacity; } - public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) + private static void AddRange(RecyclableList destination, IEnumerable source) { - BlockSize = blockSize; - initialCapacity ??= 8 * BlockSize; - int additionalCapacity = initialCapacity % BlockSize > 0 ? 1 : 0; - long targetCapacity = (initialCapacity.Value / BlockSize) + additionalCapacity; - _memoryBlocks = new(targetCapacity); - Capacity = initialCapacity.Value; - for (var blockIdx = 0; blockIdx < targetCapacity; blockIdx++) - { - _memoryBlocks.Add(BlockSize.RentArrayFromPool()); - } - foreach (var item in source) { - Add(item); + destination.Add(item); } } - public int BlockSize { get; } + private static void RemoveAt(RecyclableList list, long index) + { + if (index == list.LongCount - 1) + { + list.LongCount--; + if (list.Capacity - list.LongCount == list._blockSize) + { + list.RemoveBlock(list.BlockCount - 1); + } + } + else + { + throw new NotSupportedException("Only removal of the last element is supported"); + } + } + + private static T[] RentArrayFromPool(int minSize) => (minSize >= _minPooledArraySize) + ? ArrayPool.Shared.Rent(minSize) + : new T[minSize]; + + private static void ReturnToPool(in T[] array) + { + if (array.LongLength is < _minPooledArraySize or > int.MaxValue) + { + return; + } + + ArrayPool.Shared.Return(array); + } + + private static void RemoveBlock(RecyclableList owner, RecyclableArrayList memoryBlocks, int blockSize, int index) + { + try + { + ReturnToPool(memoryBlocks[index]); + memoryBlocks.RemoveAt(index); + } + finally + { + owner.Capacity -= blockSize; + } + } + + private static RecyclableArrayList SetupMemoryBlocks(RecyclableList owner, int blockSize, long? totalItemsCount) + { + 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 RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? totalItemsCount = default) + { + _blockSize = blockSize; + _memoryBlocks = SetupMemoryBlocks(this, blockSize, totalItemsCount); + } + + public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? totalItemsCount = default) + { + _blockSize = blockSize; + _memoryBlocks = SetupMemoryBlocks(this, blockSize, totalItemsCount); + AddRange(this, source); + } public T this[long index] { @@ -59,23 +110,22 @@ namespace Recyclable.Collections set => _memoryBlocks[index / _blockSize][index % _blockSize] = value; } - public long Capacity { get; set; } + public long Capacity { get; protected set; } public int Count => (int)LongCount; public long LongCount { get; set; } public bool IsReadOnly { get; } = false; - public long BlockCount => _memoryBlocks.LongCount; + public int BlockCount => _memoryBlocks.Count; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { - if (LongCount == Capacity) + long oldCount = LongCount; + int blockSize = _blockSize; + if (oldCount == Capacity) { - T[] newMemoryBlock = BlockSize.RentArrayFromPool(); - _memoryBlocks.Add(newMemoryBlock); - Capacity += BlockSize; + AddCapacity(this, _memoryBlocks, blockSize); } - memoryBlocks[oldCount / blockSize][oldCount % blockSize] = item; + _memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item; LongCount++; } @@ -88,7 +138,7 @@ namespace Recyclable.Collections { try { - RemoveBlock(_memoryBlocks.Count - 1); + RemoveBlock(this, _memoryBlocks, _blockSize, _memoryBlocks.Count - 1); } catch (Exception) { @@ -106,81 +156,29 @@ namespace Recyclable.Collections public bool Contains(T item) => _memoryBlocks.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); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(BlockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(BlockSize, item, _equalityComparer); + public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer); public void Insert(int index, T item) => throw new NotSupportedException(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(BlockSize, item, _equalityComparer); + public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer); public bool Remove(T item) => throw new NotSupportedException(); public void RemoveAt(int index) => RemoveAt(this, index); public void RemoveAt(long index) => RemoveAt(this, index); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void RemoveAt(RecyclableList list, long index) - { - if (index == list.LongCount - 1) - { - list.LongCount--; - if (list.Capacity - list.LongCount == list.BlockSize) - { - list.RemoveBlock(list.BlockCount - 1); - } - } - else - { - throw new NotSupportedException("Only removal of the last element is supported"); - } - } - - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(BlockSize, LongCount).GetEnumerator(); - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - Clear(); - } - - _disposedValue = true; - } - } - - // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~RecyclableList() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); public void Dispose() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + Clear(); GC.SuppressFinalize(this); } - public void RemoveBlock(long index) - { - try - { - _memoryBlocks[index].ReturnToPool(); - _memoryBlocks.RemoveAt(index); - } - finally - { - Capacity -= BlockSize; - LongCount -= BlockSize; - } - } + public void RemoveBlock(int index) => RemoveBlock(this, _memoryBlocks, _blockSize, index); } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index dbcde99..a027410 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -1,5 +1,4 @@ using System.Buffers; -using System.Buffers; using System.Runtime.CompilerServices; namespace Recyclable.Collections @@ -8,18 +7,25 @@ namespace Recyclable.Collections { private const int _minPooledArraySize = 100; - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyItems(this T[] memory, long startingIndex, long count, ref T[] destArray, long offset = 1) + { + for (long destItemIndex = startingIndex, sourceItemIndex = offset; destItemIndex < startingIndex + count; destItemIndex++, sourceItemIndex++) + { + destArray[destItemIndex] = memory[sourceItemIndex]; + } + } + public static void CopyTo(this RecyclableList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) { Span arrayMemory = destinationArray.AsSpan(); arrayMemory = arrayMemory[destinationArrayIndex..]; int startingArrayIndex = (int)(startingIndex / blockSize); startingIndex %= blockSize; - if (arrays.Count > 0) + if (arrays.LongCount > 0) { ReadOnlySpan sourceMemory = arrays[0].AsSpan(); var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); - if (arrays.Count == 1) + if (arrays.LongCount == 1) { maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); } @@ -47,91 +53,122 @@ namespace Recyclable.Collections } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) + //public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) + //{ + // long currentCount = 0; + // for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + // { + // var array = arrays[arrayIdx]; + // currentCount += chunkSize; + // switch (currentCount < totalCount) + // { + // case true: + // for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) + // { + // yield return array[valueIdx]; + // } + + // break; + + // case false: + // var partialCount = (int)(totalCount % chunkSize); + // int maxCount = partialCount > 0 ? partialCount : chunkSize; + // for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) + // { + // yield return array[valueIdx]; + // } + + // break; + // } + // } + //} + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static T LimitTo(this T value, T maxValue, IComparer comparer) + // => comparer.Compare(value, maxValue) > 0 ? maxValue : value; + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long LongIndexOf(this RecyclableList arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) { - long currentCount = 0; - for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + for (var itemIdx = 0L; itemIdx < itemsCount; itemIdx++) { - var array = arrays[arrayIdx]; - currentCount += chunkSize; - switch (currentCount < totalCount) + var item = arrays[itemIdx]; + if (comparer.Equals(itemToFind, item)) { - case true: - for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) - { - yield return array[valueIdx]; - } - - break; - - case false: - var partialCount = (int)(totalCount % chunkSize); - int maxCount = partialCount > 0 ? partialCount : chunkSize; - for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) - { - yield return array[valueIdx]; - } - - break; + return itemIdx; } + + itemIdx++; } + + return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T LimitTo(this T value, T maxValue, IComparer comparer) - => comparer.Compare(value, maxValue) > 0 ? maxValue : value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this RecyclableList arrays, int blockSize, T item, IEqualityComparer comparer) + public static long LongIndexOf(this RecyclableArrayList arrays, int itemsCount, T itemToFind, IEqualityComparer comparer) { - for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + for (var itemIdx = 0; itemIdx < itemsCount; itemIdx++) { - var array = arrays[arrayIdx]; - Span arrayMemory = array.AsSpan(); - for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++) + var item = arrays[itemIdx]; + if (comparer.Equals(itemToFind, item)) { - if (comparer.Equals(arrayMemory[memoryIdx], item)) - { - return (arrayIdx * (long)blockSize) + memoryIdx; - } + return itemIdx; } } return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + public static T[] RentArrayFromPool(this int minSize) => (minSize >= _minPooledArraySize) + ? ArrayPool.Shared.Rent(minSize) + : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this int minSize) => RentArrayFromPool((long)minSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this long minSize) - { - return minSize switch - { - < _minPooledArraySize => new T[minSize], - <= int.MaxValue => ArrayPool.Shared.Rent((int)minSize), - _ => new T[minSize] - }; - } + public static T[] RentArrayFromPool(this long minSize) => (minSize is >= _minPooledArraySize and <= int.MaxValue) + ? ArrayPool.Shared.Rent((int)minSize) + : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReturnToPool(this T[] array) { - long arrayLength = array.LongLength; - if (arrayLength is >= _minPooledArraySize and <= int.MaxValue) + if (array.LongLength is > _minPooledArraySize and <= int.MaxValue) { ArrayPool.Shared.Return(array); } } + + //public static void AddRange(this RecyclableList destination, IEnumerable source) + //{ + // foreach (var item in source) + // { + // destination.Add(item); + // } + //} + + public static long LimitTo(this long value, int limit) + => (value <= limit) ? value : limit; + + public static int IndexOf(this T[] memory, int itemCount, T? itemToFind, IEqualityComparer equalityComparer) + { + for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) + { + var item = memory[itemIdx]; + if (equalityComparer.Equals(item, itemToFind)) + { + return itemIdx; + } + } + + return -1; + } } } diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 0a70082..29c68cf 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -8,7 +8,7 @@ namespace Recyclable.Collections private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private bool _disposedValue; - private RecyclableBlockList Memory { get; } + private RecyclableArrayList Memory { get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T Dequeue(RecyclableQueue queue) diff --git a/Recyclable.CollectionsTests/RecyclableBlockListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs similarity index 86% rename from Recyclable.CollectionsTests/RecyclableBlockListTests.cs rename to Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 514d0fe..6390a78 100644 --- a/Recyclable.CollectionsTests/RecyclableBlockListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -3,7 +3,7 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - public class RecyclableBlockListTests + public class RecyclableArrayListTests { private readonly static IEnumerable _testData = Enumerable.Range(1, 20); @@ -12,13 +12,13 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(); + var list = new RecyclableArrayList(); // Act foreach (var item in testData) { list.Add(item); - _ = list.LongCount.Should().Be(item); + _ = list.Count.Should().Be(item); } // Validate @@ -33,13 +33,13 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(); + var list = new RecyclableArrayList(); // Act foreach (var item in testData) { list.Insert(0, item); - _ = list.LongCount.Should().Be(item); + _ = list.Count.Should().Be(item); } // Validate @@ -54,7 +54,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate foreach (var item in testData) @@ -72,7 +72,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= testData.LongLength; deleted++) @@ -94,7 +94,7 @@ namespace Recyclable.CollectionsTests var testData = _testData.ToArray(); // Act & Validate - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); for (var deleted = 1; deleted <= testData.LongLength; deleted++) { // Act @@ -112,7 +112,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= testData.LongLength; deleted++) @@ -135,13 +135,13 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= testData.LongLength; deleted++) { // Act - list.RemoveAt(list.LongCount - 1); + list.RemoveAt(list.Count - 1); // Validate _ = list.Should().HaveCount(testData.Length - deleted) @@ -160,7 +160,7 @@ namespace Recyclable.CollectionsTests var testData = _testData.ToArray(); // Act - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Validate _ = list.Should().HaveCount(testData.Length) @@ -173,7 +173,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act var yieldedItems = new List(); From 012fc3b21fc67d9864c4d862a2811ff4704ad0fa Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 13 Jan 2023 19:45:22 +0100 Subject: [PATCH 006/305] RecyclableArrayList.AddRange added. Smaller perf improvements. --- .../ListVsLinkedListBenchmarks.cs | 4 +- Recyclable.Collections.Benchmarks/Program.cs | 4 +- .../RecyclableCollectionsBenchmarks.List.cs | 27 ++++++++-- ...llectionsBenchmarks.RecyclableArrayList.cs | 27 ++++++++-- ...bleCollectionsBenchmarks.RecyclableList.cs | 10 ++-- Recyclable.Collections/RecyclableArrayList.cs | 51 +++++++++++++++--- .../RecyclableArrayList_CreateBenchmarks.png | Bin 0 -> 25262 bytes .../RecyclableArrayListTests.cs | 36 ++++++++++++- 8 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs index 108f96b..52d8471 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -29,11 +29,11 @@ //public class ListVsLinkedListBenchmarks //{ // [Benchmark] - // public void RecyclableBlockList() + // public void RecyclableArrayList() // { // var data = TestObjects; // var dataCount = TestObjectCount; - // using var list = new RecyclableBlockList(initialCapacity: dataCount); + // using var list = new RecyclableArrayList(initialCapacity: dataCount); // for (long i = 0; i < dataCount; i++) // { // list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index b55ba7a..fdc21ac 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -5,7 +5,9 @@ using Recyclable.Collections.Benchmarks; BenchmarkRunner.Run(); -//var test = new RecyclableCollectionsBenchmarks(); +//var benchmark = new RecyclableCollectionsBenchmarks(); +//benchmark.RecyclableArrayList_Add(); +//benchmark.RecyclableArrayList_Add_WithCapacity(); //BenchmarkRunner.Run(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 9766fc0..e7f3324 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -6,28 +6,28 @@ namespace Recyclable.Collections.Benchmarks { private readonly List _testList; - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Create() { var list = new List(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Create_0Capacity() { var list = new List(0); DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark] public void List_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = true)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -51,6 +51,23 @@ namespace Recyclable.Collections.Benchmarks } } + [Benchmark] + public void List_AddRange() + { + var data = TestObjects; + var list = new List(); + list.AddRange(data); + } + + [Benchmark] + public void List_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(dataCount); + list.AddRange(data); + } + //[Benchmark] public void List_GetItem() { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 400b3f0..11a8a33 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -20,28 +20,28 @@ namespace Recyclable.Collections.Benchmarks _testRecyclableList?.Dispose(); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Create() { using var list = new RecyclableArrayList(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Create_0Capacity() { using var list = new RecyclableArrayList(0); 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; @@ -53,7 +53,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; @@ -65,6 +65,23 @@ namespace Recyclable.Collections.Benchmarks } } + [Benchmark] + public void RecyclableArrayList_AddRange() + { + var data = TestObjects; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(dataCount); + list.AddRange(data); + } + //[Benchmark] public void RecyclableArrayList_GetItem() { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 57c760e..27ff894 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -6,28 +6,28 @@ namespace Recyclable.Collections.Benchmarks { private readonly RecyclableList _testRecyclableList; - [Benchmark] + //[Benchmark] public void RecyclableList_Create() { using var list = new RecyclableList(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableList_Create_0Capacity() { using var list = new RecyclableList(totalItemsCount: 0); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableList_Create_WithCapacity() { using var list = new RecyclableList(totalItemsCount: TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark] public void RecyclableList_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index f68b870..81ab4dc 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System.Buffers; +using System.Collections; namespace Recyclable.Collections { @@ -36,7 +37,7 @@ namespace Recyclable.Collections } } - protected void EnsureCapacity(in int requestedCapacity) + protected ref int EnsureCapacity(in int requestedCapacity) { int newCapacity; switch (_capacity > 0) @@ -56,7 +57,9 @@ namespace Recyclable.Collections } _memory = SetNewLength(_memory, newCapacity); - _capacity = _memory.Length; + newCapacity = _memory.Length; + _capacity = newCapacity; + return ref _capacity; } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added @@ -107,6 +110,7 @@ namespace Recyclable.Collections } } } + public int Count { get; set; } public bool IsReadOnly { get; } = false; @@ -115,13 +119,48 @@ namespace Recyclable.Collections int requestedCapacity = Count + 1; if (_capacity < requestedCapacity) { - EnsureCapacity(requestedCapacity); + _ = EnsureCapacity(requestedCapacity); } _memory[Count] = item; Count++; } + public void AddRange(in T[] items) + { + int sourceItemsCount = items.Length; + int targetCapacity = Count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + Span itemsSpan = items.AsSpan(); + for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) + { + _memory[targetItemIdx++] = itemsSpan[sourceItemIdx++]; + } + + Count = targetCapacity; + } + + public void AddRange(in List items) + { + var sourceItemsCount = items.Count; + var targetCapacity = Count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) + { + _memory[targetItemIdx++] = items[sourceItemIdx++]; + } + + Count = targetCapacity; + } + public void Clear() { Count = 0; @@ -159,9 +198,9 @@ namespace Recyclable.Collections public void Insert(int index, T item) { int requestedCapacity = Count + 1; - if (Capacity < requestedCapacity) + if (_capacity < requestedCapacity) { - EnsureCapacity(requestedCapacity); + _ = EnsureCapacity(requestedCapacity); } for (var toMoveIdx = Count - 1; toMoveIdx >= index; toMoveIdx--) diff --git a/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png b/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..ed3622aa14e7832123c302fce83832e470b6c9cb GIT binary patch literal 25262 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 literal 0 HcmV?d00001 diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 6390a78..6fcc043 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -6,7 +6,7 @@ namespace Recyclable.CollectionsTests public class RecyclableArrayListTests { private readonly static IEnumerable _testData = Enumerable.Range(1, 20); - + [Fact] public void AddCountShouldSucceed() { @@ -28,6 +28,40 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(testData); } + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsArray() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableArrayList(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsList() + { + // Prepare + var testData = _testData.ToList(); + var list = new RecyclableArrayList(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Count) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + [Fact] public void InsertAtTheBeginningShouldMoveItems() { From 4aa32a32f76b1c17fad60ff21963ed09e9bf75f4 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 14 Jan 2023 17:13:37 +0100 Subject: [PATCH 007/305] RecyclableArrayList performance improvements. --- Recyclable.Collections.Benchmarks/Program.cs | 3 +- .../RecyclableCollectionsBenchmarks.List.cs | 6 +- ...llectionsBenchmarks.RecyclableArrayList.cs | 4 +- .../RefVsInstanceMemberBenchmarks.cs | 63 ++++++++++++++ Recyclable.Collections/RecyclableArrayList.cs | 84 ++++++++++++------- .../RecyclableListExtensions.cs | 13 +-- Recyclable.Collections/RecyclableQueue.cs | 8 +- 7 files changed, 134 insertions(+), 47 deletions(-) create mode 100644 Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index fdc21ac..52450ac 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -4,9 +4,10 @@ using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); BenchmarkRunner.Run(); +//BenchmarkRunner.Run(); //var benchmark = new RecyclableCollectionsBenchmarks(); -//benchmark.RecyclableArrayList_Add(); +//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 e7f3324..af73248 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -59,7 +59,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + [Benchmark(Baseline = true)] public void List_AddRange_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 11a8a33..5451f76 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -41,7 +41,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -53,7 +53,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs new file mode 100644 index 0000000..ed3c1b3 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs @@ -0,0 +1,63 @@ +using BenchmarkDotNet.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Text; +using System.Threading.Tasks; + +namespace Recyclable.Collections.Benchmarks +{ + public class RefVsInstanceMemberBenchmarks + { + private readonly TestCase _testCase = new(); + private void DoNothing(int count) + { + _ = count; + } + + private class TestCase + { + private int _count = 123; + public int Count + { + get => _count; + set => _count = value; + } + + public ref int GetRefCount() + { + ref int count = ref _count; + count = 234; + _ = Add(count); + return ref count; + } + + private int Add(int count) + { + return count; + } + + public int GetCount() + { + _count = 234; + _ = Add(_count); + return _count; + } + } + + [Benchmark] + public void Ref() + { + var count = _testCase.GetRefCount(); + DoNothing(count); + } + + [Benchmark(Baseline = true)] + public void InstanceMember() + { + var count = _testCase.GetCount(); + DoNothing(count); + } + } +} diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 81ab4dc..a6711b0 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -1,19 +1,27 @@ using System.Buffers; using System.Collections; +using System.Runtime.CompilerServices; namespace Recyclable.Collections { public class RecyclableArrayList : IEnumerable, IDisposable { + private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; protected T[] _memory; + private static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException("index"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static T[] SetNewLength(in T[]? source, in int newSize) { var sourceLength = source?.Length ?? 0; T[] newArray; - newArray = newSize.RentArrayFromPool(); + newArray = newSize.RentArrayFromPool(_arrayPool); switch (newSize >= sourceLength) { case true: @@ -22,28 +30,31 @@ namespace Recyclable.Collections Memory sourceMemory = new(source); Memory newArrayMemory = new(newArray); sourceMemory.CopyTo(newArrayMemory); - source!.ReturnToPool(); + source!.ReturnToPool(_arrayPool); } return newArray; case false: - Span sourceSpan = source.AsSpan(); - Span newArraySpan = newArray.AsSpan(); + Span sourceSpan = new(source); + Span newArraySpan = new(newArray); sourceSpan = sourceSpan[..newSize]; sourceSpan.CopyTo(newArraySpan); - source!.ReturnToPool(); + source!.ReturnToPool(_arrayPool); return newArray; } } - protected ref int EnsureCapacity(in int requestedCapacity) + protected int EnsureCapacity(in int requestedCapacity) { + int oldCapacity = _capacity; + ref T[] memory = ref _memory; + int newCapacity; - switch (_capacity > 0) + switch (oldCapacity > 0) { case true: - newCapacity = _capacity; + newCapacity = oldCapacity; while (newCapacity < requestedCapacity) { newCapacity *= 2; @@ -56,10 +67,10 @@ namespace Recyclable.Collections break; } - _memory = SetNewLength(_memory, newCapacity); - newCapacity = _memory.Length; + memory = SetNewLength(memory, newCapacity); + newCapacity = memory.Length; _capacity = newCapacity; - return ref _capacity; + return newCapacity; } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added @@ -76,19 +87,15 @@ namespace Recyclable.Collections if (initialCapacity > 0) { _memory = SetNewLength(_memory, initialCapacity); - _capacity = initialCapacity; + _capacity = _memory.Length; } } public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { _memory = SetNewLength(_memory, initialCapacity); - _capacity = initialCapacity; - - foreach (var item in source) - { - Add(item); - } + _capacity = _memory.Length; + AddRange(source); } public T this[int index] @@ -106,7 +113,7 @@ namespace Recyclable.Collections if (_capacity != value) { _memory = SetNewLength(_memory, value); - _capacity = value; + _capacity = _memory.Length; } } } @@ -122,8 +129,7 @@ namespace Recyclable.Collections _ = EnsureCapacity(requestedCapacity); } - _memory[Count] = item; - Count++; + _memory[Count++] = item; } public void AddRange(in T[] items) @@ -135,10 +141,11 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - Span itemsSpan = items.AsSpan(); + Span itemsSpan = new(items); + Span targetSpan = new(_memory); for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) { - _memory[targetItemIdx++] = itemsSpan[sourceItemIdx++]; + targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++]; } Count = targetCapacity; @@ -153,14 +160,23 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } + Span targetSpan = new(_memory); for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) { - _memory[targetItemIdx++] = items[sourceItemIdx++]; + targetSpan[targetItemIdx++] = items[sourceItemIdx++]; } Count = targetCapacity; } + public void AddRange(IEnumerable source) + { + foreach (var item in source) + { + Add(item); + } + } + public void Clear() { Count = 0; @@ -172,20 +188,22 @@ namespace Recyclable.Collections protected static IEnumerable Enumerate(RecyclableArrayList list) { - for (var i = 0; i < list.Count; i++) + int count = list.Count; + var memory = list._memory; + for (var i = 0; i < count; i++) { - yield return list._memory[i]; + yield return memory[i]; } } public int IndexOf(T itemToFind) { - var memory = _memory; + Span memorySpan = _memory; int itemCount = Count; var equalityComparer = _equalityComparer; for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) { - var item = memory[itemIdx]; + var item = memorySpan[itemIdx]; if (equalityComparer.Equals(item, itemToFind)) { return itemIdx; @@ -203,12 +221,13 @@ namespace Recyclable.Collections _ = EnsureCapacity(requestedCapacity); } + Span memorySpan = new(_memory); for (var toMoveIdx = Count - 1; toMoveIdx >= index; toMoveIdx--) { - _memory[toMoveIdx + 1] = _memory[toMoveIdx]; + memorySpan[toMoveIdx + 1] = memorySpan[toMoveIdx]; } - _memory[index] = item; + memorySpan[index] = item; Count++; } @@ -235,7 +254,8 @@ namespace Recyclable.Collections if (index >= oldCount) { - throw new ArgumentOutOfRangeException(nameof(index)); + ThrowArgumentOutOfRangeException(); + return; } _memory.CopyItems(index, oldCountMinus1, ref _memory); @@ -251,7 +271,7 @@ namespace Recyclable.Collections { Clear(); Capacity = 0; - _memory.ReturnToPool(); + _memory.ReturnToPool(_arrayPool); GC.SuppressFinalize(this); } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index a027410..03ae6dc 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -128,21 +128,21 @@ namespace Recyclable.Collections //public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this int minSize) => (minSize >= _minPooledArraySize) - ? ArrayPool.Shared.Rent(minSize) + public static T[] RentArrayFromPool(this int minSize, ArrayPool arrayPool) => (minSize >= _minPooledArraySize) + ? arrayPool.Rent(minSize) : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this long minSize) => (minSize is >= _minPooledArraySize and <= int.MaxValue) - ? ArrayPool.Shared.Rent((int)minSize) + public static T[] RentArrayFromPool(this long minSize, ArrayPool arrayPool) => (minSize is >= _minPooledArraySize and <= int.MaxValue) + ? arrayPool.Rent((int)minSize) : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(this T[] array) + public static void ReturnToPool(this T[] array, ArrayPool arrayPool) { if (array.LongLength is > _minPooledArraySize and <= int.MaxValue) { - ArrayPool.Shared.Return(array); + arrayPool.Return(array); } } @@ -154,6 +154,7 @@ namespace Recyclable.Collections // } //} + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long LimitTo(this long value, int limit) => (value <= limit) ? value : limit; diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 29c68cf..32ee242 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -1,10 +1,12 @@ -using System.Collections; +using System.Buffers; +using System.Collections; using System.Runtime.CompilerServices; namespace Recyclable.Collections { public class RecyclableQueue : IRecyclableOwner, IList, IDisposable { + private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private bool _disposedValue; @@ -129,7 +131,7 @@ namespace Recyclable.Collections public void AddBlock() { - T[] newArray = BlockSize.RentArrayFromPool(); + T[] newArray = BlockSize.RentArrayFromPool(_arrayPool); Memory.Add(newArray); Capacity += BlockSize; } @@ -179,7 +181,7 @@ namespace Recyclable.Collections { try { - Memory[index].ReturnToPool(); + Memory[index].ReturnToPool(_arrayPool); Memory.RemoveAt(index); } finally From eded9ac3722c118c24227936df781ecf533c2613 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 14 Jan 2023 18:45:34 +0100 Subject: [PATCH 008/305] RecyclableArrayList performance improvements. Count made explicit property, because IL didn't inline the value. --- Recyclable.Collections/RecyclableArrayList.cs | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index a6711b0..0c492cd 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -16,12 +16,12 @@ namespace Recyclable.Collections throw new ArgumentOutOfRangeException("index"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + //[MethodImpl(MethodImplOptions.AggressiveInlining)] protected static T[] SetNewLength(in T[]? source, in int newSize) { + ArrayPool arrayPool = _arrayPool; var sourceLength = source?.Length ?? 0; - T[] newArray; - newArray = newSize.RentArrayFromPool(_arrayPool); + T[] newArray = newSize.RentArrayFromPool(arrayPool); switch (newSize >= sourceLength) { case true: @@ -30,7 +30,7 @@ namespace Recyclable.Collections Memory sourceMemory = new(source); Memory newArrayMemory = new(newArray); sourceMemory.CopyTo(newArrayMemory); - source!.ReturnToPool(_arrayPool); + source!.ReturnToPool(arrayPool); } return newArray; @@ -40,7 +40,7 @@ namespace Recyclable.Collections Span newArraySpan = new(newArray); sourceSpan = sourceSpan[..newSize]; sourceSpan.CopyTo(newArraySpan); - source!.ReturnToPool(_arrayPool); + source!.ReturnToPool(arrayPool); return newArray; } } @@ -118,24 +118,30 @@ namespace Recyclable.Collections } } - public int Count { get; set; } + private int _count; + public int Count + { + get => _count; + set => _count = value; + } + public bool IsReadOnly { get; } = false; public void Add(in T item) { - int requestedCapacity = Count + 1; + int requestedCapacity = _count + 1; if (_capacity < requestedCapacity) { _ = EnsureCapacity(requestedCapacity); } - _memory[Count++] = item; + _memory[_count++] = item; } public void AddRange(in T[] items) { int sourceItemsCount = items.Length; - int targetCapacity = Count + sourceItemsCount; + int targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) { _ = EnsureCapacity(targetCapacity); @@ -143,30 +149,30 @@ namespace Recyclable.Collections Span itemsSpan = new(items); Span targetSpan = new(_memory); - for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) + for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) { targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++]; } - Count = targetCapacity; + _count = targetCapacity; } public void AddRange(in List items) { var sourceItemsCount = items.Count; - var targetCapacity = Count + sourceItemsCount; + var targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) { _ = EnsureCapacity(targetCapacity); } Span targetSpan = new(_memory); - for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) + for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) { targetSpan[targetItemIdx++] = items[sourceItemIdx++]; } - Count = targetCapacity; + _count = targetCapacity; } public void AddRange(IEnumerable source) @@ -179,7 +185,7 @@ namespace Recyclable.Collections public void Clear() { - Count = 0; + _count = 0; } public bool Contains(T item) => _memory.Contains(item); @@ -188,7 +194,7 @@ namespace Recyclable.Collections protected static IEnumerable Enumerate(RecyclableArrayList list) { - int count = list.Count; + int count = list._count; var memory = list._memory; for (var i = 0; i < count; i++) { @@ -199,7 +205,7 @@ namespace Recyclable.Collections public int IndexOf(T itemToFind) { Span memorySpan = _memory; - int itemCount = Count; + int itemCount = _count; var equalityComparer = _equalityComparer; for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) { @@ -215,20 +221,20 @@ namespace Recyclable.Collections public void Insert(int index, T item) { - int requestedCapacity = Count + 1; + int requestedCapacity = _count + 1; if (_capacity < requestedCapacity) { _ = EnsureCapacity(requestedCapacity); } Span memorySpan = new(_memory); - for (var toMoveIdx = Count - 1; toMoveIdx >= index; toMoveIdx--) + for (var toMoveIdx = _count - 1; toMoveIdx >= index; toMoveIdx--) { memorySpan[toMoveIdx + 1] = memorySpan[toMoveIdx]; } memorySpan[index] = item; - Count++; + _count++; } public bool Remove(T itemToRemove) @@ -244,11 +250,11 @@ namespace Recyclable.Collections } public void RemoveAt(int index) { - int oldCount = Count; + int oldCount = _count; int oldCountMinus1 = oldCount - 1; if (index == oldCountMinus1) { - Count--; + _count--; return; } @@ -259,7 +265,7 @@ namespace Recyclable.Collections } _memory.CopyItems(index, oldCountMinus1, ref _memory); - Count--; + _count--; } public IEnumerator GetEnumerator() => Enumerate(this).GetEnumerator(); From 164c435d9853e96d7808f6af539ce976052957c6 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 14 Jan 2023 19:41:17 +0100 Subject: [PATCH 009/305] RecyclableArrayList performance improvements --- Recyclable.Collections/GlobalSuppressions.cs | 8 +++ Recyclable.Collections/RecyclableArrayList.cs | 50 +++++++++++++------ 2 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 Recyclable.Collections/GlobalSuppressions.cs diff --git a/Recyclable.Collections/GlobalSuppressions.cs b/Recyclable.Collections/GlobalSuppressions.cs new file mode 100644 index 0000000..67f632b --- /dev/null +++ b/Recyclable.Collections/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0032:Use auto property", Justification = "", Scope = "member", Target = "~F:Recyclable.Collections.RecyclableArrayList`1._count")] diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 0c492cd..b2eef03 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -16,7 +16,7 @@ namespace Recyclable.Collections throw new ArgumentOutOfRangeException("index"); } - //[MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static T[] SetNewLength(in T[]? source, in int newSize) { ArrayPool arrayPool = _arrayPool; @@ -36,9 +36,8 @@ namespace Recyclable.Collections return newArray; case false: - Span sourceSpan = new(source); + var sourceSpan = new Span(source)[..newSize]; Span newArraySpan = new(newArray); - sourceSpan = sourceSpan[..newSize]; sourceSpan.CopyTo(newArraySpan); source!.ReturnToPool(arrayPool); return newArray; @@ -118,7 +117,7 @@ namespace Recyclable.Collections } } - private int _count; + protected int _count; public int Count { get => _count; @@ -147,12 +146,14 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } + var targetSpan = new Span(_memory)[_count..]; Span itemsSpan = new(items); - Span targetSpan = new(_memory); - for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) - { - targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++]; - } + itemsSpan.CopyTo(targetSpan); + + //for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) + //{ + // targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++]; + //} _count = targetCapacity; } @@ -166,12 +167,33 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - Span targetSpan = new(_memory); - for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) + items.CopyTo(_memory, _count); + //Span targetSpan = new(_memory); + //for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) + //{ + // targetSpan[targetItemIdx++] = items[sourceItemIdx++]; + //} + + _count = targetCapacity; + } + + public void AddRange(in RecyclableArrayList items) + { + var sourceItemsCount = items._count; + var targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) { - targetSpan[targetItemIdx++] = items[sourceItemIdx++]; + _ = EnsureCapacity(targetCapacity); } + var targetSpan = new Span(_memory)[_count..]; + Span itemsSpan = new(items._memory); + itemsSpan.CopyTo(targetSpan); + //for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) + //{ + // targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++]; + //} + // _count = targetCapacity; } @@ -273,10 +295,10 @@ namespace Recyclable.Collections public void Dispose() { - if (Capacity > 0) + if (_capacity > 0) { Clear(); - Capacity = 0; + _capacity = 0; _memory.ReturnToPool(_arrayPool); GC.SuppressFinalize(this); } From 7d72e1ea3299479bc63dc3cab85e0911f955098a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 14 Jan 2023 20:03:15 +0100 Subject: [PATCH 010/305] RecyclableArrayList.Insert performance improvements. Tiny cleanup. --- Recyclable.Collections/RecyclableArrayList.cs | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index b2eef03..436aa53 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -103,7 +103,7 @@ namespace Recyclable.Collections set => _memory[index] = value; } - internal int _capacity; + protected int _capacity; public int Capacity { get => _capacity; @@ -149,12 +149,6 @@ namespace Recyclable.Collections var targetSpan = new Span(_memory)[_count..]; Span itemsSpan = new(items); itemsSpan.CopyTo(targetSpan); - - //for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) - //{ - // targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++]; - //} - _count = targetCapacity; } @@ -226,7 +220,7 @@ namespace Recyclable.Collections public int IndexOf(T itemToFind) { - Span memorySpan = _memory; + Span memorySpan = new(_memory); int itemCount = _count; var equalityComparer = _equalityComparer; for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) @@ -249,13 +243,10 @@ namespace Recyclable.Collections _ = EnsureCapacity(requestedCapacity); } - Span memorySpan = new(_memory); - for (var toMoveIdx = _count - 1; toMoveIdx >= index; toMoveIdx--) - { - memorySpan[toMoveIdx + 1] = memorySpan[toMoveIdx]; - } - - memorySpan[index] = item; + var sourceSpan = new Span(_memory)[index..]; + var targetSpan = new Span(_memory)[(index + 1)..]; + sourceSpan.CopyTo(targetSpan); + sourceSpan[index] = item; _count++; } From 878ec4924a34f785ba41b4e162fcf944e9c776de Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 14 Jan 2023 20:31:57 +0100 Subject: [PATCH 011/305] RecyclableArrayList.Insert failing test fixed. Tiny cleanup. --- Recyclable.Collections/RecyclableArrayList.cs | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 436aa53..27fa4ea 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -162,12 +162,6 @@ namespace Recyclable.Collections } items.CopyTo(_memory, _count); - //Span targetSpan = new(_memory); - //for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) - //{ - // targetSpan[targetItemIdx++] = items[sourceItemIdx++]; - //} - _count = targetCapacity; } @@ -183,11 +177,6 @@ namespace Recyclable.Collections var targetSpan = new Span(_memory)[_count..]; Span itemsSpan = new(items._memory); itemsSpan.CopyTo(targetSpan); - //for (int sourceItemIdx = 0, targetItemIdx = _count; sourceItemIdx < sourceItemsCount;) - //{ - // targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++]; - //} - // _count = targetCapacity; } @@ -237,16 +226,21 @@ namespace Recyclable.Collections public void Insert(int index, T item) { - int requestedCapacity = _count + 1; + int oldCount = _count; + int requestedCapacity = oldCount + 1; if (_capacity < requestedCapacity) { _ = EnsureCapacity(requestedCapacity); } - var sourceSpan = new Span(_memory)[index..]; - var targetSpan = new Span(_memory)[(index + 1)..]; - sourceSpan.CopyTo(targetSpan); - sourceSpan[index] = item; + if (oldCount > 0) + { + var sourceSpan = new Span(_memory)[index..oldCount]; + var targetSpan = new Span(_memory)[(index + 1)..]; + sourceSpan.CopyTo(targetSpan); + } + + _memory[index] = item; _count++; } From 8984a3fac0080eb92a1251b9cb0a6b3af80078f2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 15 Jan 2023 12:23:46 +0100 Subject: [PATCH 012/305] Additional, optimized constructors & AddRange variants added to RecyclableArrayList. Benchmarks extended. --- .../ListVsLinkedListBenchmarks.cs | 6 + Recyclable.Collections.Benchmarks/Program.cs | 4 +- .../RecyclableCollectionsBenchmarks.Array.cs | 35 +- .../RecyclableCollectionsBenchmarks.List.cs | 62 ++-- ...llectionsBenchmarks.RecyclableArrayList.cs | 68 +++- ...bleCollectionsBenchmarks.RecyclableList.cs | 4 +- .../RefVsInstanceMemberBenchmarks.cs | 298 +++++++++++++++++- Recyclable.Collections/RecyclableArrayList.cs | 57 +++- .../RecyclableArrayList_CreateBenchmarks.png | Bin 25262 -> 22660 bytes Recyclable.Collections/RecyclableDefaults.cs | 2 +- .../RecyclableListExtensions.cs | 23 +- 11 files changed, 484 insertions(+), 75 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs index 52d8471..579a4d8 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -14,6 +14,12 @@ protected object[]? _testObjects; protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); + private List? _testObjectsAsList; + protected List TestObjectsAsList => _testObjectsAsList ??= TestObjects.ToList(); + + private RecyclableArrayList? _testObjectsAsRecyclableArrayList; + protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ??= TestObjects.ToRecyclableArrayList(); + protected static void DoNothing(T obj) { _ = obj; diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 52450ac..c9eba31 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -3,8 +3,8 @@ using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); -BenchmarkRunner.Run(); -//BenchmarkRunner.Run(); +//BenchmarkRunner.Run(); +BenchmarkRunner.Run(); //var benchmark = new RecyclableCollectionsBenchmarks(); //benchmark.RecyclableArrayList_AddRange_WithCapacity(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index b87abe3..d82524b 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -7,27 +7,17 @@ namespace Recyclable.Collections.Benchmarks { private readonly object[] _testArray; - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void Array_Create_WithCapacity() { - DoNothing(new object[TestObjectCount]); + var data = new object[TestObjectCount]; + DoNothing(data); } - //[Benchmark] - public void Array_GetItem() - { - var data = _testArray; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - //[Benchmark] + [Benchmark(Baseline = true)] public void Array_SetItem() { - var data = _testArray; + var data = TestObjects; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -35,14 +25,25 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] + public void Array_GetItem() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark] public void Array_Count() { var data = _testArray; DoNothing(data.Length); } - //[Benchmark] + [Benchmark] public void Array_LongCount() { var data = _testArray; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index af73248..766a3b3 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -6,7 +6,7 @@ namespace Recyclable.Collections.Benchmarks { private readonly List _testList; - //[Benchmark(Baseline = true)] + //[Benchmark(Baseline = false)] public void List_Create() { var list = new List(); @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - //[Benchmark] + [Benchmark] public void List_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -52,14 +52,38 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void List_AddRange() + public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; var list = new List(); list.AddRange(data); } - [Benchmark(Baseline = true)] + [Benchmark] + public void List_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + var list = new List(); + list.AddRange(data); + } + + [Benchmark] + public void List_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsList; + var list = new List(); + list.AddRange(data); + } + + [Benchmark] + public void List_AddRangeWhenSourceIsIEnumerable() + { + var data = (IEnumerable)TestObjects; + var list = new List(); + list.AddRange(data); + } + + [Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -68,21 +92,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] - public void List_GetItem() - { - var data = _testList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - //[Benchmark] + [Benchmark] public void List_SetItem() { - var data = _testList; + var data = TestObjectsAsList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -90,7 +103,18 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] + public void List_GetItem() + { + var data = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark] public void List_Count() { var data = _testList; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 5451f76..08d3f16 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -41,7 +41,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -53,7 +53,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; @@ -66,13 +66,45 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void RecyclableArrayList_AddRange() + public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; using var list = new RecyclableArrayList(); list.AddRange(data); } + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() + { + var data = (IEnumerable)TestObjects; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() + { + var data = TestObjectsAsRecyclableArrayList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + [Benchmark] public void RecyclableArrayList_AddRange_WithCapacity() { @@ -82,10 +114,23 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] + public void RecyclableArrayList_SetItem() + { + var data = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + + data.Count = dataCount; + } + + [Benchmark] public void RecyclableArrayList_GetItem() { - var data = _testRecyclableArrayList; + var data = TestObjectsAsRecyclableArrayList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -93,18 +138,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] - public void RecyclableArrayList_SetItem() - { - var data = _testRecyclableArrayList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - data[i] = TestObjects[i]; - } - } - - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Count() { var data = _testRecyclableArrayList; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 27ff894..d6a166c 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableList_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs index ed3c1b3..2851a58 100644 --- a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs @@ -18,14 +18,27 @@ namespace Recyclable.Collections.Benchmarks private class TestCase { + private int[] _items = new int[123]; private int _count = 123; + private int Add(int count) + { + return count; + } + public int Count { get => _count; set => _count = value; } - public ref int GetRefCount() + public ref int GetCountAsRefCount2() + { + ref int count = ref _count; + count = 234; + return ref count; + } + + public ref int GetCountAsRefCount3() { ref int count = ref _count; count = 234; @@ -33,30 +46,299 @@ namespace Recyclable.Collections.Benchmarks return ref count; } - private int Add(int count) + public ref int GetCountAsRefCount4() { - return count; + ref int count = ref _count; + count = 234; + _ = Add(count); + _ = Add(count); + return ref count; } - public int GetCount() + public ref int GetCountAsRefCount5() + { + ref int count = ref _count; + count = 234; + _ = Add(count); + _ = Add(count); + _ = Add(count); + return ref count; + } + + public int GetCountAsInstanceMember2() + { + _count = 234; + return _count; + } + + public int GetCountAsInstanceMember3() { _count = 234; _ = Add(_count); return _count; } + + public int GetCountAsInstanceMember4() + { + _count = 234; + _ = Add(_count); + _ = Add(_count); + return _count; + } + + public int GetCountAsInstanceMember5() + { + _count = 234; + _ = Add(_count); + _ = Add(_count); + _ = Add(_count); + return _count; + } + + public int GetLengthAsArray2() + { + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray3() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray4() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray5() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray6() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArrayLocalVar5() + { + var items = _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsArrayLocalVar6() + { + var items = _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray2() + { + ref int[] items = ref _items; + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray3() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray4() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray5() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray6() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } } [Benchmark] - public void Ref() + public void RefToInt2() { - var count = _testCase.GetRefCount(); + var count = _testCase.GetCountAsRefCount2(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt3() + { + var count = _testCase.GetCountAsRefCount3(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt4() + { + var count = _testCase.GetCountAsRefCount4(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt5() + { + var count = _testCase.GetCountAsRefCount5(); DoNothing(count); } [Benchmark(Baseline = true)] - public void InstanceMember() + public void IntInstanceMember2() { - var count = _testCase.GetCount(); + var count = _testCase.GetCountAsInstanceMember2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember3() + { + var count = _testCase.GetCountAsInstanceMember3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember4() + { + var count = _testCase.GetCountAsInstanceMember4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember5() + { + var count = _testCase.GetCountAsInstanceMember5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray2() + { + var count = _testCase.GetLengthAsRefArray2(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray3() + { + var count = _testCase.GetLengthAsRefArray3(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray4() + { + var count = _testCase.GetLengthAsRefArray4(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray5() + { + var count = _testCase.GetLengthAsRefArray5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray6() + { + var count = _testCase.GetLengthAsRefArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember2() + { + var count = _testCase.GetLengthAsArray2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember3() + { + var count = _testCase.GetLengthAsArray3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember4() + { + var count = _testCase.GetLengthAsArray4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember5() + { + var count = _testCase.GetLengthAsArray5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember6() + { + var count = _testCase.GetLengthAsArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar5() + { + var count = _testCase.GetLengthAsArrayLocalVar5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar6() + { + var count = _testCase.GetLengthAsArrayLocalVar6(); DoNothing(count); } } diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 27fa4ea..2542b79 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -82,7 +82,6 @@ namespace Recyclable.Collections public RecyclableArrayList(int initialCapacity) #pragma warning restore CS8618 { - // _memory will be initialized when the 1st item is added if (initialCapacity > 0) { _memory = SetNewLength(_memory, initialCapacity); @@ -90,6 +89,34 @@ namespace Recyclable.Collections } } +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(RecyclableArrayList source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(in T[] source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(List source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(IList source) +#pragma warning restore CS8618 + { + AddRange(source); + } + public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { _memory = SetNewLength(_memory, initialCapacity); @@ -126,7 +153,7 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } = false; - public void Add(in T item) + public void Add(T item) { int requestedCapacity = _count + 1; if (_capacity < requestedCapacity) @@ -152,7 +179,7 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(in List items) + public void AddRange(List items) { var sourceItemsCount = items.Count; var targetCapacity = _count + sourceItemsCount; @@ -165,7 +192,20 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(in RecyclableArrayList items) + public void AddRange(IList items) + { + var sourceItemsCount = items.Count; + var targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + items.CopyTo(_memory, _count); + _count = targetCapacity; + } + + public void AddRange(RecyclableArrayList items) { var sourceItemsCount = items._count; var targetCapacity = _count + sourceItemsCount; @@ -180,10 +220,17 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(IEnumerable source) + public void AddRange(IEnumerable source, int growByCount = 100) { + ref int capacity = ref _capacity; + ref int count = ref _count; foreach (var item in source) { + if (capacity == count) + { + _ = EnsureCapacity(capacity * growByCount); + } + Add(item); } } diff --git a/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png b/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png index ed3622aa14e7832123c302fce83832e470b6c9cb..b70bdfdfcdd40f538aeda58b7a58a072abcd64d1 100644 GIT binary patch literal 22660 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 literal 25262 zcmd?Rc|4SV`#xMLS};j6)~U3J(AcuhkV2_Yb}};AO@*v8LsFIulkC~45XQbQlQ1Lu zzDJQI`!<7N#&fC9eczw&@B7@Z*XMa&&wtM!2wmfSo!9w3&*M0c<2^w-cQx3K@*dr{ zZy(!D&FlB}?K|`y`1iZR%)n=Zx7wq?m;EmHG_LI4dPjBNzI^<#ori~j z?~gpyGl3Fy#ev`_~nCF{OEgYsRrWsu#ZAS**r^;#!g#Te!kh z7nP<**Wipv1Y^<=iGz~B|N1oLh&he=A3r$p|F0j&LNs@mc6ZtlyL94irKt@L`qAGd zv-|)#_Ultz3@f>jo{w)M^X4Mi`t$uAjEMb6h*FXewropc?% z^SAjGkN{^a*KyO|Kg@mbO1t-syJMdKig=cp=e|qe5`AS`)zmPPcyz}pLc8N_@%pTF z_GWS~d^W}|z89g&4_Txt%6!4)jNHy^B-J|-FPa79aU$bf=Mx(Fq0-m5hl zz+(yD6Oiw}kvHEIh;5gRBWskprZW7VM-^gTo^tOO9u9C~h;Gjiwx9c1ZUs5?ece97 zB7mXN9B-5#p?C1GZP+@wv$c2OWsvwXCemzi)^ZTXiXeEyX>@hJH|di%uGMT= zs6_8>jc#a4;@p5l7V&7Gx89Dh#9ueMYX`G1Bf9JeLNxi1kC}pugWhrTnp>8Q(mGi3Z5zpNh88R}HZpZfWm%zQ7zm;Kex zizJ6(y)8YqVfXoJ1;Q-35*`pZ@QZGtwPm%Qa5@Xlx3~VPFDy9>?X~?AlJ4KO6Y5xi zzs{G!a!{f|$--l+S1VVL_uONFeFwILYi2U5ZXi1n19*}m*4_$0G^Msb%V2v9QTm96 z^7Ytkl5mEygWy>3-6EldYo8@%hO_R+@2hEuUKPrJ(~Cr&(uic}^9ybN(CXA& zYg!BRUQfrhQ$4@9@*(ZXl*{Vtrq?l_Bo;0?tlc3gk~OoOvUDX7S*QjwDoz$}(;h5h zv!%IKQK(*sJ-i;Y7Ep%&6#rr6sBJ5)PQ`#70hc4#OAgpb;EAFsd8vq( z6w%S)3Xvp2iVBOjR)yhJq&|GA!7JAENvjB?6>E@Wcd#W%UZ0l$PVKcuV{lYPdlTN5!nm*)F$3|Oww+I?L@H@wbRl4+6&pn(y1Lm~9-SSy2 zNK!feIloNP{RlEQ*qN8^+Aagy_f0Blo(zO;;dh1sRY3)3pT z7i`rMR#3KR5H4%jVYA-GLI1L*E`NR~cl96}zE9%XRtDiH9d4|q!*1(x;}sWO?q_g& zk6YW7CS;^nlTCSOK2rJ3_PDYIU(8sPS&NYngfgYP)`{B-6UcqN_ORoPyp3N={IEB& zq!EAP)%Uk4%9csSsUPE&K5kPVHo0cwUAl^AlmCRG-Xm}lg=|_Km`lra>jvj zxx<0&VOkjGo@~snDIN>1Zt*@uZgl#74D){M*?u4tqCTVox1>_&KK{cilkeX;#IS5& z27esm>|bfNj&u*@kPai!EKkwfYa?KhhYvI!N8xMyeMR-eN zk}+>?^4JTcPieqelhXCsGi=6&J%J9h<)NO}DI$DZ7WLm`aFY=NDTtS=x(~Q$J_Y{# z@f7w$S=lohZKvI@*qrXKXS&y6QcQ?4R-V-LD?yo#6}%0WF#yf#LAG(2i2QyQW%|;T zA8uf5iGTO=UQHUlqLnBz@s2ymXiQS8CxU#vMMl=O-4*jS2rfV0n)@d#*`y2_S;Fmg zKNw^jZ{QT4z`_WG&34lAtqkyVUGaOy*2EXcpK@MX`%!2bb!hxp)3bKhb%~mEoRJQe zs`Rs&4>4d%jxM2oPX4l2f1tc6-2H`^oNZlWt@be$qp``$Zfbs4n!+-_fL7{>cq}HQN1CNGDNI8q^IR z6Rhr;eNTJqa(~0f@Yzby@)*WmN_<<>eN_#!$mVcgRz2j{Ah+?S;#{?iQqosHao#gL zSlBP7tk)lQ&bYW!OKM9LcDU?g&Z4KVMN-W`$ZqKg#Lvu$M1>(8$hM)AIQBw)*Dc&9 zxCF+m+aG){{@1J1f0bs)&tqFgZycUG2rz##!xnnQOs3x26$uTaC&y7GVP7Ddmj!8| zi5ij9Hvwd*yKRe(a5(7J3o^|oG%1n~^A^Isx!j4Rg8NZ5>dl*zC~8{@+VQ&Sr=*27 zvAqbfy7vJ+cOZTtO~l5o>Zk)U;e`*MU~LvAVy6YU`Vz=-?6MJ%5OH zC6x3wAqReLM~^LYI?<^ILg>QhgAtxL^DeBuUW^4TfYlsuSZhi)8#>J_z<)i2w>wIq zRlEF+>*Ll2DG2n*Y@P-W0Mx_=2?b=9mRI>E^dI7|3>8FQE}Bp*p5xRIP(fBN(tm)6 zREF{YV6sEDpiV!0tF(oiM8%$vp6p;0Y6(h5WC}(!enqSsni4YcSjxRaMR?GDf{bj^ zx%C0!UICXst4=wa;=G9DLH)Jqwz3YjPUe>UCy47VxOqQ%b8t20IQE=%d)mC`iESNi zB@1oJ^Ii3FX4~8f*(ds*(_q!;GWYk~CN5*m{S|nuIxN(RBjo1TprS@(A*QE|d-3C5 zo7b7VGNi%P*0p&!hOgc%*)MItIzrSadfmWuXhv{3>)FR8MSCECJz0q6zN(!}-cQ=i z`ZSK+mTEB@wjAZe$stipi9A*>X;Q&f<~?!Nzy;cZxvto|;yLO5J&f`6B2bnS85K54 zO^d5aIR)eZq2JJ7;&XYAneRl)BRTck{>Yz<0gY*72mpTPjOt{v$L8`uiS1#qbpXea zs`gFab2$_$75)PZ6)+f?rviuVosI>UgK-~ zFm;sQ1^~#G08ZW@!xSJ6RiQFwz(o(~=6LKfCUpJB6!3ul&Pl96dxi1eoP`8-IT8nY zzZD#@Q%p8bfRbW(|G}%K>}I6=!zFhBH3z(QAYQALORf!wK|K(PMLhNytj|Nhp$u4U z9j#h$`}-FEFSR|m*K+KsxQyXfeaBAn4(S&RE4~4#`Y;zl4^M2DV??Ko- zp7sAQwg>Awl6)BDrYxaS6aO{DhP3~zm&0u9i$!A4e_gTJuR1mM_q&u_A38;XxYPBD z>P`r#Y+vb~`?2V!_mp}yr`luigw(GzT0^xg`WTdyKc4NUlW zZ!hHhXKnCm!9^HZbQWpeQl=37g~Q<)-IJn|&w;!!@hx#MckfohfF~tujU2LGuFLEc-6dAy{rN1smz9#(Un|>8_?eV)r$?xcm*Il#Ig8Blei|S%S`C|x zb$Z5H$K`+lZV-=41Z|A(7Sw@}=Fk5DGo0B=UeEnNMjj44vkOOA|M&bc`&<5KJe@q| z=Xv43XV8cLR~htAcBhZ;iVt?bGapRqoHk7lg+^==tH z75@f;Up{l;B&7aUFJfJvc*?QMAKvraXY;2Z{04&lBkqYC-bZGM-)83#1I}P<-M6r>q-0>0bSXY#{zBO%A*USCr3Yc!ZRa%}vh{n|&i`|2Y zT8D42D!d{+D>*#=D`HG!jAV!7Ju z`$aU(S=(9vz;rMy$Y7z-@DtN{^Y7{S0o?~yj95BwV{^%h3hKKy-{Iq$0qrWgn`*}i zX-~>RH+sm_7FBfefs()nu%I)f0Pse(2Kr8Zi52TgsFVLvhaco%25Yr2-(+ZGB4afl zlWcZ!xLF)k=oQ&I({QtB8XUXUB=_STqiEtj1=_FEKje%X;g(hb21J3E{i*c>f$6kA z&*>*kR94+7+j%soe2Q}zr8s|2oA9F|r(I61PP>kmuyTZ^zi|mwQpNd3Xp{Z6`I)hq zs|9G3jhw$rM@Dp(jS|Ikua?0MpI}Fq8M5p=!@aBzwv~`yX^0Bo3#d?0o_UzgP)b&z z9ACh7lvOL74r_vW1{Obs5P#lptBZV;RnpV!KXq84!&)r=vFvV(H`jEROb2Lezl2(~ z#9glS(*~^j7&r-|%}cb``6pS5UJV{Y1k>z6mqso58>&BoMm@wU0d|?eb z1`w2$Qm;l9?<2b5f<{}1hpTtKPXqHt1i8SdjrQDO&~Eti=!Uo>#!K9|0f@;W4KeZ#RmCCe02rK;M%z?e2HY}dI~TF*gE~`$i)lUMs1_h&0+!hSG*cK zA8RKzvcEr6G7((ht%h@;%^ic!98bnt6v!qcwyz|%67Au2_;&jg^BBwQ|U> ziA(X?hJARi&HDIDM*b*Nk~!hSMz2#>pQ=Q^oO+^K7V6by{{ot==tL&Wr1$h3@OPd4 zNm*;bbWz!|bfu{M#5Wu+FXu@c5As(>UH$#tHB3S?HsvkZauBb6?_Fh+>Pc?W>jg;| z)W7tFy6tcl-x;6ft@qRavcQ_`NcRT)to1Awe`7$^4owY)VDG5QRLJ3_Ns9G|qQy0``+PFQuTYUwCPu z@aPiQMhc=w{Sn330V9hlWMf;UKpY@iBDMItL#fpjK$X%C>E`M<499&oto9bl zPN8H;4y8xBM+sKPKaZ+*kK;)K*O{uO*%MB!cy>hD<+jY~8~gxz((mf^vKyx9^(I2( zYY+=B!p)8k;`TF8+4BIO(&<7Lz7mZ&3-OgwQy~+>+x6AuZBy_<;-TrTiGl5gz|9X3 zy8cI|6v53bZZ({iv5a^g=R~x_XLZMm>f700DoMuMXLG*sBS=EE_7Y)5cP@7T+yJ#&5x9WP4Z}{qodf36 zn77{5*ow(-C9k|^xSKa$CFFQ10NeR+HQH4waZ_mzwL=ab36OZt|LI1=%V)U(TuJ$f z??1@Cz^Dvw<_UG`o*FjfOgM2Rpo^c@$*qWqB{6R(?G%Ofi6g@u^V9NOJsMrjxKW!S zP&(D22>Ncfg>ee#L4F8J$M5*WsE8k9C1#E7gWMwX*SBBuwE7w~+?65azuE-`Iu>eH zQ0jA7WO+%IL;OvNhA(~KMw-@=c})ap`6|{<&ByXA?P6BD1$)c=jnJ@#Dv`S;5S()e zKtPrsKX=kMr^C?&h5DIBZU`~86o%>H*r?T&)2r^j#bE>g^eDtnx;SJ~i4 zfa0z=7rk(wFt|IY*c@1r>)bxWVkpF4hx|`}O%|M=yQPeX8deZ!X>ihfr|_P0v5dm&XRSs)@vcANQ*wW{n(e_GpNSOCw`t1!`|AsKlz5p-E zIJ%ig{Ah=RPF?*cYv0vXTPs-(SoNAD1sak9|G_$dZnq}PSZ$U-SmqgWZ|&(LMU#bw zaKGJT>?GW7x0Fa<=wI@t{c7G^PKWqZt-}`)2|mnlw%@AFF_SS!&DNAV$~;H)l`$?p z=ZswRVgTga@%yhOEK$M40!GgSU+;#c*|vq=9?)ttz+RbyzxFL*o3!sHaSUbM9qHs= zN(^qpaqiIfK%@y|dq2ts`UUbz)dyBi1_1;+|Ad+`>m!PO;$oWNVz-EmJ*J*!C~bj? z&=@x{9ILEPpMEQp=kHKe=&gYv#-{wnlc65wR_)=v(`>)lYMe$b=?pX4l>?%gbT#qq zz~!(l6$NtnGk6vP7wYj)ar~k8AprDd!D1+o29;rd=VH5w!wdQs7iWS2sB1$y!l3%D zZx{Z5w+>k;r;3X=yPiD3t?mA@bqQa0z0yotDu(+-ZrLO&&rKZXHi8@x3w38<6KS#h zd7JH(%P;T1i-LXiQR8#%@ZAyAFtRVn@60&&SVkiFVq!0Rk*RmAHyXgYd-sskpn~(e z%r3+1sWb0+A)&RBbU2>p;%6Bcd7~&8n8K%M<2V-ewQqN5JNZ^C(#8{z=a4e~1>nZn zETut>+|hJ>VEU3l$+bPrmp08sHyEv~H|F&|(f2gOz3)khw&>Us$^RhTrnOt;nDxuR zwf_n=^I0I&8mgG3kQH;wv9qYA z%_6#I=WB|W$6U=fmIcAGJ;Ba!ZHH*Cm3?2hU+A8`ub)>a|g zrF4P^b^gbHU3;E>++Onw?XI+7>)$*_FYzXRO%}sG%T?kW)&mNJG4@Y}gvfOT0D~5M z>YbNjLshAFsvMo^vag4pew*V9l2yNQ3N;KJ_WGWFPw8CQ1*q`W)2CW*brh-ZW>s!7 zZGPo((=&|qvG!rc{;G@qV071~$r$l*e@oxKmLsUm_iVI|^dU&c3-6*@;y`!G=4EwB zHW@c1i)is`&lZcz05A+x-m2gwhuQlK32U!YRY#aa;yQz!0_pkkP5Pz)4m73ICNf_8 zCW2;$gN2&%Za)70u02Lk(#vTaoatf}-`Jnex~#pkUu+>2pAkIdQr*B^~V0LYMMDAF-%$8An9tD5cYSC7`Rezk;}gMi=FQ^cjoBS_=lvQEwyJ|ldtF0 zoHNu>zRo8kZJo=md7z+O&{AMT3WDWHiDzL7x`xV(W^3ixJkjK#ke$;4FNFJ1yYvvr zGujC|Z|(LK$~>P66bre*N7JWe-KMR6Ag%%uKCv-!Gd8PIv$Ezfg1gWg0DEoc|{oU?lUNZ+kUN zc{KKBR8zWoS8tCz&5s|Lv`SK|*M0G^H4&W4PdZRCp_S-e8RS4)YtVH)=h-y$sJ-I3 z_k!Oe+1*ccLEZaPASGNz8b47_=6NUY5PIj_wuqmu&(zD0`Kb2(^fgk(?b4CbVfy~vF*Io^*4yNHxKm(w9h&l56au;U=P%;E84Lkj9`Kz(@bR=8Io zIyQm;gzcf_vv>(_Wh$pOA=FPyt&N)2r;bWbu(BD zZDCAPvA=UilxmYKfHJMt3uOy|7Dngz&h?RtbdCGqB?-{DIZd^WGc`00tre9NV0P^^ z^TB?}w>bk|Ln5xQ@K*v`s@ij?%!I%T+ z#dMRL9hRw(5%JK^ArWH6ggBnOs%H5V7vCHOo(XGEF~}`As!i#B0R)8>lIP!-+Z45QmCH+hzt}J$^si9pyA%hu`<2{A zTkXBtU&)g9aOnDg-m9={%HTx*uj$o8GtHAuG{ETr6NN_n6$fgk=*F*8fAtI%V_f-VcB`@SpI*RdB}UTXKwJBJyX5pf@P|8 z_`OrZ23IrRvao!@)Fw-~ZJrHeTf8}9qUI2tfAi!(=KwABi>nzYY7>=CEzPvv3raHO zA>T}JbquF9@6&{w{iXwYeDgphM$Aoz%YNQ`m(-NUfx3Zckv`azNyg)HR>iKkPj_L3 zsmsUuQLIOs0`VnEbM3`DqPu>p*GE{YmEWkEI?g0I4C?WeObomWC+^VwK7oYi8dUla zUK@#5SAA*sNUrV7R;%aBaoe&>_+`^AQ!M4L45XX+_L zM<3K#JPQcl=RPnGKy7mD<0cUE5K|WjXe*FQ5_o&b~8zDM`yDH7adY2kpwc~s!d%e4W}u);Bi=zLy+ z;wMe(&$olqHV8QF_VXG*hyOiK{8SNTFYdSvW%f+(ck@Sj++Kh9p2D#}RRW1pxcFYxd?Z=#$ z*QEug+3fzua#XLJ z>DVXipHj%~mwW-{<;w9Q5j@1XqcTL;Fdft<*JPDNiDfQi%;YHiYL~I9%M^B)iD*z`in~@ROjkzRlAYJuVHYF7E<8CK?fY7tw7G? ztCiuG9csO1C1DiS+ILp~^^ADFU!@NoT|KuXHziK=0?4qQ(Tb|N8KlUmf3(yQ0OGIlz79=#xdm!2-W$=shIoPl_EkRky)yFftvF3rR*b5E!_SJGCA*Vf zXEjEY)xnC?iHhR1;`T%qE(i|D!M*JFcO+5Mux}c4n6Vq1K=z9j^k(^u^6~`3M9WVo zYdDoOu<)Q|02`84sTB52NZ0}s)I^PoHfpRk$P)=kNs_@lW|btyDCT{bKC)(wO*1Po z$(4*O<<*;?yH8oi&wc%TYWn2BLw?LDkYQH#riEyyMum+)wcNs5QZ$Z6;;- zukW1S@&;Z1i_pDo^ia6{o6xBRd@yrhGjk9#YschSdE?GQ8sx2c>uw8mfCp09hPwT| zBN-y$yPJ=^&BzS2^l5eJtCU(pCvW|jXiO0cP3m#(<6~aO)!A-5VO1_0KNcvBuV{Jm zpbuo0Ryw<4Sd{Bl!Ht$~oFba!7Vu=>*cK+4j?6U$*fTY~GL;i;>rkDuNYqI{d_3bY z`A++pj-w657R@wqiHSAb6yms#=s%Vo18wLTc|byy2!ksqrhF z+V_;`pWA_7nHfeWJ}P%CPILg<=`2W4e_jhYX8T!g#na@~);y)wpu^X5)|^Hef12el zX1tp%h;R-;Sh>u(w+zxk^CZC2!h7?1e&*WSnDyDSPEsW2@mpUL)M`4_)cLZ3{SZPb ztFAa)Qtad#@@ZuT!dd#(GY)6W*CBC1H3pDmhyl+gdD(dUBnhU(46&qqe(SZgst5QJ zkbeCT-~5!~!BJ69YG21(JJX4zhLUQd)FnYS&2d}aC1jPJaq9=&4M4`4wUg#m!@gWZ zn^3u6I`8TO?oHO;^RGZ*{WZP}E z2BxF|D}I^|$*6)*zqi$Dl_&FWXA{o&QHq?d?Hc(^gM&2V#Ma&`uib;JGFfE<{>pDB z{ATgmUKHgLTx}H~&!jKiPRsT_i2Nq69-~#FT{yiT`DDdKt1l_P4ANjMOB8#wZE;GC zCmW?r6zV7Q1HN3&1zs7!B(%$UR93s6a=GSwuUyyK3KuM+${l*K*lt7hCzlhi{QWx7 zbtZ?+7VvwCiPa-3o~e}O!DgV@m}!`>l~BjF%}=51S`zWr7RX#sNix_nQ6#S6hXI>u z0fTFMHMm0=E8T{3Oal7MA3}HK@KjEmOjM!orsW|=ouQdvJx7h5g`nEbGO>YB#PUnp z#+z2RaJ9df?acDKmws4`uUkYhuYGpnx6U2Cep(FL(Mh>bR5HX1*`aP_ygT4VGG}{V zNaM&_3D)77lH*Og)`LAnHQH8VhVn59bUep4)9gwoKYV<}z+EI-ggbmsVOs!M8~olv z@U##o!f>%Lq|N!^p?N_8o0gCQxg>LAl4e~jc8IAz68tM{5RXF7;pZQy6+u(EFFJ`2 zR65{_T2^m^>^^YYs`5;@P{(w5=bdbOzK^S7@3|v}i<{mCLl1Or-#Lh@}ORSI|8yOu@hpLq#(9>v~;W?V@Zf7iPL%^nMfUFGuQraABq=d6Tk*ua;l z4Oor?9(2|in45IEAM1VciqZK*V*vh}4Fz;EZy%Obsa)~OGu}Mg`}oB86Kz&Yzxs&t zcO3D3wPpi=hMOaLA@(mmt>QBk`WSP-DyL5h(lVvtnH56%uFzeU%F&+>mn|mT@*&yK zDm zz*071ap_m?FD$;oOwGiX=N)jvhZc^)a4;N7bY1!YVFOuCT9w`epi;?_`sDux z4f>_4Oo&Zu=l0TGEQh(5A-ZI>C#?M#Gf>9A{=u>7SFSF0aI#R&)KjoF3`Wu}!jFkb z7Eo?b2mYP)ZU^4I6zF8`u6}zE5Zl-?WnUPjp)dn^L6?FTvTnZE{2;-SsH8I!EFgPM zw(MfQy+Fp{}=(10B`#zZi?q zv+ADeSEz|f&|uZ#unR4^Yjdqm(gB0up61dXrw1WIA&B0mzYCa2Ugyvc@7yF)MKmTR zB&j#JbJMa2l&2V+0+`439wZG>`(hfLO4J*2hC-ylA!FAn&37+zTLA^k6yDDT>3IAR zj>7-Nmvi3A0tI(b^coCnKJi zYdrtvmZ%d__sKtkLRIa9XW0lwNcqeLmL?{tPu#yt?4*67O8sWetq=3cy^c#oddXfl zO@9v|KkS~_f7;_PNmgng+3B*Z6jE6Ax^(Gbt94zzsZ@pY9{-XKlWIrb5C zg685|LEAYOM!GLVg-CxC?+%0tcbi1$SyiSwWu5swVfe&+ATHtw_V6*%aJbe7tz(SG z!$C%Zz&ILXh5Qa$wByEhOY#zXqm5!0e)O^T&+`4U`^<~vd9;SeX-`Jrt`hdgi8!o1 zl;Ac1puw`?T;1qSUN;{x^$gl{8-9AxV=#w^%0~CYdWkEbHM5=D%6uEY0G*lbPCk?&&lc1LyN@*1W-;N`+<-PS*+ZImB5%AWi6} zwoda2WwrO|Oxp1#ra%CXDH|dMawW+vbYV@n*$S9eu!)pEdm`R5;KYvWcThwE&~SnO zh75a3>X|i2&cg>=TIfT-H$gA1MBWDM&po_f^>fFxgnDfvI|Xe({nEoO;-W z*3_3%j200$&z!7mnBtUIY?I_?+8_g7RC56#E|_vCVL42uW*1hb*!L80diqn)%1hsw z;2``O;C*Nv{#<)HphS7S{bXWGWv=!c#NxZ}ov}$5Rypkiue)!3wFFI(wHG#aWJh#u z*{c;?*mAwC6`2?DfE!ck$G6??iJ7Ai?$`MRmaztg8kLyDI zfIK!30LQGg7K9X9+)bls)X*8*5~!{DUYq5lk=z}PjrwoyaQeDF8BX7v_~0XF;mY6w z25xnbdn#~Pxdu7MWcB_7hRTQWU~HBG;nMvbj$o+ZKtGOqLW&Al-uMAGi9rm7Jm$0W zBOsTOk2toX;*%JQAyxFso7YxOry7!OMrGx_xha zE<3G$GRc(win?x-%ZX50NBUUeC1+3ijNI3Ny_Z}%dMNa>qUX{LSGv(@w!1NLYCjZ!n`Q3 z9Oo#KMvl)7rrBR=2-}G(bBM{y|F%+8yJS!C(q^+YJzrFHI4IUZm6#t;r-qu`^&0lU zPNLh;e@dg_;k!FoX!C2rLUs*qx$0km*%uw>7NTr0&%~1Mh#{ScT_37?-9*Db=g+^Y zT@=Jg_?HiGTUF80c*B(5;FriP@@+2QH0_6JrM5>hnI?O+Xsvut3XKIx6)?Qy=oOrSlO^jxmv zBggv1LEZM+vjK(c_rJVm3C_V%AcH!5vb?U<(XJs({LG~Rzao&@a97Lt%>&F*+gxmU zU&{*8%I5?#Yy{1^1^M=lcFkqkwz#f49dj)J+s>c7%_NT>j;aF)6LKK5Av)?|`31aG!$&Su+PBGB+dKxRk!kG*p~0V&hJ z54FX98$G`)*n-VnU?o3ovnOy31TA3;nzvZ`F(>%)wO{>_N+V+)J%6tr>uv8pcEt9? zamu+fH9HwZc9tNH*{z;vp#upuE6X6o&KertF$25v-a!} zW0W;jifx_mPyshtgNafb+NbGL)HZ9CBx&1k*csz6|EcB%HDA?~fK*yz}f! z89&nhxPPse__KhcA)+1|pF_tkwe1h-C^sxUw1Z1}CQAp zgrHr=t=Dpg+V{-TUSx@zQa;7?0~h>@5SO|~zlS@+eBFe$>tD=MX3-X+Q-?zQ5(d=K zsG@cei_%Z6_cJ!1lA;(nh_exCIXh`a9iXgFNE~2bjSogXYWoB#S$~2R5{{b`NqkVm zQDJktI2JfL5gLZK(X+Ib*-JvbPFvu5GzKjHM_5&r*$dgXFuc0n4u8IX-;;x0xKa7{ z56^4kY=@gJEP?YMkS>yz4LS~^(60JkW4c?Dm76eO2&pjSFGW!l`c?S(5tBu(Y~_Xzv^t zlmu{!VXx7KP?5nPN4;O)!J^LSZv&z74W9ZuA;XegntR^Ik7saGh@X`0X#XqWBSLx?Jqi~=aO;v=!n|v+atfC1NPxs^g+QhQpBjX-38p=2CZiu0_1?$;S5z-TC z*8%h0?C=HcNb>b&52*RD43&nPXPT5^%UDvqr^7cTHN2iiYoh7NcExND4p@Fved!84 zg__WEcw)|#r^^2bB>S6os>6m-f7mjlwoNSq*+2Z#3DBp1Yl|9oSAy#*?Kf`%fOAxz2%KxPC!~nc8YJ1P z{v*p;wbZ2DgKaO~`%fYEt@a#=@qnMmi%O1JKG|f zF>qnYaRmbOis^e4=$;X?7kpfss{0XO&ntach4ITFc$3}N%*Onq59+k{Tp>4U`E6x= z_5>bPg!6MMaek$`qTl-^(xYWok!3O`$Gy%Mt)sQUKCar|MUwT^ zm20)-i-=!``Z&Mm3ks(ZqN`>Q`L!3`3cv=dBIZtW#F$p4n`56t!C4!UG6f(ZHgK?W zF=tPq%}hG%g=BH_>>1OfjuF$4-jW*=>$579s^k(^g|gs@rRjM!qMBjz5YxW(z5vGS)yhm4%{2 zJK6!YJ6q9S*aXu3SerG6^c;|L7mKtt{*rS~2iIM+-Mj>(BY%cqF zD7ST`@^2V`aJv%<-`!cuVdU|#Ae_9>>|bUA7=Si5UkA85tp`c5S8~Ic!=4a#8Cs?= zG(~C?G-SvKcN<@b+eVSL1VM1A& z#HccNg22T>v(FS^b&>KZam?!4Q6KtB0HlVblG}>Q-kn{${l_e<4|)Cju#qTli!&E+ zm|wdV*o$p6{ltr+d5N1vCJHQGiQ>g7ZCG5(C zhkHlzm&@vHJvt*Y(XjpFufgF=3~aBm6Hvjxu)U7}QXoW+<=qr$V*S z_N=jHYKVl=OWJR^z4i{jkdvE7%}hNd^jT-BIjNYd0N;J$db)MwSvymNq^z0ahJA!E zz&_e-`v;H>u&1_~9g^&PDg9HCdeTO(Hb?m;7Cy36w>)S70H@{~r9jJA_GC(3lA>*1 zQ!MZ6%Bil4&)w?d7fWebt@uuJiGa7&r4nCI8VI5g4#hvto ziLu$xept`VlVH_jFU z&J~}T<08G-`!=0b;Ha|A^C<$zJ_My&;`BA-zJS_=hYFJe-P*Uo7i*M0w|HC82OgOX zc8<7$dgin^7xKe^_xUKP$#~Ke>@fHq7t{oGng1l4)1 zVDIdV_B^Bhm|E>&OL_Vw8*D7|n+AE8YkV-fZjxso#CrSbDWO<@=-pn8iuEa|U%nAC zx2@$NNMoVQyz>M?81!8mI5rStWbe~>B8V<}rC*kGhBSWJ5C!EH*fBU*zE&i31Ue-)hwaO7mM z>BB{2_OZJ1rM;@%X53nQKyjs7+ik=9Du)Jb8Odm7M``kZVNPX>3ygLj{Qe;kmL28= z1I~S<&G3N|@;J#c6hjEXjDi5W!ypq4E*5TC~E%SW#%n&#QigwlM%orDH z&FlqS6Dw7skBh^0rJ^#4P6=?FhmvW zWtiP(uIJl^=)vu~`!2!C`;54Lv!?r$wLoRMoth~j=+F?!?8EeZjDO&*-%$^sy8H>c z{J1Ao53(?56DCH-FaPM|g_JX@c^$v%yZ0=2|1r52P6NZ2{X<7bmGdUtU%RLXJzMNW z{a=lpi$Bxt|Hl$7|_+%T8G#jct;>lVJyJ}aVMZ_5_LjjRvf;sX>q%E$Om}H zl+;S6-cud?bdC3Uth3oyT^6 zsJbVn0}}PvSS?DxPsTFuuw08ZWaf)D=pOYa^n_et4O>J1BB}A-`7CZcVAp)lt3K@J zr&Y%btC%}RU#}c;ohTAvH!6#TSZ4-3Q7_>lpSrabG;O`6RPo-^;(;%|w9?oAZVqmw zq+B1B-v09kN7?uV!`vxdf~?m!5o|f%Dx8#^An;S6&~XJ8Pi3}KS;|nUbZtvV>wf6`)YJD>b}au zybAG;ZH}bJu$hP4yXvH1rFNR{;GXgY6<4NNN=KA+H@`^S00vpX)PEO&cH?KswR67Y z0t$m~=dP>hbl;+p?QSj`e(Br%y_kAYRi^@t?>Y+My43&381MB>FvWi^NbfpZm|Bxi z!CX%Q8K)68FOIZjp)=O++T+~(=X2d*<=w`~D8+`e!7R69wG^k{ujS`xvpfhjDA=08 zDt4o1Ia0kD#D-q+rIq8r8KesEd_gFA97cvot1dEE>KWhP7Yl3yzDw9$qv^UH{YU-8VU zgBNnE1fYa8Q?Pm<94oI0JUz}~A3rR0?L7*Rn4q5|z1kv$%9LJQZT{f?Wmu$QS}h$G^zh->+I%!KYP zGrA_aP(?|)@@CAsqRG124uT1gUnaT{eEBsynG}ROm5Fq6jlpLhZ>q4VVRZCDrnI|t zZtd!_uEwP3q|Fo;AVMybMLT@>LheTtI){(N2wIdqSNE}YdLeN+;EAiwW=OO^e9*vw z(VU{u9Wsjrb+1od!n(|t>^{!wGf|D&iR`0*iP9ydDH`fI;}5reP=y|93+gP+KIGhl zF0$i34$eS6GnXqHr*dIav6}_COTb$*aIz%t0?YhyXR6=bGiJTfq4{-`t~58Q6)h*t zjgpfk`hSA8TF~*W0q6B+rdyXXCyC~T@2>@rTdJmp1fiL!WmxUgL}13O?PM^zIPXyu zcA~Uv3TtF0OakKyiriLOEuPdN?al5s@fma$?^ZC8B*8ocapAQp{28mwuk|t;=BTi< z73w4fpSW+_KFA-d3TI%|DH4NS!jt9@o$HpdVU9>MjUjbO9T0Bdqa`!IlniFP7b?f33|J|N) zg69(*uu|G>20R_H>Z8U+u=M;%SdPon_TZa!Tb;(BtC3usT#S|(Psitt@EGAJO2tWz z@!qEwO4D{U^j39?s?BMtS2oVm7?$^%mOM!Y)kFyh?k>~qKEw-)9vS9Nu^v?LwomM( z6Wr>k(>*$Vlu zqc~p{`xM06X=V=b`Wx)|7pUK)e{=N?pV37Zse|JNXhg?Ct z8X(DQ--M9~^4A@h9KEPF`LXMZLP#s8o_`e9{Y@f>kV=%E?VH5ZO*>XdvTE{3a(bRF zgFxkZRl@lB-v@f#dH&kyi7op0zvZ4Z0=|P>3uCqXYj)VwI^w5y^Q)6FH!t+`$pj-- zh@16ENHLy`4OrV) zcAzZ|nnS_C&qm7X$NRf;kIh6Q0usbv5>y0~g3M?n^o1s}I(Qr3P#e-h5#p zPrLAy6(IQ}n~bC)tc)XJv^YqWXn$bUj0Oy-{fy)Z%Ay7K%nw>3Cw> z654pL?8@`LLi9wVxyYH;!*nK=uL$=kSwLew&zEzlKm(;C-5!f~>65l|zYLv)?^&~K zVPq8mTdIKgH7h>wMEBDZ@vpH%ZkStdOM=pQZQ-8y-r-h@Qf^H58owAOv{5$l8dSqv zyg(z~7Jv{KsTrr(dNzT>^^Z_*nFdKxCr-zR8{s0Y!eCDm#9mf@El)nF&z#=0w5GE3 zcU=3Ir8Raioc)tV`uwnHJwL761Vwc=sK}P~aYZaA^qp=<5#xSy9B6}-u9~Nonpl+S-OFjnGFD1__zm9 zN1}WQn!V3;8a8PbWvR`b%EDU4hb|CzE6t}wThf180?u%$-KCdxg7Y%40LUZu;sI`B z(Je!HhnWR6oD&cKFt9miaXTX_6ksi!i8p1kvau#32rHls>G7;EP6u{Z-4_|xCZxv@ zO`2s@qjldjdb2=x$9KcBcH~B<)LQ@a_#@Sr&!~9caFOwupA+T-ha+Kuccw$N0=+vD zP*D!+e-^B2UE8(B~Hy zzy7AhAO2jJqNClT%AE@gc-k3t^zOzd5@;%6XZv~bv=KHUfjfI^RAvhY?-t?d#`%EJr`w}Fqqa<4bo>DPYig|TA=jC8? zUSZ{9l0&msbJdD|9~c3S*L#%hP_`!Fa8)%0K;8-+HeUA|2!c_T97boN_!p%%`g8nC zk2#cagqm%qlx%h1o$sa@BlQ5Rb^LpU#ocRvJ95lS7~jSH63>l1gN(4(J2$O#fF5n+ zreB)D8IF$b)TzJzt6Q=s!1P-9==5=+QekS%3dfMg4& z^1+FL2us0LpRxo%aU?h z+>(@w74`^2687S@cy}GMBm0l53~3g!uWZAXf6vM~CLmsIf;tp~y;ic1GBfj8&f>H( z+@Q2fa|mURZb0sN{3RV2A4y?J7di^P$+9VYKLcuYWKCN3lPrfTweh{Rj76~qZ6dtr za>l%5;BQSRT$GHPyV3bUyQ{!;v+4u%Hmuo)6P+P$6jiTu@nxdS_mCW;r)7L%*L{d z*#Lwef4xDPI;jHKE??<8;{p^mjv8((9O?_HvoL*odX0|=Ij(%cbtk+7ljHu$&;uH} zm_e)m1Ky-d+GjheiR${LHtSr~ZR11X#&C^~C4|4ef`^(jRl@CA7m1&xk1J@MeqA9a z`pxwFo5)nh61FT{hRPS4UNJ@qjX?+DU<4S_sVp)#QM6D+a8YbEuN*#8RQvC@;OZVb zta1lBRWmq)GDL*JTcm=rb)?ph`K6WLsCxVREU5oBCTy4+-g_FBVhwc=W4=q@pN({o zwh;G=+nRj1_P!VN*nVK1?2_Nx3hUW&qDdgb;nNq@XtA_`?0^?r9G=*O{oAJ7h^IEX z1SXQ^Y|(hFYkCa9;x3M2h1;avNk|+&*@_0&D7#^z*Eg$@ALHG$ZirASZ*OE)1du%W zeFz_9e{bcVVOB{Up^qNg!A{Eow2ltfw=x}%c$6IeA(gK5?$_HjuJ};oC z0zwO#(cRJ((b-D1;6qwAii4@bt04$m6K64^LiXV^c@lf|(E!;Y8>*0xV_s-rG1kr- z=e=IP7>JPo*be`eG>qCJgji#j_PtB_?GVp7qKWFh=WL2XPx*%b)aZpd37y+I1FcOl zF}G%E>aTW{kNz*GZ_4=Fui^na%=hypYRaZWwb1&&+*V-WBeYQesqDyRUX`3O_BIz! z!tHZ?#zo%H?A~Pz7$?L7yjOLvV%0liN-PZt)Kc9f;*2fSD^{%#<74!~++Bp0gY7^u zimk%1(973jfXC$?CrE?d69}q@d=X`mmEzT(mSyAC4xRPLs;pP+GZ@}Ry3DK0_H)*x zNlyYa%F*#gkR1LN1MG%Sw(|pXk#%B zSZ5fRI1RPNO2cqJ7SONn8R&|pkUP1tH`j7XOksNo==nW+zb`cJM_JC151Q`tayh&w zI#*B5?}c?%_qpHKE(=1IVU~mHq5_SXY(3lC?JxO!l!3($`%Azo=jFcxHvt2@1B80M zd__cly+UInpGx7%KJT*jW)}6Ejr6Lh#){o|n`88bWKVn)m=3;efHmEH9pxdW_Me== zlb8OKuabjV-kb8(MF${8g0^uqeSf1%D!Y|~ae~mNT)}Ax_a&0{62SZ@e(4P^blJxl zRDm4XxiHmy3?h6LEoLyn9#_qT_%u&$P4*zFkdy@r{YQ)JMMzxL)0Q$?)LvjBqznuVeSslb0aWrI6whhQta+x|cJOAVa#KZhAv(h*1 z#WKFVm_;*yTV$Z5#qS4YqnJEbYk?N~Gt?*p50X0l${6TWN#O#z+ zaR#APMjJBGqCGCOy|3*tv4l)ib;ZuOlKp6R@r+C4x5k^kqP0thbrOv3DpriMR9`D) zHJqbYawADyHHbvgMPvolI9(FQdMC8#goXAUHS{2dD$zbl>E^?`x2vKkS}B|I%BSnx z#&aU0b)p5Lkxazqsr_oY#;(nV_j%aSDrDf{V@Zhq4Ab(ui4G-P$bz(TFD*}#XAl&*2K~0J_yM=+2b9a80B1L}#*_2Du1R{ii;P>FI^3Kxb_IT7X5^mQ zI^c6|kyVW@hA^jH9|3bxv8Ho}{%GCs2^CN()eaZ(Ieq2W!*0#7CU^;fs`DP4Uz~jZ za4X@6aCD7(;}qxC^0Yp_fw0QlX0uZ5MV#3$9iq1Id!%2x&pE*Ra!(;{=J3N2m zqpv3FwViu{Z+)kK>CV3PCVmWx{_#uXQ%wA^l_NuL`tSI;Cfn2u&W8g;yysm@2Vniy zG7MzQ4^>cd(AJdD^Utd7ky}ZA>GyG zbXjXF#SeAwVfes0hOHU7jWZQzfw)|>_DAK1f%QFZw|4$DE*g0ZXkH__r6D%wg0q^1 zXp;*VFnYcH;2j7+*<-{z2L85v`+BBQ1#FjD`U+u+STf>i0CveX{Jtw@QAXep*;2UQA@10B*816Q{B>#@A}t*b36rC-`#GkRuPvKl z&KAv@YAdfVsBew^e@j381iGZ5ZE3-oUW(h_xvL(eqy;~8@ysu-`oR%@XO=I1mmB24 vqv`+Mqy0nDss5#Z?tSV&xvfP!DE5lfRfXMIufTsy2>fDn;dJ4tYxn*K=`_{| diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 09e80ce..b7ea187 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -3,7 +3,7 @@ public class RecyclableDefaults { public const int BlockSize = 10_240; - public const int Capacity = 8; + public const int Capacity = 1; public const int MinPooledArrayLength = 100; } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 03ae6dc..959c07c 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -121,11 +121,26 @@ namespace Recyclable.Collections return -1; } - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this IEnumerable values) => new(values, values.TryGetNonEnumeratedCount(out int count) ? count : RecyclableDefaults.Capacity); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this T[] values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this List values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this RecyclableArrayList values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] RentArrayFromPool(this int minSize, ArrayPool arrayPool) => (minSize >= _minPooledArraySize) From c48b4e2f56a4adc6b314309897adcf3204d06353 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 15 Jan 2023 12:23:46 +0100 Subject: [PATCH 013/305] Additional, optimized constructors & AddRange variants added to RecyclableArrayList. Benchmarks extended. --- .../ListVsLinkedListBenchmarks.cs | 6 + Recyclable.Collections.Benchmarks/Program.cs | 4 +- .../RecyclableCollectionsBenchmarks.Array.cs | 35 +- .../RecyclableCollectionsBenchmarks.List.cs | 62 ++-- ...llectionsBenchmarks.RecyclableArrayList.cs | 68 +++- ...bleCollectionsBenchmarks.RecyclableList.cs | 4 +- .../RefVsInstanceMemberBenchmarks.cs | 298 +++++++++++++++++- Recyclable.Collections/RecyclableArrayList.cs | 57 +++- .../RecyclableArrayList_CreateBenchmarks.png | Bin 25262 -> 22660 bytes Recyclable.Collections/RecyclableDefaults.cs | 2 +- .../RecyclableListExtensions.cs | 23 +- .../RecyclableArrayListTests.cs | 4 +- 12 files changed, 486 insertions(+), 77 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs index 52d8471..579a4d8 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -14,6 +14,12 @@ protected object[]? _testObjects; protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); + private List? _testObjectsAsList; + protected List TestObjectsAsList => _testObjectsAsList ??= TestObjects.ToList(); + + private RecyclableArrayList? _testObjectsAsRecyclableArrayList; + protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ??= TestObjects.ToRecyclableArrayList(); + protected static void DoNothing(T obj) { _ = obj; diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 52450ac..c9eba31 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -3,8 +3,8 @@ using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); -BenchmarkRunner.Run(); -//BenchmarkRunner.Run(); +//BenchmarkRunner.Run(); +BenchmarkRunner.Run(); //var benchmark = new RecyclableCollectionsBenchmarks(); //benchmark.RecyclableArrayList_AddRange_WithCapacity(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index b87abe3..d82524b 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -7,27 +7,17 @@ namespace Recyclable.Collections.Benchmarks { private readonly object[] _testArray; - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void Array_Create_WithCapacity() { - DoNothing(new object[TestObjectCount]); + var data = new object[TestObjectCount]; + DoNothing(data); } - //[Benchmark] - public void Array_GetItem() - { - var data = _testArray; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - //[Benchmark] + [Benchmark(Baseline = true)] public void Array_SetItem() { - var data = _testArray; + var data = TestObjects; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -35,14 +25,25 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] + public void Array_GetItem() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark] public void Array_Count() { var data = _testArray; DoNothing(data.Length); } - //[Benchmark] + [Benchmark] public void Array_LongCount() { var data = _testArray; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index af73248..766a3b3 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -6,7 +6,7 @@ namespace Recyclable.Collections.Benchmarks { private readonly List _testList; - //[Benchmark(Baseline = true)] + //[Benchmark(Baseline = false)] public void List_Create() { var list = new List(); @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - //[Benchmark] + [Benchmark] public void List_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -52,14 +52,38 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void List_AddRange() + public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; var list = new List(); list.AddRange(data); } - [Benchmark(Baseline = true)] + [Benchmark] + public void List_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + var list = new List(); + list.AddRange(data); + } + + [Benchmark] + public void List_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsList; + var list = new List(); + list.AddRange(data); + } + + [Benchmark] + public void List_AddRangeWhenSourceIsIEnumerable() + { + var data = (IEnumerable)TestObjects; + var list = new List(); + list.AddRange(data); + } + + [Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -68,21 +92,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] - public void List_GetItem() - { - var data = _testList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - //[Benchmark] + [Benchmark] public void List_SetItem() { - var data = _testList; + var data = TestObjectsAsList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -90,7 +103,18 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] + public void List_GetItem() + { + var data = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark] public void List_Count() { var data = _testList; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 5451f76..08d3f16 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -41,7 +41,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -53,7 +53,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; @@ -66,13 +66,45 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void RecyclableArrayList_AddRange() + public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; using var list = new RecyclableArrayList(); list.AddRange(data); } + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() + { + var data = (IEnumerable)TestObjects; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() + { + var data = TestObjectsAsRecyclableArrayList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + [Benchmark] public void RecyclableArrayList_AddRange_WithCapacity() { @@ -82,10 +114,23 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] + public void RecyclableArrayList_SetItem() + { + var data = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + + data.Count = dataCount; + } + + [Benchmark] public void RecyclableArrayList_GetItem() { - var data = _testRecyclableArrayList; + var data = TestObjectsAsRecyclableArrayList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -93,18 +138,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] - public void RecyclableArrayList_SetItem() - { - var data = _testRecyclableArrayList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - data[i] = TestObjects[i]; - } - } - - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Count() { var data = _testRecyclableArrayList; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 27ff894..d6a166c 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableList_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs index ed3c1b3..2851a58 100644 --- a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs @@ -18,14 +18,27 @@ namespace Recyclable.Collections.Benchmarks private class TestCase { + private int[] _items = new int[123]; private int _count = 123; + private int Add(int count) + { + return count; + } + public int Count { get => _count; set => _count = value; } - public ref int GetRefCount() + public ref int GetCountAsRefCount2() + { + ref int count = ref _count; + count = 234; + return ref count; + } + + public ref int GetCountAsRefCount3() { ref int count = ref _count; count = 234; @@ -33,30 +46,299 @@ namespace Recyclable.Collections.Benchmarks return ref count; } - private int Add(int count) + public ref int GetCountAsRefCount4() { - return count; + ref int count = ref _count; + count = 234; + _ = Add(count); + _ = Add(count); + return ref count; } - public int GetCount() + public ref int GetCountAsRefCount5() + { + ref int count = ref _count; + count = 234; + _ = Add(count); + _ = Add(count); + _ = Add(count); + return ref count; + } + + public int GetCountAsInstanceMember2() + { + _count = 234; + return _count; + } + + public int GetCountAsInstanceMember3() { _count = 234; _ = Add(_count); return _count; } + + public int GetCountAsInstanceMember4() + { + _count = 234; + _ = Add(_count); + _ = Add(_count); + return _count; + } + + public int GetCountAsInstanceMember5() + { + _count = 234; + _ = Add(_count); + _ = Add(_count); + _ = Add(_count); + return _count; + } + + public int GetLengthAsArray2() + { + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray3() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray4() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray5() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray6() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArrayLocalVar5() + { + var items = _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsArrayLocalVar6() + { + var items = _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray2() + { + ref int[] items = ref _items; + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray3() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray4() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray5() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray6() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } } [Benchmark] - public void Ref() + public void RefToInt2() { - var count = _testCase.GetRefCount(); + var count = _testCase.GetCountAsRefCount2(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt3() + { + var count = _testCase.GetCountAsRefCount3(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt4() + { + var count = _testCase.GetCountAsRefCount4(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt5() + { + var count = _testCase.GetCountAsRefCount5(); DoNothing(count); } [Benchmark(Baseline = true)] - public void InstanceMember() + public void IntInstanceMember2() { - var count = _testCase.GetCount(); + var count = _testCase.GetCountAsInstanceMember2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember3() + { + var count = _testCase.GetCountAsInstanceMember3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember4() + { + var count = _testCase.GetCountAsInstanceMember4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember5() + { + var count = _testCase.GetCountAsInstanceMember5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray2() + { + var count = _testCase.GetLengthAsRefArray2(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray3() + { + var count = _testCase.GetLengthAsRefArray3(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray4() + { + var count = _testCase.GetLengthAsRefArray4(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray5() + { + var count = _testCase.GetLengthAsRefArray5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray6() + { + var count = _testCase.GetLengthAsRefArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember2() + { + var count = _testCase.GetLengthAsArray2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember3() + { + var count = _testCase.GetLengthAsArray3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember4() + { + var count = _testCase.GetLengthAsArray4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember5() + { + var count = _testCase.GetLengthAsArray5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember6() + { + var count = _testCase.GetLengthAsArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar5() + { + var count = _testCase.GetLengthAsArrayLocalVar5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar6() + { + var count = _testCase.GetLengthAsArrayLocalVar6(); DoNothing(count); } } diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 27fa4ea..2542b79 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -82,7 +82,6 @@ namespace Recyclable.Collections public RecyclableArrayList(int initialCapacity) #pragma warning restore CS8618 { - // _memory will be initialized when the 1st item is added if (initialCapacity > 0) { _memory = SetNewLength(_memory, initialCapacity); @@ -90,6 +89,34 @@ namespace Recyclable.Collections } } +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(RecyclableArrayList source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(in T[] source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(List source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(IList source) +#pragma warning restore CS8618 + { + AddRange(source); + } + public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { _memory = SetNewLength(_memory, initialCapacity); @@ -126,7 +153,7 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } = false; - public void Add(in T item) + public void Add(T item) { int requestedCapacity = _count + 1; if (_capacity < requestedCapacity) @@ -152,7 +179,7 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(in List items) + public void AddRange(List items) { var sourceItemsCount = items.Count; var targetCapacity = _count + sourceItemsCount; @@ -165,7 +192,20 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(in RecyclableArrayList items) + public void AddRange(IList items) + { + var sourceItemsCount = items.Count; + var targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + items.CopyTo(_memory, _count); + _count = targetCapacity; + } + + public void AddRange(RecyclableArrayList items) { var sourceItemsCount = items._count; var targetCapacity = _count + sourceItemsCount; @@ -180,10 +220,17 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(IEnumerable source) + public void AddRange(IEnumerable source, int growByCount = 100) { + ref int capacity = ref _capacity; + ref int count = ref _count; foreach (var item in source) { + if (capacity == count) + { + _ = EnsureCapacity(capacity * growByCount); + } + Add(item); } } diff --git a/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png b/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png index ed3622aa14e7832123c302fce83832e470b6c9cb..b70bdfdfcdd40f538aeda58b7a58a072abcd64d1 100644 GIT binary patch literal 22660 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 literal 25262 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 diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 09e80ce..b7ea187 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -3,7 +3,7 @@ public class RecyclableDefaults { public const int BlockSize = 10_240; - public const int Capacity = 8; + public const int Capacity = 1; public const int MinPooledArrayLength = 100; } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 03ae6dc..959c07c 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -121,11 +121,26 @@ namespace Recyclable.Collections return -1; } - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this IEnumerable values) => new(values, values.TryGetNonEnumeratedCount(out int count) ? count : RecyclableDefaults.Capacity); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this T[] values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this List values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this RecyclableArrayList values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] RentArrayFromPool(this int minSize, ArrayPool arrayPool) => (minSize >= _minPooledArraySize) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 6fcc043..f482d9c 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -161,7 +161,7 @@ namespace Recyclable.CollectionsTests } _ = list.Should().BeEmpty(); - _ = list.Capacity.Should().Be(32); + _ = list.Capacity.Should().Be(20); } [Fact] @@ -184,7 +184,7 @@ namespace Recyclable.CollectionsTests } _ = list.Should().BeEmpty(); - _ = list.Capacity.Should().Be(32); + _ = list.Capacity.Should().Be(20); } [Fact] From bf2d966f2867fab676604af4209b4a91717999bb Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 15 Jan 2023 17:49:18 +0100 Subject: [PATCH 014/305] RecyclableArrayList[int index] set performance improvement. Add marked for aggresive inlining. --- Recyclable.Collections.Benchmarks/Program.cs | 4 ++-- .../RecyclableCollectionsBenchmarks.Array.cs | 2 +- ...CollectionsBenchmarks.RecyclableArrayList.cs | 5 +++-- Recyclable.Collections/RecyclableArrayList.cs | 3 ++- .../RecyclableArrayList_AllInOneBenchmarks.png | Bin 0 -> 85664 bytes 5 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 Recyclable.Collections/RecyclableArrayList_AllInOneBenchmarks.png diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index c9eba31..52450ac 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -3,8 +3,8 @@ using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); -//BenchmarkRunner.Run(); -BenchmarkRunner.Run(); +BenchmarkRunner.Run(); +//BenchmarkRunner.Run(); //var benchmark = new RecyclableCollectionsBenchmarks(); //benchmark.RecyclableArrayList_AddRange_WithCapacity(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index d82524b..c138fb9 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -7,7 +7,7 @@ namespace Recyclable.Collections.Benchmarks { private readonly object[] _testArray; - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void Array_Create_WithCapacity() { var data = new object[TestObjectCount]; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 08d3f16..a85ee9b 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -12,12 +12,15 @@ namespace Recyclable.Collections.Benchmarks _testRecyclableList = new(TestObjects, TestObjectCount); _testArray = TestObjects.ToArray(); _testList = new(TestObjects); + _ = TestObjectsAsList; + _ = TestObjectsAsRecyclableArrayList; } ~RecyclableCollectionsBenchmarks() { _testRecyclableArrayList?.Dispose(); _testRecyclableList?.Dispose(); + TestObjectsAsRecyclableArrayList?.Dispose(); } //[Benchmark] @@ -123,8 +126,6 @@ namespace Recyclable.Collections.Benchmarks { data[i] = TestObjects[i]; } - - data.Count = dataCount; } [Benchmark] diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 2542b79..d5af696 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -127,7 +127,7 @@ namespace Recyclable.Collections public T this[int index] { get => _memory[index]; - set => _memory[index] = value; + set => new Span(_memory)[index] = value; } protected int _capacity; @@ -153,6 +153,7 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } = false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { int requestedCapacity = _count + 1; diff --git a/Recyclable.Collections/RecyclableArrayList_AllInOneBenchmarks.png b/Recyclable.Collections/RecyclableArrayList_AllInOneBenchmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..2056af015d33619df2e01a84260da2e649d2f32e GIT binary patch literal 85664 zcmdSBcT|&U*FLIZ8yhkX0#c$P;)sF}i6A8^LhQ)Uqy?o5L4-(4fT*Y-B@q~;7eR`& zNDCqaR3IThLK70Y5PEMRK-zi0dCU6V?>lF{v({Pbcm4?S#5*C+zW2TNwXc2cyuNz} zwtvr&JzKVH*?;}o)jzgu*?DlwmaXr1?*yJ{adu+?|7=D60lTuLfF?BoTx@r|eEag2 zErqzf+y^^=>t7yUGed6Ka`5)XW2)*oB82r{l;y=>$IpXOB*u=FAe!e{s16Z<$_ zbY#XgH_Su({?;95(p#`ky&Af{V1yR!8xe-^ilPRkGKAF^j7I4~6UkyvhRSi_;U8-A zsxohy>Vuoq&P7Qjwy_0k)`BSvuU*_WOqul0A18-|cmL~Z4D}9(jrAu|q$ertTUgs-tnFJSV^|GB zAM&Z*rQtgnaA{S*s}{_~6ec$TpThS-B|fg${dS+&7{5x5J$h^Qu6~uHhg5>6RIwEK zB)PEsFqxypphQ8B0;|izz$;?Qh3!%HKGtAIX8dO*8jm%d$H9Fm7wjuStuN_AdBB4^ zU$FvPaAqy<7Fbirxl0<~rYIM&W?0)Oq%MNrjN#GgYV#lrr>A5(`j|sgxqR*=@`5%F zHe;pXySQsRa68pH!uv35Yh(B+0cSu!EV*Fk-^{Q+UzU7^8Rkx9ouXHGu3ob=9J?fC z;WIzS@)wOwM?#xH)dGHsU=4@ys|&YH7>M(6J{vstoHaO5&6)MSh`aAlFzSAa4P(E! zrynuL=d#G6`ECupj>3XN{TosZ_}rym0kS^-7|^TS3V<%)L(FMlZ)GHt<@tj&-(x&Ehn8 z-M&+(6>>?^J!$(6vgx9dMa7UNF7L8qGyB&3j+E7FWc42cP?i7GO3F*LDfET=ccNya zLhZCc;RCI9;S3FJZ4C%6c<<+f(5rVZ)aJ}sxO*YQK~VJ47e~ycPo|YUY7^_xYzKw} z9dr=zTJ8Z?Kd$I6m}eQ)CtLf!ysFoMx=NK^E;4BEJYJEw^n)A`)U=wyw1;f1Sl%^p z6C%VT;)$RN&)u3%Z8FvlC(^A-yE_NZxXU=CoWV9|y#e(JzHcqMfekQ`lqE{E%uu_Q zkHObP6ji#Fiaf2=ji5g={;;(xH?2ySp)0VVkR;C7yWY9!UpkQ6q30g&UpCax^iaC& zMo1o)7ucfnc!+{@bpB#>$i%+!{8jO>9nH%)LVvev{MZ>rY&f;!$KHi;%KhKU`u#71 zy8pa@%~ifPBiJn?l2BiOYsOO3naAfN0<_FXafJqblZ=3)UvV9vl3d1P8Tz>utv;O! z7aLWX&y2t%??;!$4l$Ejb19|+30wO*G}nZlD+34lUHF?_YOYVaB^k>@HCA)alzU)K zj?7^xt?6r-yF_WM`ETlyHYHiN@1ekUOUig^>&5Ld=Y3ZV`?f-gE+*Ci`)@22jnO(^ ztX(Vi>_&3FJYbYWj_N1aYYFxPrAI^`lc}mjF;0}udRXTUyY+ju5mT+958hueMM^jD zCX>%`q`qEE^?WxXg1(;EzI&a5DW%|7f&%+3ADo9bWBeLA+ayf<{p18irMXG+PWwY# zWdT1wS$n)k3^sKpfHk|fE}Z+og07(K^K!%2BdkCv(g`rK?qhr*pKT}MNZJV+mUjvC z`_))VMS|}zb0W@S-bT1o!P|8 zMGR|yrl%X|^-L?4W*>Ol!r%AfYvB+Bx3_8qz91W`$Ce*-hBfa_3@U41YIwC?>g5m& zY0|D${uT5lI$`>&R#YyrEZGQEeAU9?fXCOphm_R7N_RRwgPLCkR8CPonVfBbG0972 zD5NU=9k*cim*Qp-V$73uZD*y|>$2ln%Ewm^bL@~WTYd5cN0cmXI^LVWFC1@P5tf*` zYFY6PNeOYPWk_EF45)Q2} z#g2zpG%D0-=s;xgRTpZo*v$yLCfyxtld!UrVm?&W3TJoQ2O(V<#PB!!+d&6suPf0T zGVIPqPAGv7^@wvZ_AV6v(bkr=1wX8*m2@}sK=&N#K`xpY#*(=+_LM~aFQv3)7hj4 zKpLHJ}um9pKC#4 zdZX%C_lXShr8tNu&cC92z$W6Y!%f2|QZNBfm~Fdk^TM!{gbMn{)l)>J$$s zN2;~zJruQRDLVG6r7=0Z{K_0Ww1h8ovCI5o4UjR0K(=-+rit<%6Z!n{l3x>Yzbg5K zunohk71~A8tX=O=#*A6Zr({GE;3=ZXz+oco&|3E9X(&MQgqmat2JIzNM=(rtQC^cB zqiJ^UG<(R)?)H14Bhm{mweWhOz{%xs0%MSC420mnlJT1KjNWc{xBKl(=&|?v84Mfi zs?WvlHx!9J{n%%>Cmx4MDoCD?OouSCFX%|oD}`OdFE!bl1fedahno)#o^41E?-vJO zm2?0NF}`UPa4c(89Zm!xv?L~|Pm__*SD^N%Vsdo}mL(Xi*cq{|&v>-S$DOsj;}&8s z5EFGSJC+kSLNJ*S8+Dr9fqbzJ8($hHo|wd?T5IfFssYWuYqflbg1CIe*Z~IGi&D^T zv!*vM$etME>6{uOmKXvzYq|j>}0Pj+=_=}nN>QzCZtegZPNJ>qSmGy=yO=D z?&4d=+E@NBKryzG^uq8`9aY-8sEcNlyO9PtyPdS(30icL07>8QreIs?!RR5Y*3M~- zY3kEi=Vb2Iui~~%WTrngG2CSmKsW5V@(Ye0L8wKoC?+G189Sl|QO8nC)%xeY-bwxp zI=FhIXlX~y1}f71d#K2ey%Fw#7yR?bZ20Ce{^xa=@~HO{=+Sw> zt4q?q`@7H1g(BT(!v|QyXaH%Mk(C?W5didZajzT*OwwEf89)G}u<$Ra1-yb9*ZVHn zuNeSzQ)&+C*xOiJYNWYzc^1ijerqNFk6-5$L(!%?cqv4@ zbZhDK5wpBFs2^vgS+LqOP=zhDN?j^wzL9cNBQ1gQ=gl#`W|v5xf_=k?6%uomUNq2X z1ahReUkk{%g{+#=ilU^=VX2k3ls~CBz@Fg$q|hrm4dr6+-r>|}mAXg2xd4DP0DDV$ zfYQE?ue1Sp{51RNOqIp2q<6Ob02pwepTL&MuncALWTuBHoABuW#p=`P zg5`EqR{8(rrk-Io`?2gLU!#BX>%1Y%o0}4Ddfq8bg2Ycye*4fI4T*=kFcNpXJK`YIT#) z>IxHQ?J^-)!wV)GtY1O{1U#r<2_#sJS0G2PIiPpUoXzJ>YK=B23+6Bawwqrdyjn)K zZ^H>Yf-Je@0sac!YjKfo9k4cuo8BloE}$fotH%WsK_I?dPtr|Yh`!ALb_XNo5kv+u ztW&avh;5GZw5&tRi?skz+*nROm}|^%Jj-r1)NU%XCQIvGnOUTZ8Q%Jzy}~ zRaV8}48-tqJ0-HY*`y|CW?UXA^Le7JQri^$kBONEhx*Bq28`#Nshuh~XE4V>J0sa^ zuKl!FMUjM;pJsv;=rqwPA;L76S~0j8{k+4dv&kS%F`-V&>+ln+&}t7>l~&`OpDrfz z6nd2V)+B%>gB*>81-y`$NM6%9zAE&Md0*SH(_M5f$?}qZGw2|;Mzf%vNAsT(-i@qoQbs=4&lma+PJSo2rbkE@LQBmTtg5 zL(BWnAh=;-Fi{7Y#jIan30}yND!RiT8B3|AwPw6h19dck%a5}=c<0j^+~jJsT^0Dh zD-k{Qi~@{o9ZDB&(VDdQKTP$3H<)SAdsCfvr#!w}d zZZ$Gw2BCM*y5QOEt4hnFjH}i1%pKAi%Y)fsUNU;;>DO}gEqZV2p}Kq+U!ZpzEcEc!VKoxS55deblINNR~^dDnaps#0?DM&_i{7wkIjxY?< zWAo1tl~YY?jH!O+TKVH9<&0>`N^l3?v3RN`ow-fpqW8~~I4hperv)WBv$`xk9p>cK zju^9#Zlt@|sKJpxlrp)c7JglJuJ+*_p=y#9>n18+aifgkj5rs_KG!2CHKz;Fdq(xF z5hPDg-)dSf`spQnw}OfSV^^sn-MxD! zZ5(cOT(%Gk*|1dh=yeSbTJFN1==GM&sW{6vefBSe1p^hDXUdF<22LbeP*EP+Dhnw= z&bj3K)B+YFElt9R_rT!$Y=bo%Qv2JDO2;{LYvWwlzBknM3+9gIDp1tL!u*`@!^?0j z_ETsRjNHXFK~)rmS3XOFUp*QgkMMtnJJ@FsxM8}CXfSKBGe4^!a1B$tPZw3RY z&^AIv=Q%3a!p4#Y;a|tzMx%rIDdJ#qrZ8Uvg8m4ZnO;*SjnCB1U>A}f$SE$T3vy>J zZ&qaTHGA`AoPD~6%~j6Mqq?wRcBo*R^rxrBMj%x`NXoE}YpIPo&k;_Fg&_KJIK2ZN zK|#-S_!wl$@}W~-Be>(c_F`*q3o`11Z<%U090*`fr&TYWgJ~D;tyLfmj$5A2@jYHs z<^17@zDli!@?G}5%uq&T@C zvyok`>zOXColX0W1TJ|crmx`$xK!WpAtGvE?{poP)?fmJz_@E8u-qDT*UvQPbXZA8 zw{LW>(875BSJ%zZWAaWs$IT+kjGqA$tn}kn6msGpy4+il!>Jll^lH`1ypqlI6+Xv% zD*>^wUroz|lR_;Jj)u!kbu}r7$ritNs0c{g5%=}yEe=G z#C?+Ir<|@zemcBrk`>vCy$!$1YTN4y3J(m!?KaRB5^QNM zk;FtO+E(|AGcsSr?iD5*Kx(En^^EwYMU2j(G;)o+zwseQoaxA$Rb+o#e~&A*>8Uj7 zN9BcT*$uIIb8(mM5o*C~f#1?gN-4?O2Af!ga5-CuBq zz`BGFe~@`IVG(1;+qoX@4DEbu3{JTR7|U4E%f3jH7g&B*U;Cc?MKO3~VMdJvL&wbjY9&q3K zvnZoQ)6TF+&5aaZWR}L0HR3l9r;)Ge!`4MC>Yg}M!e?QqV)d)9W(dUmI0^9--^T@O z*SyXPT31B$+986I2INOywoa}o)_cI-3gsM>K zqQzQ?q1Hfsobmiwxg%vc53-#{4^95wPPH)~u`z|zh=7`H$_{`e#C%>dDkXnc>yTRUxa3<`kdnqw!vly#d!; zI8Wj5wKoTQz>N3fxypt4yY~M$7FezJrt*T!#YmTd7Ee1pB}w6O#CR}b)F;6#I4B#7E%^nvzC{brOFxkaw`UkJ-gmJYm!2WI&ooY z1X8%lGitQ?uRu|MTA(0tO3XOGLaS7VqwL{T*~z{koOH@%>~y_O1q}cm$7u`GTv!k6 z!_eWe--kXm=t}B$1wyK$Z_PyX`{d7cTolTuI;BVIqDmt~Tptf#Y}k$ri0;Kt%K`p( zt0|14JZZ8vc5#w5T;Cnr)|sYVw99orEGOP~3>9J<;v^gTFgnz#Yag-Pl3Ks30KJsA ztOs%YH*=C1P19>ywCg`-d&9BAWc3)E(9u= zg1UtOVXwvSyr*A;?`l||18M}#U7ZN+J@a&*T#Ve~T?M`L!5O*Z)I`&DdS@Gh|S8=O6sb-!_1qb-$po8H%Bb zn($Tm2>hG;YYNpUoG&R>D?6x~zt~9kTMfe7=EV)91gs8P^XgyPEvIAj@y{=EXXh|; z>&?(VucKdht(VT6TM~9r!B7uDM})ZxoVf}We*1Nwr_!Qvd3f@5d8!z!;J`uG3KDfnkAIJz4!x9De0 z9=q#HCk&y!uuIpb@yx^m${vr;CT5qCvqBVI3Eu39w#i&)*JFEyT(m|I}NonVo*g{4Xq zI$LNiM)DE&iW!6EW34BbM_;&oFOvy&V4+&{Rmqw|yf0F(FQ`z`XZIB^`9NH6$>s~g zhNNG6Ik>Kj#&xvs3tPzMvPT6udBZjr`tyWqKg$Q+>ZfH{x#)R;Kl;Y>mMQxUYj4v^ z%6rsZ{*@M+pM9z2}bRF%D*>?VOmA4A!Zh4wygBsN;Z=gl&iC`K-^-l6^^^hwKI>;^t=1eE``qjmmqZ3n)SYiz ze|(}p;$9CJQtaDSW~vWPUCQQ`En)0UJPHrFb;_+Dvc;yB?lqHhdw|GSRY09xCf=OI z_rg-MqB_DSbBS>2VwSQbKBFTcy7U}P;rk)pmZT2ABtyP=lFHpTPdkjVujy4e{tL6d zBg~iEkI1A6kyvM7+=aqf8`*ldZPGTpipFY=Psn(f{ju&Z3Q9yvCS`}mTCe=zpsOk zvRx5DFW{KbRM0jx6%fzZaXP)w7A=gJ58PKuzQ?^3csu+Z=;$R@C+)TSxEO{J*E%J_ zaR)&1P`$8zQ-rWVE;RVdv}W>crOdRknn#to7Cj%MI0jnb!M2Zw&5P34=BNTuGYW9< zL@n~AhefmsB8(TE00#nGuwI^K4=d7d@J|@%G+jiGYH!P@Wj~_9^&O^4OgZ2p8F!jg z`C9S<|2%$*z2J;@R2Np{q%V6zhxr6=+GriZF}L( z?y0&2{FWI^7WLu#wt-0_mF_~y-W>RJ>P))(n+1_$4d|9208PA7w5^Blotf-`m)R}C z(W=RIZ6n#fdXl0=9Z^>MmyV_^RHwEGE1WHv3R3Mj2m?IoA;&hoW3FSr>IIJN))jGi zg8h=6luZjkNLZ(J;93U#SMn@1{L`=2T$i~(36%kO#Fh)BVW}Mxbr*^) z`Z!uBZCV(;Jt4yWDu=j$&ygo}0md=yxb2eFodO<*PBCSDt~PnO68?)-qV;?J0imj~ z2^vdh)DxWObG>mTH27mhtg>Ltygl?eF06)%!}X>X__&hx@h81m}{zwfH5wZe$V2RcO(<^q=N(}1f6VLkCSz5T*o z-ERkq z&sJ-X8OhJsrg3}bv6%hR5h7rkV=zfQmH%GcpGBm<>o18klvj=4KrZ0U_ix`(vAO^4#u9{jas{ib1?-{#J+^Whb5!-gC5D>}oLRfeSO zH5#@<29(*YyIK1XA(no#HSdsgs`TQ!ODTilxk7EZcA|wKpa!~C zf{Z)Xo%}LZSJ3mxL0Nj@HQ~{FA;5GxZi=Sh#iXfzO$A~ERRVqtGWY6ViVDHo)_|{` zN)FNe^`ra#(k`qW^_d8T7R?{8kMA#9kL1k$SdyP}S$&1miu2u<0yjKKF0WSVzu6Sm z`{3*XD(=5;L~L~)+xrTDZ)0ELsj#j5i(vwrcl0ru-+EQ1fNHGoAQPApy~#xX1e4}N zES3LkAp93Bsk9BilF4_4hO6bl0Q2fF54YLuTcG}b10ltRdT`Ky{I6V{S6XYiUof*I zXi2^0U`4gSf6aQqdZfO8*xz-(2L;-6yH|}iN&vQZcyvRK;(2FAutCpy-btDO{Q{fA zS92Hf0nMX^E;$W`x*4XFdrSAA-98?7EE*NKLinbdazS(iT&{Uh*c~Tgz~6^jISoHOXT^Y;hFbp@p$gv}aBUEF2)ps& zx%|bYW&yXR%*~q4-Ca`bp8wd+ZLx~5Q}{TBKaqt;VlB)3ftoTpEIun)%?$&fnNr=P z1(C}|j*A~ujYmGD0e6=txBmDDlLBxc4*7%Yty4|=XgoKG3FE~xf-e`p@IXDu)T~jy zL71~_IZ9SzD3#g)h(+LUQ_VLm-p8f|V8T!TA zqL@FK4H`4UmG9)Eu%NqxxB-knE_Zl*3a|E%X(_meYYTM6!W5(+-xqJwOofTA%^^pt%s$V7AcxI$A~*cAv}vmFGW z6S+t@EC4m+fxziBxCP-!W3G3}xe{Yl-h8!{7MR zEHoIOa+1fh7Vy}KNuP*Ka##mAu_%Iy*OS?Q_u4(~M`coqewO%^ySZxL)+JjTvmvic zV0Iqeg^i8*@YrJwst0ELt_H7bt!|a4oo(Z{z(55OSxM>8qm?Hc-YrTZ87twy?%eWh zxq>bc{w}K0^&vH7v?*fhl;fSSue)AH3skYeE6|s@NRX=$^J4%tK<(F7nGF){8b7zZ zN&(lvd&Hlhuf+AGr=7)G!pf1D{kWVDw@hZ_qR(La0t4c~8;f&LHi@~mpEcli0GVbf zUF>9A`#)6(JAS~{R#WPPl+xB+Zg$#rj-JBVc?2K^Gv0>pSl#-{1tP;5G*_ap4`Q|> zYu>ISzn{z$KAdQ48t@PqrO%}wuyj#ObeWr23rvcO;mNcTByc0(Zj9MF^WD~YY z5IpnOQv+I>m(6ofAxoopy&m&~K|_~GvvT~6ukm=*^6}i^b*D=jlKY|GO8uAH8iTf%hTjjgeS|O>_xLWX#r;xq?+BRxBfY%M zdMtkLST`Er-L$?`O_Qp?c`MZ##s}o)>Pp%qyMFtwo2NfbER#7#5-veN6yMg8zdCNasDD zf;XMsfDK-Yn>fq13jUsn16OV@7AwiClB(X*)CaILhyI_X06-?6*J-1P?c3pJc6V|9jogpE8Bm<{;r+TzNMxK3IE=`Rrd3g|3>-p7p==ZkIGR)DVKBYj}fj zp!L1yZ??V#!9@UO!IA$a<+Lf4`(Lg8&5yG{#;ZnP9)FU@h!()h)|&fP$uSaQn{82n zsK6Mz>f`CxFAZRTkN?B~g>PTj^#*coB~%Cq{MSz1|ww~Np_A9Gu@Q2SPi7aXzE8M6JWpZPW%ntXB!;idu)Ue zRlUa)-&4y}f(p3J8oJ8AvaHjI5U zDmy*kjHCbXk=3dC4cbi%!{u}ScvZl^h=tHt!t`+^=6T3Rk#W2d>^|6{ELZ7!$wE=S zdfkE(o!b%Q>kh%G0011Hgs#ue zr&kq8#Kq~n;BV%5&i>8DV#ivgkm*>!`Q(6kW};;Q_i0!XYy?VMF!V8+O^LuD)Y#2q z2CBwgeq}^Ed&O1c-^=PJ<|DH~XI2&JSKI23P|PVe<>-|kbKK+(`GtKs2@?6#`K|fY z7tadiuv&`Mcs|&>6TR^7Ud9Sp@cE z?$zSe@JL518~|O6Rh~SQ9k{p{c*_Oja6n|;kyTqh+iYbrwsatD@z0Ic@JGpJ*wmf9 zpK%SCM0(b_23@@j`P059YrHhADNq!RwV!3JT44iN)G$I2^0Ii8hL~)8Ij);zgKb}eMW53r=Thk9N@QrRiRoH zT~mdZY-PNrPm+Cm%A)ZeSJ{tFt(a<$KgN&SO27jC98}Us=f$aCwZVkTOCWz#V+VD_ zKsz0w-*q=`_|zTKkpR6+tK=qpP+~$ebENn627Bg?y8lvVlbHAp% zdS@Mf8o+8*J@6tGvGs@C{~pQA{+qf9e{k2(!tdxe8`Su%ZA!^`K_Wyc=XZ}hPfN@& zhINbCTQ8%+E^9!CV7xucs3`*f<~E3UL7_B6e2WTAB7wfyxc)wgkr_Uj(-d!G5}?$E9uQZp2Jf=GB@;wQq+S$?l?vy zvzN@-W`Y#wl(Bvrqkb}gG*0^Uzf?d%BX!>>Khp1HMyz?yjscg@;{qCQYCbr=!H#hh zI#&{tN83>lHgRpU;6ZveIr#pwgY^?rTWCN0$v;hed7dXodeb7X2?j(Af7F6hPseE= zX`!Vx?|MnV!7r`7NhL^}hrcL4${dMXaAJyEXFhC4HAlEmg107!lzUzDGK+b?gpPJ1!%}s8kFe7SQAwA zbl*y(6Z&lfhJyA|ApYO9m;Sm}JU=|><0)PVj zTaXZc3Cu6Mgr8s7vU++y9-{Owc@}d2e~zC3lOC{e)Q{@3wKW|F* zivHWe3eyc(pk&1#98Z?|cr(S=j5KK(C#yU$!2{%n{=AyDdNmK}5z#m+{?cWhPIomwsr9nL52KNFBlVUt z`4!7&GZOp0m7y8rV zyUN+`%=MFXM{V^+tUYlib`W&{WloJm_?VPNbj)AFAJ2`OtP)?V)`)?9lQNn7MUQQv z7oC*@!S)AfXeU!=i6#?+Q8w4Y9N44v&Im7hR4c!iX}~sDZI)=U$j_RTa4xIz_V10R zKJ7Ov^v(FxR1Zt_?j-Icellk3%ITDurzi|4m_>(%FhB|5Dc!xFix>L+`=uw0`E#V5 zMd-|iy4fnLt~|TqJld|!e!X@gEPtZHtLsvikNV2+Q>$rwgNn&mLoZ_*jY71oGrj>) z51&1~c9X)KQ_>gC$?{8Yb^3jQtPMXnpf*0 zk|RFnW6mg6Aj_Y7709Z9V1->2EhEnm|Iu&iOrXnUoGtkkgqRmR?M&j+Jc4={|&(?@8eHj28O)ZmJ|Cwr9H3C5y|1I~q%def3jw zbMeW_)KiV>6N?sD&ULb79|S0{wj19zZmEvhuxsc3=~W*{1=~EsG{3kQ5YRRFL+GtM zqlI8}J}@w%HJ}h{IJXw44*D=>1@g8BCrTukhBSux)jA_`&}Ty}JgtpX3e@NFoA0i{ zDLIx#=Lmi#`?|Gd)50Ze&M?iv0BGb6gzvXyc?cUT=Ad%25hg`uCSyXI2M% z2@2APMVp+UZ9m?nQk!>AgYO_hnUfD*ArXdq6U$J`UB0JYd+8x5-PoobS{OAGlhP93>c`L2|r-U4Seb|2T$kXS~udQ%ExN_aiu##8n^-dBw!jrY%eH*$MK zWR$RVbLs2V76U+g4)xr)@3ejd1Y2AbCaazNb(RQeOE7bUO_MS^a!2%O-YWRmQytB+sAT!5I(!paXSxPL&Cp%=f`I~f#oebO073*>*RlDV{sKlUejUGe1$?^FU!-u8UZ9{OzUKR?fwC#zX7F) zRlZ1!e0S4ANlfH*1f^ozKJ3tolLNNQ09;NM3$5t0ysuXtqh5gkn2^TZ%& zE7S3Jdx9%S(YINR@BmgCB{n~Ao7Skff79r%ER?H_r(&zg)Lb^fF?-a!wOw-Lp(?vA z9b=KTdc)Oy&i(ejkG{+N{)NjbT~1SP5?$a@j#Dz{wZ~@EU;qlhw@gw;pU2h#9?nv& zC_`%l&0srsb!NY#VqB$M_fUPQ9X=9G&Gp9V5kqn?rSJmMp`z{|l{tfb8z!Hu8s7-i zY7kg~9NI+dYlz}fyqQZ*ceb4<%uiE@j@sEJ0$v(#oqVVrhXb<>>&)2Q zPRZcB`U1<>}^T6*>-Os*_ie!_@4j zib;(&QIzv{l*Hm;dL_?XS3XG)uay*TW7@mi-gJF=NC=;hD!E6{Z^q2FJ>I(ogI)ISNJ_3(Ua>5|-hSV~Oe8R}Obt6U zZYoT5eg9P9&P=o|=}K@E?rKq;Is&?2lOmEIF6xltZIK2a5j;Y)d<{S(e3)ZY)87^* za5k8{%}K7NAaHSJ@m7M8Hlp_8*r14p0ZnJ5q48Ji@t&B&aKb~nUbgQfK0^%;5L7%- z6sjpM2q=3oW4d{cVo>$PgHQdtI&_}N{AgvG^I&gaqp$C*&_(M%JR?Y3qlJwa?=g8I zM=vyKgtD9ziJyWIiU1*JbF%B_p@^7Z*&t7_&5{UgLkIB6YrFE8>0sf)1BI%&^uVfo zTx4DO+%U8o{;orEBSDeG8?%Gs z@IR;-W$;;2aZY&7D+Qtx+Fonxgz3gUG+C;Q)7RzA64}wL?QxHkGL4fz6*$|LJ*wT% zx;??OY<3p2PZjk8f#?E6izop?v!m>^1R!@jm2}=#&H>yE0ypQoBCtRL0OZn)%0Da} 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%0D=4&gJa&BhTuEFw` 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 literal 0 HcmV?d00001 From f500f846ac954cb9cf173882a31a195a769b69d9 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 15 Jan 2023 20:12:52 +0100 Subject: [PATCH 015/305] RecyclableArrayList.AddRange(IEnumerable) performance improvements. ...WhenSourceIsIEnumerable benchmarks reworked to use real IEnumerable, which can't be typecasted to anything. --- .../ListVsLinkedListBenchmarks.cs | 1 + .../RecyclableCollectionsBenchmarks.Array.cs | 6 +- .../RecyclableCollectionsBenchmarks.List.cs | 20 ++--- ...llectionsBenchmarks.RecyclableArrayList.cs | 22 ++--- Recyclable.Collections/RecyclableArrayList.cs | 87 +++++++++++++++++-- .../RecyclableListExtensions.cs | 2 +- .../RecyclableArrayListTests.cs | 16 ++++ 7 files changed, 121 insertions(+), 33 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs index 579a4d8..d2d36f7 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -19,6 +19,7 @@ private RecyclableArrayList? _testObjectsAsRecyclableArrayList; protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ??= TestObjects.ToRecyclableArrayList(); + protected IEnumerable TestObjectsAsIEnumerable = Enumerable.Range(1, TestObjectCount).Select(static x => new object()); protected static void DoNothing(T obj) { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index c138fb9..2712b7c 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -25,7 +25,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void Array_GetItem() { var data = TestObjects; @@ -36,14 +36,14 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void Array_Count() { var data = _testArray; DoNothing(data.Length); } - [Benchmark] + //[Benchmark] public void Array_LongCount() { var data = _testArray; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 766a3b3..83ee075 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -51,7 +51,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -59,7 +59,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void List_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -67,7 +67,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void List_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsList; @@ -78,12 +78,12 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void List_AddRangeWhenSourceIsIEnumerable() { - var data = (IEnumerable)TestObjects; + var data = TestObjectsAsIEnumerable; var list = new List(); list.AddRange(data); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -92,7 +92,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void List_SetItem() { var data = TestObjectsAsList; @@ -103,7 +103,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void List_GetItem() { var data = TestObjectsAsList; @@ -114,7 +114,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void List_Count() { var data = _testList; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index a85ee9b..a405506 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -44,7 +44,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -56,7 +56,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; @@ -68,7 +68,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -76,7 +76,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -84,7 +84,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsList; @@ -95,12 +95,12 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() { - var data = (IEnumerable)TestObjects; + var data = TestObjectsAsIEnumerable; using var list = new RecyclableArrayList(); list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; @@ -108,7 +108,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRange_WithCapacity() { var data = TestObjects; @@ -117,7 +117,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_SetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -128,7 +128,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_GetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -139,7 +139,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Count() { var data = _testRecyclableArrayList; diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index d5af696..3591ecb 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 requestedCapacity = _count + 1; @@ -221,19 +221,90 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(IEnumerable source, int growByCount = 100) + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { - ref int capacity = ref _capacity; - ref int count = ref _count; - foreach (var item in source) + if (source is T[] sourceArray) { - if (capacity == count) + 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; + } + + int capacity = _capacity; + int targetIndex = _count; + Span memorySpan; + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + _ = EnsureCapacity(targetIndex + requiredAdditionalCapacity); + memorySpan = new(_memory); + foreach (var item in source) { - _ = EnsureCapacity(capacity * growByCount); + memorySpan[targetIndex++] = item; } - Add(item); + _count = targetIndex; + return; } + + int i; + using var enumerator = source.GetEnumerator(); + + memorySpan = new(_memory); + if (enumerator.MoveNext()) + { + int available = capacity - targetIndex; + while (true) + { + if (targetIndex + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memorySpan = new(_memory); + available = capacity - targetIndex; + } + + for (i = 0; i < available; i++) + { + memorySpan[targetIndex++] = enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + } + + if (i < available) + { + break; + } + } + } + + _count = targetIndex; + + //foreach (var item in source) + //{ + // if (capacity == count) + // { + // } + + // Add(item); + //} } public void Clear() diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 959c07c..f07c7c2 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -5,7 +5,7 @@ namespace Recyclable.Collections { public static class RecyclableListExtensions { - private const int _minPooledArraySize = 100; + private const int _minPooledArraySize = RecyclableDefaults.MinPooledArrayLength; public static void CopyItems(this T[] memory, long startingIndex, long count, ref T[] destArray, long offset = 1) { diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index f482d9c..db99cc3 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -62,6 +62,22 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(testData); } + [Fact] + public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable() + { + // Prepare + var testData = _testData; + + // Act + var list = new RecyclableArrayList(testData); + + // Validate + _ = list.Capacity.Should().Be(32, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Count()) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + [Fact] public void InsertAtTheBeginningShouldMoveItems() { From 8067bfb542ac4f3cd705e94dae35a5166592e610 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 15 Jan 2023 20:13:49 +0100 Subject: [PATCH 016/305] Tiny cleanup --- Recyclable.Collections/RecyclableListExtensions.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index f07c7c2..9dc4d46 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -161,14 +161,6 @@ namespace Recyclable.Collections } } - //public static void AddRange(this RecyclableList destination, IEnumerable source) - //{ - // foreach (var item in source) - // { - // destination.Add(item); - // } - //} - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long LimitTo(this long value, int limit) => (value <= limit) ? value : limit; From 4bfb40007a95ca5f6ea961369c8682f986dfb56d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 15 Jan 2023 20:19:01 +0100 Subject: [PATCH 017/305] Missing IList interface added. Tiny cleanup. --- Recyclable.Collections/RecyclableArrayList.cs | 11 +---------- .../RecyclableArrayListTests.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 3591ecb..8294b0a 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public class RecyclableArrayList : IEnumerable, IDisposable + public class RecyclableArrayList : IEnumerable, IList, IDisposable { private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; @@ -296,15 +296,6 @@ namespace Recyclable.Collections } _count = targetIndex; - - //foreach (var item in source) - //{ - // if (capacity == count) - // { - // } - - // Add(item); - //} } public void Clear() diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index db99cc3..66ff6a6 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -237,5 +237,22 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(testData) .And.BeEquivalentTo(testData); } + + [Fact] + public void CopyToShouldCopyAllItemsInTheCorrectOrder() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableArrayList(testData); + int[] yieldedItems = new int[testData.Length]; + + // Act + list.CopyTo(yieldedItems, 0); + + // Validate + _ = yieldedItems.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } } } From cc364c5fd7764c8c1816ca95f6e01b963f6e3a4a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 16 Jan 2023 22:00:45 +0100 Subject: [PATCH 018/305] Create benchmarks updated. Global benchmark setup & cleanup added. --- ...nkedListBenchmarks.cs => BenchmarkBase.cs} | 16 ++++++------ .../RecyclableCollectionsBenchmarks.Array.cs | 6 ++--- .../RecyclableCollectionsBenchmarks.List.cs | 10 +++----- ...llectionsBenchmarks.RecyclableArrayList.cs | 23 ++++++++++++------ ...bleCollectionsBenchmarks.RecyclableList.cs | 2 +- .../RecyclableArrayList_CreateBenchmarks.png | Bin 22660 -> 1117876 bytes Recyclable.Collections/RecyclableList.cs | 5 ++++ 7 files changed, 38 insertions(+), 24 deletions(-) rename Recyclable.Collections.Benchmarks/{ListVsLinkedListBenchmarks.cs => BenchmarkBase.cs} (85%) diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs similarity index 85% rename from Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs rename to Recyclable.Collections.Benchmarks/BenchmarkBase.cs index d2d36f7..f5dcafe 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -1,10 +1,12 @@ -namespace Recyclable.Collections.Benchmarks +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks { public class BenchmarkBase { - //[Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public const int TestObjectCount = 1_000_000; - public static int BlockSize = TestObjectCount switch + [Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + public int TestObjectCount; + public int BlockSize => TestObjectCount switch { 0 => 1, > 0 and <= 10 => TestObjectCount, @@ -14,12 +16,12 @@ protected object[]? _testObjects; protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); - private List? _testObjectsAsList; + protected List? _testObjectsAsList; protected List TestObjectsAsList => _testObjectsAsList ??= TestObjects.ToList(); - private RecyclableArrayList? _testObjectsAsRecyclableArrayList; + protected RecyclableArrayList? _testObjectsAsRecyclableArrayList; protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ??= TestObjects.ToRecyclableArrayList(); - protected IEnumerable TestObjectsAsIEnumerable = Enumerable.Range(1, TestObjectCount).Select(static x => new object()); + protected IEnumerable TestObjectsAsIEnumerable => Enumerable.Range(1, TestObjectCount).Select(static x => new object()); protected static void DoNothing(T obj) { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index 2712b7c..cab42a8 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -5,16 +5,16 @@ namespace Recyclable.Collections.Benchmarks [MemoryDiagnoser] public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private readonly object[] _testArray; + private object[] _testArray; - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = true)] public void Array_Create_WithCapacity() { var data = new object[TestObjectCount]; DoNothing(data); } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void Array_SetItem() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 83ee075..84387a1 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -4,9 +4,7 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private readonly List _testList; - - //[Benchmark(Baseline = false)] + //[Benchmark(Baseline = true)] public void List_Create() { var list = new List(); @@ -20,7 +18,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - //[Benchmark] + [Benchmark] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); @@ -75,7 +73,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void List_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -117,7 +115,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void List_Count() { - var data = _testList; + var data = TestObjectsAsList; DoNothing(data.Count); } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index a405506..fac626f 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -4,23 +4,32 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private readonly RecyclableArrayList _testRecyclableArrayList; + private RecyclableArrayList _testRecyclableArrayList; - public RecyclableCollectionsBenchmarks() + [GlobalSetup] + public void Setup() { _testRecyclableArrayList = new(TestObjects, TestObjectCount); _testRecyclableList = new(TestObjects, TestObjectCount); _testArray = TestObjects.ToArray(); - _testList = new(TestObjects); _ = TestObjectsAsList; _ = TestObjectsAsRecyclableArrayList; + _ = TestObjectsAsIEnumerable; } - ~RecyclableCollectionsBenchmarks() + + [GlobalCleanup] + public void Cleanup() { _testRecyclableArrayList?.Dispose(); + _testRecyclableArrayList = default; _testRecyclableList?.Dispose(); + _testRecyclableList = default; TestObjectsAsRecyclableArrayList?.Dispose(); + _testObjectsAsRecyclableArrayList = default; + _testArray = default; + _testObjectsAsList = default; + _testObjects = default; } //[Benchmark] @@ -37,7 +46,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Create_WithCapacity() { using var list = new RecyclableArrayList(TestObjectCount); @@ -92,7 +101,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -142,7 +151,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void RecyclableArrayList_Count() { - var data = _testRecyclableArrayList; + var data = TestObjectsAsRecyclableArrayList; DoNothing(data.Count); } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index d6a166c..89f305b 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -4,7 +4,7 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private readonly RecyclableList _testRecyclableList; + private RecyclableList _testRecyclableList; //[Benchmark] public void RecyclableList_Create() diff --git a/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png b/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png index b70bdfdfcdd40f538aeda58b7a58a072abcd64d1..fd59fd8d177e93e1401d503e410eb3112051a14d 100644 GIT binary patch literal 1117876 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

Gdul>y2Hn<3-bk-JQlG}*J|M=zk9q0Yl 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

G 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

pNaY<({ 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

I=dWd)HyNoWOI=6D9<062+SD(Sh*D`Y*ruHZ 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

UjI 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

a`7u#9x$s_VW0YR$l%^dP_ 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

$#~a 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

EKz`pki^y!BcH^b-2X&5Z0p_X@o{!)C% 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Ëdm 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 z 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

_u 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�XOcRHfGjI><_XI)0m(^=0O2~AG0Rqw9nxvZDT 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𝔩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 z zGyQ_%)sB4|AiW%7uWbLTl~EFQOEOnQr6HG}s@MhR*XA}6x6$bno=quVs@8>9OT`Gl zpY!&>)QNhJ&lbGR{}=+czaudqSfNlw-Bh4ZK7f7*bMD|CA1kK@q1MA?rnTe>`y}^}}km~n>_j@!aw5aTHH zw24AG)6ah!Qw8?8u6k0OouJ15 zSibULl!DUsV3>Y~V^2TIyHmRT}Rw$dM&JKhS772cVP zk9ETA-i@JBox9^*1>C!e%ym)1;1lwEaNpC+S3)Z?OT!gGg2MpOJg9^N85NJ>-R{*~ zk+XC=)AUsI7-P-1WBWxDRETt@7G2 z-a;tW(L25cpx8c@*f@f3FBgifrTkFZA< zH)cvt;mI$pElZbuNfDZhm>dA1do$up=WDWTOx{O;`^DYp3UJAPhlL$ZxiyhNX58)X z%sv)aZqf^F-**!QNwes!tFdmr#hI=+eVLXEZ<-OR;F}xB>2j(}t)UpXdi$TSFapPa zhlM!{F@s&d(z6>OEMVZEGtmaMkUL4{NMO2n0uonxvJW{u_@e|&4U<)>ITYkOJMl#k%ZT1JNo`z6->usN9}lYQHn z4g4oIO!=&_mnqUS6T;5i=P-Pg(#G$t3+`ta19qF0OdB4&#kp{9s`#3aCg2}dt6-qi zk?S88OHG#`M>M3D#MV@}C-GQW(tiFG%GTR-Inw=X1M>wyX%pJc|MFO`IaBymvVMi&>Xd%N|NcgpyUED(4B=LV`-=&6vdVAim1^Zl{naiQM{?-aHvC?Wk}AD zFqMsdnU6lq05Yn7?gX~b%Z@o}6K+}X)F|m@xq}RQtt4$z!|AMe`r&AB3VoKPLChg` zq8Ggb2Dtv@?{}TN8#!o~>S|OlTcKZ#8GJZp*qfy+Eetvn?5*Gv}t3;Vev4YuB)3vFbgnFTSf>Y|BQ{L;Pyy}T>VZH(b_gt z-nxaBCvVA$2;T-Gr!7pu{IyQaMFOi` zTisgrJYMtM))e7ewc(#bxY`I@B4{#i{+1Nd5=n5_^YMjQIuRt8<~Mf#$?6vMAM~(p z7ny^bGWyYtiEW|5yLt@dW;EZ~Q%?G=SFq zLJEtZfE0`W5*3S20Dnc-}{{S=S8Go!%-^kgY#{a z%Wa6wvNyPw+fDuZ>T6G`yAUGvA9W^3UaSfHT(HK!F5@Hn#ktbOhL%uA5wxv%Uhcj< zEecy9JycEeIJ-PjizE`qAgv}l2ivH6;*$~z_3)DQ&9@%Mh{tGUo7mxSM|7q^1__oD z@@yxzYKb&*6j@24B4xoT1SaFs30fZ>)fch=0}6$cS$PG{b1nzBmL{TTs6w=lwJjL0 zY@|35h_Yvw0BQZ=^Oi154!0EuS_yQ2BZg>Du5(g0qxq3l<9Ugb2OOGX3>^_BEnQx;)+`h5_7P0|H2MCxnhSU za=~Hy##(`pSmQWwI^LC-96rT`bxfOI(8Lvbd$MJEytXuJ$g!fed*>O_f{&^b{jfcl zN6r73NA2D`<=~tBQD#!TJXWgx6-mwU{_~>ppX@N$-t;~Ss!>WzqqL|G1rG_{OQmq&+u2=MiH@P#H9NPN&*NH$#?_kty5Ksxe)j(C4dQ3l9e{zkpT% z(+RWgPsH{t$>GixkZW9aP-paBF7eCRLc=iV;s@iXeiF?eL2N{HVh#n=*;>x^$6BwY1Qqk>Uv%8fd?JianX~A{)wUZ?%~CCyrWni|wI`EC5_l zl==7{znZ>c6(mz(!3c$87A0~EW9-+6W~yJMKSo)zL?e|oC6p$CuGfFqfp6 z-{Oc_*N|)B!xi{9Q{2n%;F*1nh-rbXOaoW3`B>H~lO(`ZH~9^iIPtY;Gl>@UNO$<> z;PIgr$0BoWYf)9xj=iMyL<=+-mZ(xTTBdBoZ59G_cc{A4kE7_Jx0v`!hZ(|iwt76r z;7E%Sm~xauJ}h_&UYyN9mQZ^Twu9R-jGW2wj@SABGyTS z^@z@Fylb^3^A-G{x54^VmOutC>il-ObLCjT{+M~z0%93aQEOGt}bgbF|Bf;ED|j~V%t zhGKOh6(NKrK=$#{fG^ltxx&7{j1kNI3d(I3G7Rg%KABach6rn5(KY>#36tNLhQ@AE zvR3$Kb1AeqYAEDW35~sUroRlc!maphunn?(@Lg{~agX4lYllK9UztjEmA0SF)f-@n zhZv-z$5bVtk~4D>@l!_)PCWdIw`3Gct#C zrITsPBsj_YY5hti3(ULK0uJ79H5naaA=RcV?)T0yns)Z5LZ8CxO=v&#ZYGj*~tf0qoGrkuk>9@P+Kfa{5qd#?rHo9KDlxr3@W z<_)6~jf+wJ`C27w)#q1_`M!KTJTN`79vz5uw6t88jU*{iTXotOWd2y-l058Wa|&HA z3~@L5T)P`H{wsGQC_(+j-8e7&7kA^(OQk>UGco^SpQ+$o6`sj>KtOX9CWAuk3}{<+ z`M4l%JTX5M=!_RRu53Ftb_=#NSxM+dZ6(LMFa!#5Sx%yUrgYf=Jt{)fcvDU}gnY{( zUy^+deE49tfh17Mr0H)FVZ_duYr|VErB12iC}beI)pS^a)Nl~bU7Epr%r`T-%0x6r zI5T*y4W{Q^y0c@aNlp0QI0pkljw3yx#YviZShokqPf}^7)Jmv`orKj zQ@=a^kXOY}mA1kwJfa#y!Wz$!2bGP?pR6WXGwDms=88m&n4fOqH=v^8efGF4z9OY zX<;#q^)1yQbmQ7Xc^)x?aoLHMN!p~xYEA9N;doRkdSO>WM}2RtjX!jb4N1c&^h&cb;pD1`>eN68*T(;(7za{kzoM%HWfB|H}CIE1Hb*|HEjq9whxk!SAQ)U)AzF?-e49?$3M9n?q;{ zBO+Zz68C56Kilw0+J5W30q7hu7JPXON}+0?{b1Q?jsasc($_v_XhXSD;M)PND2H@M z?{hjZ`}p6(U)M?V0E`1vNgsYoQ#o{#C&l$Xyc=fEm(PT^T7&S*6B-^`0Lq$Im*nDAJ#IeX{a z=VDZ*=l+#YLlt|_gGtY2<#}ahmudbu19QT}TKRvzO$Y@@f>4st$SA;H#Fh=hBD zJ<{*pUgrHU+h1?0M7*=JT94xDd>A-d*%TOfWd+Jz;L}2Ugmq|Ll=Ib$V_bx=m8av5 zn-QOD;KL1cHI(o2!#ibbfeML?hzNUJ7*hlHneChYZZmar*(+9A{yjU85FbS7W+h6G zeb<)2H;sdiTvPnDdLiq5n?T5)-h1T*OZX5v04uX!W-uU-E`H7RQ$s^@@e@_H9C2lZ zOfwV&sIuG=5A7GoSivLg&Aaoh`9eZavJ840Vq)`k9~1o$$gSH84%^<&{<@w@(0g~V zp*E<3{P8gS;dMq0@Ru)e``ofY^j#P^b)LXpu;)JRYx;ATXN zF(!0>mk85_sd9;R%qdt+B?JS(AwCdVdO0t5X`@3BU+I1S^Q~Prc;TKGDmA@B6zPZ$=LF8l=Q_o0 z@VcQuwko|^i4hVcB+g}lv*!g#Y~ipHy? z(%b8TZD5KZuEiCM6x{~$EqJPT)Ts5}&|bUy{@Dz3&N`nuoZ zDy~*Y@@)yiw1&|)A5oM4(-ifhk=3taR+crff~Dw zf-E!=O7=z=&xHm$dosqRSYP55`@ryq-Pr=%E(Hqf*!^7m=x;03^$#ZwN(A~BAWxNK ziVB8qECvq)e4B^l#4nmF6sXVn^TU^L%v*C%;$D%`2xFW(i=8e7`wAqP#oiznBE)V(@R;S~9Cyp}?PG3E8 zk4ojfZeR+R7AoQe?^~lZWVGb#c;%g{TVEmm$ior7*Asx&OG7drF5AwHDfVa@X1b1- z$rr$cS?(t$A=nb+5*GYnqe=SO+tA_uuOQ*wbfpG%JP5d8wY_(~#N}Ir^#e#*WYXmc z2FFd|%Oe%yl_ni)+BXsofugf*7y_2mCAHSIuIXk(>TNY_Y+vOLV_q5?lHpvfF{*+9 zz@TP7pyJN(n~X6PLRTv}Uh@KW_6YQQAstblY?c^$9?&B9=q+ii9=R{yL36ScZ&OD5 z_}NlI8j}^Xr%yfmF;!$Cd7(Vcm^KF{FiZAylWfZQ8NyH8BlV_-0JbnY%(G+Mtk}nc z67eSti;q%7GJ@FEd8I6QUMU>546%NYD(bsBijMUXIxU~cdABGtN4Sl{Ky%;d9d*dh ziHrH+kR<$dz=D9aPO;V}fIxE5*N2+xPjneClXQhcYzLHNkC#ttZFhYd@llj$J!|vs z6`kM+avLRn@4rEpy_fqpbQuG}+^{Lx2qGrvhQxLx5-w23XA7)^v@` z?l=r8lh+)~gsIiU>-sA8icrvY^?Dg9nlBXZHT$dm!6FbaWchyq&4IE;ZNT)8lSRUA7VZ-`m)WeKG6-VT2(v>cjn*6x`^CS zL6s2ou*{0}$*9n+mthvTov(RZW5g#atIp019_LWpbq`6}-6nXTcMYeV`WrncGS%X#FYC{`_!U%-t_wC#Wq9Y=g%?5I)`Q1<*h(CZIW{d%5v z#ZdZNQKSjkQ~cTAPxOx)k!lj4$#|>NILgYPoWi2yG`ylyH7H9n&=_2&k%w;uYYZ%z zcTn42?|FP%dPm;8Mq-1vOaPS;^~k!6p2{eCkyS*z{<1q>Pnf~5;3-Wi?=WKb@IeN)_daxY+(uTa zuP|orM-}`}iZXi?3CMV3my0ngfU}CD*A^?61CcT!tSCAAu>)cOp`BgXj$Cbk@{In^ z?=hd%4kzD*`zH$Yz0C_s!9NyAdi>LOqOQ@xsE;ogU)U_Za4cTY0mwb~R)e@_YB396 zk7cjYXy^P(NcqndkRJWtINclSzHuS`w$@Lw%>Csn>Om~YCC?$>%N1-H+zUa%%k}Z> z1LqaTN3Vm9%)zn}PEhX&eRor@Y=Yb~FRh$!2Mc?h-4FU7ZU9C5P8?VxeFKNo@83S9 z-hyk=p_GK76}e;p7x@u|5!5nQ9g|2fZ~trA#1n|phb1Bm8B@Nee&_mbSLF7e7g>k@ z2beQj3e;8R_nrzG75R=JrS=&hw0;y(J{BKp*R)H?E=?Q!XTn+DB(yNjlvXH{!tY@PkXLar5T@_Dd)c=p z5VSqqh;%iSe1=>gAMDBfciFjrn@FIt|CmV1yF!>cXR$5FT%-)0k77{a9qPy42NT<* zDbbmt&O;v$#n7L;|3(0a$+zbAS9KEW7D6`7=R<-!@Vs)N9Gv)6P%^KmYttC8%%4`R zkBZ1!v$Y53iJ@NUW7DmcdIAVrxN}U(ibp6IK>VeXw&3ArDL*fy5J)YbLSB*kGx=sy zsp2e&$`=dWp)4xg7CeL$LF=`(nOf1Hxc?OYibEGS<5CX^1JID>ed4E*u4x~eH_j^| zKvSAbh0wGWm`Emn5oy*d&u8bwQK3i1GUq&Vno#Kw$|xk(K8*764U$}JSwm2?l-+U( zcwA4}_rGq^eC@lcV2qBjg&x4B&|@-|9;}x=XaE3gJl#6dW{3TUb}tATlqB%kF|l?v zo!@@P%{+$-I>7s2xogR(2@S+;d!@FlkC_&I-(f zUIvLe7PqR~y?WF#cqV*gS|LoPJea86^QF2%Z`~+C+6AmBI-dkQy@LLUiR7+6ZedPT zp^+H%d3hN3gI1NOob;G5OND#HnMIKYBG)`Hvyf@bq29Go=V?6aY~zg7wEKqgs0Y0` z%dbH!O4BdkKElP;Cz4On%Gx!WR2qdYxC@+B+4O@KU_+*$7MZRxQF2dstigm1J)bqF z0i|0*EN!4L5%k?@z{8fxuBOD5dXKQfkl69$mUl?h@V(5+CL9dKc09Tt()*!x?efEF zbKz}Tbd6S#JHpxPIi`_5V3(0DQ$F4=BuC5%$txBBkAznaiRdF=wnGJ2sZz#et@f}x zCQ0DaY6Lvx(p2C;2`}cDx(j+Yb2c2VKE)T|(cyTqCqgy1HzUmn!B4WhNZ) zr-US!*Zk^w)VSBpL4+d(S|~jR|H-j=L+lsFCg3Y$o-HSI7u8;VY|i*}`b3!ahJ%uL z=*!dK*AvKYM}reSDLEyK&sd0ryLYk49~F>RkiXg5OOS$*9&LYflXg4b_}HgNvM5(! zT0=1biNhs$8EgH23&(af&lFLc-txIv&*$&*^a}E64N$3m)aK4V6xtd>j{HUAwz#$U zcT#9uvb4HhZdZvN8inLOb$3*zdU{#-%?)7829UuZLijr7*;)FlH@6<|7Sm#8d+o5< zk;Zbx%u5)h=QSZ`ezNK5*R5g;G+`6!AVp#{rhrFQLcIdQcWAv0np$5Cm!KC%UL>$a zWE;qy`B_6Wr?`oa{|;vRL}L-{*2Y=g3T7FXq}DqKr+Jcl;l&Z;Kb4yc8dPgGA1VKq zVWj_#^XMJPr{h)879S&_VYk|Fwz+Ob_a@vX*r^z7dlQ{`MT%<81QiGNJJWmMTdQG1 z3E||FeDz_|_iQXO8`%isP1?IdL5z8>(2=_dw|2W3p*3=wF34F?G4 zb_ay45)R(EST`#J19T)E9P5Hni#e;;Z1AAZsKxaxqvx~j1KD+R^ivzTf*;j*y~;1p zK%#=}YiY(2z6ld>w>(OESr&Pchs7AKgv>@Yj!YAsYjaLXs(K|+MQ=^Wdm|@$ zY0$>$ypEDC6eqeFbsoft=ia!Qh@6wSAIf(g)*^&dL3_tGbhx$U6hPMn3kjgezI^m!alc_WA#?0@L^{;$KGWe#M%2ZN~aEg{|;|eXzTB&H~%e7H}c=4 z>Grea$})0P9(Hl#K5X9&$>KbL>^)K+;bvM*W|xc6@5J8%ool1qEv#cv7c;_n9Mwg} z-2V+Wo4;DFmZ>JDDYU3FmdP{CK~mJvbw}^FU16ygEdIR(%OM^d1sUR?orC-A_}2M{ zy+`=3sgxCjG%!v|o|rqrKO5fS*wzCV^B=z5%krKrOv3l!SZds~t_%b2P0^=rP)(pQy^jbzPG>`U-T;WG_ie1B<>$UK)yrH4+ky}D zcOzkVu81qD%aHpc>~_Z|v=usO6Nnz~bf?{A2UA44wDMSKm%*iNF9w!uZpa3UT>M8> z2o#Tk_bujsX0)9Y`1~K|=}Mh#EAP_3Q^RD6AtoxGo@*T=L>-F9=&b{l9Xx?8^eeLS z@eLx5UT;hof^wze5?T&04sGawPZx_Y$&Fh11~yg8$3u+j8zCA6im*Z@SJAmu*5#eG z@_HhfJn>gPO!q!hLIx&H8mXv5A1KjGezG)eDhll9+r>~taUQiwpkBuuuO}RxIp=#t zZL?1Q3VcDacInChj5pv|z=d|=m(0==-|1-1r*KLR8d#m}2aIegd z{?!A^&UIMh{GoE%-ylkn(?BmnqurZ|c)Sta@{v7_YL?}{AN zBKo9LqJP^``nw4)Nj7E|wcnH4{6FzvH=zHt0YQvBfHl7U@nYjU({IJ$AVc}DfjbD0 z;ur3NWQ2??_yNp<-!A|}AyzDc;_2_@_%6uzz;mfR*^_|vyk{$Vwf4i?nwsnh*kQ99 z&X*1Atq>%h{l<-#=K?0T?os~ld-b4%poLFF)%^kR7b1R-DDlDpF2(%_iS!$!1$J8Z z99$4seLv;8llJ^D)KhEwV=Oq;K}f#z*T}yc!1bgCo;V+M!yrQ#`jIWwQdG>6>7xKBxEs`s)%7btTahrG4t* zsEC~SkqJ4{+nDwKIz_4ntZ#+Qlu)>`7CJt*2kX%9w70ca_JBX|n`IO>i-<2j*ORK5 zb2ETgt-bf`s>gz?UOPuGxJF ze?&MD-jrFUvbN-@+Y1ct-9_Ovj!KvgQh&IB0uy0%5eLtQ3 zQw6k2U}B;fDyqjwm+Ne?@t#C|iyr)cfS+?^ctFlJVyj2S$MG%;zJbzWrFWf=z{UU~ zQhiR}Cv$oD0Yb-H(V3ck4m~*w*z8icBu8|F!FJ49sJiZ4uEoG3ApL<3{DFmgsM2qR zy+*BFxpj9Q!Jqha1M33R?j*Hy>kyqbC9-Gv_NHqcJ0e#eiwL_1KI}m9KxBW6TZy{W z9<6iF@AmYR=Ah2m6f$0op{N`>vR|JE&mRo8emLGTGL?|~9Gd-LSazEHycOuTW-eE2 zFyBf(nX0@ea?lrwm>xWTS5dQ%2u7$C3hivaR}v8)rIP+nvnSx}OYu95xe=6R%Pq>R zWQVnCCew?=KSUyxrMu(h!>CPcxbvWnA-G!aRs(ZE=1^jwcf?Sb-B>z&5ftIuElbe^ z%QNgh)j_s$=E1K-5*qyTSZ=DniP$er9K=W23Q@p<*OqOTV-Ly=w1klbHcFw+Z_gTr zX^ZMxyHu;yFqBA8htR}1LkHwA(zgtg@7)#NLO;D5$gq6}Jwhgj?}#abE7X&2BlGU! zjS1Qy6YWEt|lgz0DOfK`?44?0%zXBGLl62g*fnBK8QFgmgIbql6&mc_yL!Wp?M2HR7K$0~m5l$)P%ba*l%#O%VZjYK_-`U3Bjq^Oi$^6T=p| z`ML_VmAr&gvZ-U^j4!gLk;Qj?TD)Rp=@O*- zGx3bvdv$V|q>Mhi$V_0%{FhfHFQh|p?(n6N1D8kdqw-nuuFf7br#2P2qBS%l6S-Op z#asi5BYE_KVwV}a4+4Gk9GFo*ueO>w~Dk9#{B;meA2Lm{l0>DFmcvs{(i12&A{!Mu7Cx*9+3!Ue z3A3t19pvggQgj{6^%4@s85%Kk>Y9T#xm4e4+Zm{XyS0PWreq8^+TBPAJkd@{g*wud zUq#U_74Cyy2c}?XpgY=C)L0eUH5!q*mgYsZpvQxbv?p%mwYJ5{E&drJ{f*F1untMXQ>xC;0~rKe#S>oFvqvhAyV z>$fyDoHJJh3pR@-v_8A~-M`9aRwgK0-XHYs$zUZkOq0x>CFg+aVTsFL%3Dj2L+T)u zQM6IP_7giNNy^(Wy z+OPX!*x|{I9>>;zs`@~kwEq#2?o?zIlXNwgoX*GBuZV#V6~#kjBsp0tL+4Z#U8*&@ zZdC^n&(BN8NJfL80G)UghvkKqHva25J-%$8kZ3)vcr@ne4ON4xNmDjS;Y7@*vfOEG z-PuU_w~MbD-)^jC52wqeYKG{2mlt9m`5ET=RQs`O#i3iC2}$6gzCUimR888{tPS<4 zSMi!5^$=0PxCa08NQtmJcRgSp5WtP^%-A|^jnk3P?3oZhF80p2;!=Y3(_B{r zuQ^^RE#;l}fbo6MCukM===jM6nWBCUx)Okbb-T4eJLm$NQXiUnkY~Hv&6C?;U#6;; zYJGz-_`RNp-VCZ97GJIwDRanZc*&}BA_GKl8rl3EvHAW4so#Bd-a0#|+@nDCw^HYm zH++=Tsw(E(Bwh!+q4QN^Q95)aE40q`7_^-Qjw)Y=@Eqi_zhKeBqrJ+Okrim(ba4t7 z(AbP2wj{JdnGz%2=QUz=@5lv9$pK3u)zP`(SB0w)rmq>J^VNlo8hv05V!kRG#H0XT zxuw|ysdIseJOJ3Vfp|})w(|p#*qS?3SlunE9z>t^ep6Zkzcy0B`T7~G(m+9web^eT zL%Sn8s_$(FaCcZ8wQ5Ov7OJaJ`bq`0bK0cTJhsJS4EW_7V=?}PE?dRPiqqXM#v2j} zTh|&Wzy|qFiJq#A?p~~8-KnNHmP0IlyX>6O&BM|Q`1GK1Ulr)b`8y1A_Ba(gnwx=i znUCMZ-no~Kz%GW}4vY81pdiiz+${iNfUB^hJR=i) zvI{rB9il$QbNHyTY|_k4e60e3T}^J^HoM~unCrFBpYsm8n`wm-Ak|}8mJ$*3XEA|P zV-3Y<^=&hz5ypt`*cR;rkrMwx;`pzDdbX0EQi(2^N3(4NEID@6YT6y0KZVs9CaD`# zJ*0{?qyI2lH0grAIZD-KG);oC=aF=0lpj>%DA|vOsy})>zR=5AQMm%_op6_oGaL`s z5Od+5hIfY*KkPW)nkCDsmAQ&I%E&=x=3j=h94Y|aj(zPJ=xRnGS%&?4!g7@DKUSG6SYN1%>2e?8s}Yi_YvzpkKo9ccKcC3NE<4D4pZZDs zNbvH6`RgDaiUi7TY*$HfPTQrLsYVGziNpZg_rJ* z67mc=+nPJi<|bi~r#xr`{gktYT0g%P@&YoF-A*6yN1ZA~8*qGSf=m5**p%3TaaUw- zl*{&rVq9xnwmG8&J6f6l-tl56{YkVhG#QYm+me<>`)nMEk?HZ@71-##t>P@6&Xmsg zrf_o!znPDWAV8}2A*2UJ7y5W|e=>*L0No~x5Eb1~(-%tUthBAQ>)j|E`LB=6CdqeO zg=N52gn9g3zVd{|uH(8Tg~J@H8#tDhzuQwo{Wvf|#x}u-jF%7|=EavYBXHl?nv2wa z)qMVT4mbY#LckN2`R*rtA+E$WNW=<-iUil#KVLa>4k4Ar+Uh6TYOrw81Hk7 zhz>v33%wO!0&J61I2GYWF#B3hC8-89ndijIO~a!M-oSe#%pS+M`0Jj>+8JaJyw48_HR z5#Rc0%%2cHV}4PXgbjc~cP!uKxx44VbH5r_*CDowuZDgYK|bMj|Jc$FFIJGqW|0z&f{g!0KHOpH9F3a&0xx z3qO)6nAAp=MckDbhAYHocxN3=aM3NH+IlO(0B)KgyY=1cM2C>dToHb%j;jOb)x&qn zs0pikpby7ji-1rE6Ky+Y&zek^?IzNZ^b(wH)}Jy4NMDZtSFk^h*iM1hIc{IX$NfT9 zy|a88gVMrz9uDcT|IoeyU`_)dO+q{U5ue9 zn5o;UA72_vez;LMxwiE;HxyQq!Dne0MA~B`^J2`Y{b9Qvy1l7Z5!aKQMMo!f!*?>_ z{%V1C+baA6HLtt~Rg%+P=Q>$gAs-G7u+$87Y@fF;tyq$wt`RGgUJ2n3VQ-64WMvSI z>=KiV1o4=0ovgsVFLvo_<7^4ZLgr zx^2az6$N@kKO&Z7eT{UwO{TW?w$`4X4~@=W;cJk0l)_2ExZ??RW``M?5q6kFDhpXI zM?F;iN%V;6oUwgtL-MZ`E4MrqL0sIcgr$&?)g*s01tCC)KuhK34f21Lv`bgGckH*} zS3?CGcEh!kt!J-{(UdyBOMwENHwpe-{_%iMsj)wmLnbxMvw)Nw`^P($JeWjtS4%PU zs)B}Uo3X4fbF{E_(kIwvVzXTK41-2yTs3oZSa{4ML;eq7m1(IPM3Z_C3Zwkm@Q%mj z((8@w>q8BchC-$=&}D+(SC);~!^R=2Z=t8)1JDiSQi6}=^R6UJCB=|U$IwuJFVQ}> z&JD_Qm2T#x1YG$CbgRSUc!aauWcR;LbEmp=?K4A|@wu!0Yc_o6N z#rb9}pskI&Ak#*rsZzPnB`bY&_O)`cyf3u^E4Zs$u5EKmTj4`!Z*l;HsqP^5)!@P26B@eVc7+gPlBF?`?d7JJ2rTf zPszKn1$|z`-j>xzyDEd6SyuXt=u)*E1WKfO4k)In5X^erTS8%nb?OAVq0aBOPNb%{&t$xFD1I1-q9e4g24ae83cL5y9}Q1lq# zG0pGUDrH+2%sWae&gx?|ChWQ11qV2_aU$GAk#%F}vQOlHvwczfNc%Az-G~fi!3$MQ z;4@2uGv8CM#C5`r3pZHZb}U|m<5lI^kG~}2GIEsV*L`Nc)No?kUC33YmHV^3cn6~; z19RDn(^bZ2(GQQF;(UBDo$-mc_;K-lyVXUmd7Nk~S20o0(hcCwR=0}kZy`{>9Myqe zSD#>%H^=MzGKRilVUHvKYLd<6m!-!uXvBPiW(WFC;1GE;Mak$8bX*Uc7iVDqfzi5Z zgvH-Jt0Vd@oT-douBfI);Ii{2KXgh#4G5;ZqZNEjGpHZV#G)=7eD25F&=BV>mzN&~ z$F4rl!OqgA#aHXQq-+^yOsvw>G&{zn;zY~u;Y#wUff0oybt*4+=`YbG-3Jkr$5p}G z%QNIS8l;jmwczl>l9jUF+H$iL<;&6HO53dICu2Ip$C#eP>cP_Y6N+zok!rCzB`{3j z#!eL0x2>c_^aPW!o+*iK+cm-1w%wmo!5>$E#_0R zdc2!B!0oM^n8^ePC9Qnh{Z`lAZ0jQ|_6JwlD+*uTs{^}%)V)gCm!y*}4t5Q1Q3(id z7YJr8Ft=POQKRio6VDq7v?;h=>Et+T!l|O)L_O(JNprQWKt{sG_-K2&qJ@a@S#;kc zQ+!*bpH_Wxs~KU%=a6%KnypfKoleP~jb%asnNTwd>aoL`pm zCfV?5gyJS|+!$eNU0#|D3k}+ju}Hh?75GY0`qt1ou270jm3y`hjw8iI4AWQkGr@RF zQ$bay9m||;7(8`$S91tM#qnvqoNt@98atjO!jkk#wLmJgUx%0bBL}H#Br*yZ@l0GB zJ*f2FK3{)4aLA><*fVzi#VEgTGvW@>J<>p`$1=0Z_UwKW+wle^&dNQ}BNW%m`^v0lCEg zD&E=iE_4ONa;7DbOURdZq*q;jJ&1kBNXcRS5AATrq!h*1Ufu}_28Kedap@1(ri1HQ z9xS{7jB<{RD$z}#Iiq&|o6*^xk&p9d|Ds6r{M)6TkdNN3=N2kitIS54+Nqh%j3<|v z^VHWGEGF6XAI1nyxV4s{JgHE&jH)*}0)&kf;##j3P+;bU4~k#Tu0E5#?_N_$c%XUU zc*V}>Q^|$V()2cOmA%5r(VkA|!V(N(wUlhQbbXw$t-MO(WIOB0M;cSt!j$z8e3Vmt zv6tNVaN~ecqo|kM@Bvm&)^f~{;0DOFZHkS!cM?H0kBFOAP!xC3j&C0rItvc+I4@8# zP6>V1%P$23Jn1G=cslXQ>#n!z#7)=RfR}SSrBUPn7W>n=ARY>LP)N~saQ#e%D5@$- z5B(-R1wc&h0>a8&Q|-3uLgaZ%SOsJJGf?5>Kc{QQ@9U3#vj0g;gKSIhL^FX?D_42h zhCe$Gjp+H%2NUeK&Uhj@FZ{Lz88M*F-7x${7k7aWs`Pk1s;DWPj+31!sz2aUehFD& z_A=V8{dK!jLej$Nw$Lw_!pSV6i)FEv$yR|)i(3k*aO5zBREUWU11$%J&Rjy(b6vv5VpdIQY&`C`9g}@InV9@? zX895QR?ZvDLZ_UBhAetw;I=b`Bu`GgnSn)Oj&H#a%I0OPE;!J=)|4s0 zxL#kX^$MqrlwB+b-aiYR)0X&_ZZ*|d-!aB{vTZi~?7R_2{?y#XNA%4dM|~a{HGP`_ z;z#$e+;S&5WfTJ?^I!>C`A8 zM-ppVz)ARUB!#H)cx{gr+aEjzd$(p8rOfQ_tP6iJ=y;Z=Ht+E4-+>RlUw&b)d;tV4 zoEZ<4eXmz~LNdm{rU5YnqCN$!bPZpa9?IS-QvbL71)I-1_4Dl2JDL4cgF zzr5OA)X;l{A=N~2-NEL1;VgYB6E`5va=s!GqBnv^yijC)m(pmkT%Uza?M#} zr+5V0blwaI&`BIR<7(R5zR#uRw)L(Rw7wYm$<<`ohmWb2Q4Ml>s9a2_rffwPaXSb` z64eOlT%UWyVous?p7os90Z{?lW3dE6_l%GI!tZf$dhl~c>wm;7Cn;d-z=xnvnh@SQ z2XoQsr_ME_$6Ce|o+%c|QRPF+DohmA5}CzgmlFyTmo*~eKyTtGy$3pX*x^Buq~pLx~j-3g0~!=Ir$pkH`CuN6#pUz5k(K{}cw zE}0+g3LHM}-d<1V<&18looiMP@B zmy#71lJDD;B#Xd2FdXn`7B|IZO$m3=ImZX&O-OmzG3|1G?r9$NWzhNdTXv>B^N8%D z;*-+)vr)Sr*{I&(Iz{now^VX;AZSbx!mfbojpwx)w&xT_-7!*SW}k?G3Uo+t!-til zbO{uj66g>Z86O2t>ldT;a~*ti%RCR$Ngy+FPwOtX6@4-L z%*rL+#Z*6g)-q9M8Fn{=mN#%_Ri*bkX}>CQoPgiKZHBY zIPcg}la7=()Z6QvQvFs^PNFhiwjc3?@Q>MFjW|4~z3y5|0as-ws*_FPxLiGQ;R5a? zKrf(Xw3my}$2dpbUVr02t1$2{HH`)5Sv~QOw{riL3+<-{FS}`)zH*1ultmS#slH6F ziLX3^{!TTcqng1;LAEGDym8{sp~jPHx|!9=>D3eOI*h zUFzgp-1&DsX5YPy+K-_tYx-@S4i?myPZ5BBcoZ5UTzc$`2SiP=g^s zqy&7Y(q*-x3^{a?l%adeMydVJ^lP{n$5|>WrZhcGHFGcp(DZ;w2!%sW%+I+RpcMe^ zncx}tiS+lLJpipenVEhA_d9METJK%aNd}VL$zkQrm zx_Phr?=O?Y0`0>-dj8vZRZ0dE>i;&LlZfnXiGLeUC0bBB_TR=!gwNeu<+7TvtgSlk zifk&HB(~~HG)=aV0y9XQ8m*0GHX(jFmOV`Rutw0t6tu0_bqAJAMZD!{=WZKvS1ahY zI8_o+ve?pHxT&>qGxs9*}rWxWIS`_`luHa`LLOsdy*E&PZJaX>@< zz6VA?Z+;bAf~C|iW4C7ev7FWPRCN3eS=p8{R(bSaH;(I~xp21ni-SJZvHP*+(`n*& zGAM<_BR&PQ5|q1?@56_QIm~dstx{alx$B19Ky(~~EK9J>!xj={%*wrS4_VBML+bc~jT(%!}JDTGak?(TTAMu8eY!>t%O4BLp$GNcqHkQ5E+j_jd7V{m z5Z>U)(sTq?IZ4ncS`u^(7py5+LpLiBo^w$wZW3BdnLdFv_`#3zQMHWJyH<*=i-=W9 z^;_yZ?dOG1mrD;|;<&O;%$1SENp3+YcKS(p6s1hIW4UXqJ1or9aJ?^PpntTA>&xwe zZnX+JSMib<^@*T1dRa+-C$9l?5#x@oRQaZa9N^CG@j%1`$BHjZoj>FM(6-8{F;27m zlbr)nRYmspm_>KNBOHY&JK)(3EG%I8*3H8|8K(7HoYH8r|2pvW8ObJECK~hPjP@KEX)|DTcjU+_Z;nn7(5R)c% zn6Cbzq$)oL;y?)6VDCZUSI52^VdO5s%d%SvaWWb-I@Ut0JpHu&K{MoC&l~-kTkrd$ zBSJ+!*rkg=2irR4uj60^Z4*iMSzVV6`f?tli((xT`W=s(qQC=n zcU4s|otBngIW*8y3}N*t8L^xg4OV^q_o zFH)xSfP48~+7H`QTUfjeaweDbnPppNqR|JFnVKz>_xLo>`Gon2E$8j{rn2wq;5AuF zX&O}^#a+yzXY%NBvJgIe50GwFZ+iHmH8z*bUxj_i@6n>x}V92b9zAzN|ZwwK2t3Dy&V>q?_!oBeBMG`Q!>$d1Hs>+ZOFn zK1DHiqF;ToDAX`|M|edGzk(NAXAE968|{{zsMRpQy|GycRl32Pb2nHg9UJ;hWwTYSIBW3O1q*kZwz=is;W>c~APd3=uCX(mH=d5e0 zmih*IU{|cR&BCA;@S{;+&mp5A)Am|fJ7qy2A=BzRT#t9FHzg$gv09h#vfD|lZ9tiFrYMtyoyTrFo!d-D0mEmQ^;WLG0zjWzhJ7Q=XO6jzzvh6cP z;)LQ0e|f=X9;NQ4e5`tH%$DSGmPpqjW%Sq+_Wzvy2?IQl{z7D$eWa>Ts65NyNTc#E zIYg2*x;Nza)l(9yb3-KXta#VkX=&b9h|cm;Ugt5YW6i@WCcn`K=Bgwfl4Jgw`VAFu zuA9TFDJ5UlkFe+fr&kByllxA)LaL;dQ%2lczzhf0EwkY&tXyV|j;M~ni7mXSpU+Q& zufH87s-sWC0;CHyO|{+o^49X#is>QV`xZ9UUAf_WfssbWZJ?!21>d+%!{YhgEh&s~ z4LxISp$(aVg(Vn@Axf~*e*_*j{B^Zcm#^@B<{|JW!As=o8d4ahcsI^>7#<-P;BH|F@(Bg=g>A?Md!2lW<$n<_K;1C!smO88&? zfD^ZJ3Y@eHLH7(sW9IxkSDS}SL-6fhl(C*OIuo5b-%a^@OSVMW1(Y+QJ@uuN7|z~O!}wXuj{mp?>X|Td|$Lz+cg&x+&1_I z1K1a)-Ws%CUZSGa%-RXc|M7rT| zk^zV?Cb`Yv3^V~q1L@|xWtVEuN4r}vt23}t-?LX_Y3<*)j+?6@ttYOZf^o(BMUCedUrPsNyTeGc=f`+0YHvMzZ-s%1)pe2X(1k5!ubtQv^? zkN0o6Xi<_={6(Bl6ZDY@!m{W4%a6_?T0$H8(8YhhqU1qEM#3-Z<|Ni*h-N;;xLmQ# zFD;2K+{HT1{Jf2)5V=mzU53E!(Pz#la4^7kz0vgkIBf9now0d(rl5;XL|P@T(pwQb zST|1dd}TJTMy|VnBZW*S4OaBpEf=&Yu4Yp%T6`p>7=SE36p=QLa`>;hwNh6k;I{s) zBY-RHtVfeUt=5L0nE+=_)i{YXBzTA1H(^= zMD9RimH2*z=PTIe@c+DYf17R*faU+_S#t!mnH82-p^pC?O8*nz-R5$dus$?#x7O-^ zU@(^1v8Z@KF0~koq@^DSXK%W2 zR}JenggKkzT=rTW40X$A6g>Zs%Lemh5=sRmmt%(M& z!z0L|JJ+DhlG0Go!tfLbYT8d&)T|)}6LvIyGqV(v4J-Ngz}DrJ$Pk_uy3{g6pqxNX zD|zh%8*7iu;e6;MPdmyb-(c(Bs@Bf)46--#7DCVl6Jd~q^s{HH&lccB%Vlpcx@a>B=H~zi)xg(%{K~eU0O5$fy!t1Fa zvLWBvvY;WCH81E=8-{ULEAUOS)lqw=GtqiH5z*!55Dx<$q}Hg+>~5dKq{dw6=0wLy zFccSX>jG2pRF3KQ&jyYny_eJynxjjsOKzH}D%9Oh>&z=nEqs1w@-*p3 zLN@af8kYb)&yJGzalLX)*53$6&svPM9WVZ@$9Al+qE@^nvbP}!`m(Rfy?~A7AiK`B zNIOY_n_DfsUvvO6+=QnZ9{5*6nLa01K3Q$CP0ULh6Z|lzb8aP!_O?U=_F{{?}} zN#4i%uJc;%&mr5QAMAN^RRDa~cA|9Ui;myDkdA|2x8!O#*MeEMCre`L2_Ae^#Se4? zeJFj&onm={p#~FoZPQH@`5iEpAGXY0)=p}7T`4DBP0RKNA*ouO-A9!#8p)Gyrz4>| zGk#`UlYT_EITx6-PL?GcO8o<8cOv%v6Ha}uZfg?z{@!b_6;U_Y_kN87N>$5HSCXhQ z;E13;%fIK`#W~7JtMERrv(3;g@Nj4}N$AhVH^s#=Upl)ybs4lp7j8KeysX<-;2EP> zR@!ID@%?bG0@!QfsD~hXCB1bI#&%p956L!J%rDCm2lhOk_Ee)2o)=c{XBa6*?tNQ6 zs6V%VpY3_Ru+|2_lmS@09aK>^gl!peWh&V`ksgY%u_rf_Tc$xK@4NdPmv+@jc;eYH zcll~O#)?hw`K^>7OYrek+<1$K*toYi^G&r+bT-ys-uF~Zh@sG(J?ewZyxBn;G(mvA z@&*>raD=h>qLg*}L49m8&w^dukd+K(qQu%z)k)`A1hCi-%I`*C@u+;F2!S|cg=2>b zaNCC}BB{4>i545n_V%6DoUotzOcw@{{07$#FcXk~Es7hxm=>S@#a!rg94dT29cqC% z&{d=xopN(0{E0eJKTXs{8vNvX&OHR3Z6z=JGjq=srq{!@u=o8B9-fcHv{}^xc306`F>mY_E_*LjDX+gRjplvj;{_kSuufeY zDe8D{J9)Yb&p7TWZ5+7c*N$(WZ(g%!YSI`BY6K^|%$D)1l?(8qMxsy|Mq6%q&9C=r zLjS3k)8xR`O6>UZ_-O4prKW`GB<@=dL{TwCM#x(Z<n1kJC(MF zdQK^^v*`%0?WNTFZNixDhKWngHrRU)e-M}t;P|-w@w{-72%SEZFb$h(MhlU0STL7K z2owkJ?)FhfEjd%D!>ehdLSK0Qt@I@NcR zi%Xp06RKz&*D;^1c|Q=Fd=YP6*4`|t&nPeQFJ9dX2R?lg_78v~vSTS_Ar4l2#{^ssJu;ho6hbgaJ@Tv+qs(ZYF3- z!9A2Vydr%&V5Vg2xz1|xF?p$t6-g(S<_&xd+7RJbE*tv~*u9%`0AVGr{dzL}?Kkfx zgAsi#gzm(hk-wQrn$Z8`DXn;4v2qfzWn{5H+y9R6dAZL{{xkdq+^6-)CfmXg$rfI* z_=mV0O);0NKR+vYhinM9bb<^S#RDW3V){LGomIOJ7oPXr0(#5YoE|D zeCKtyXm?%{eXyH!>O6Rn4qbk6byyFBKD|aItJa4lpWz*x&M)gXt{jA|s z#=W?dvI50U#_|KsXiyuMo>&)#kuQ!T(EV(ti?G2{o+3U=b5k~^%WGs&o3Y4o_NH4| zo7slgzUnMiwH}FFlmf2VG)Pqmt{+;f%Gb=&F_Q88TG)yt^DWp;pS&@DSMs5$N8^XQ z@&f7kGoG7Po@L*eQDP*bEh{Z#u$=v6I=xQD?py0;@OIbT-@u-?gYP~8wxC4*3o6|Y z;)nbA4q-a&DGdS?uZN1r2GjxS3=hLc%@wjdIj36x*xc~w7jmFVcP8NMXSueh<bd0(Ge>W?<>k9Mw!7uTaq2KGC#1tu6jkiB@II~atdA5tDyt?(?dq}*ov;$ zM9Fi+q9mTvdOI5%Ogfa{tbxeiG<=G8EN=>yvxg){>G-y#Oz0lLo#qt&z`f@KJQAA^wEOnP^<6U=%;3>}I4a z&&c$Eu z4Kl$?(_?H#m#WtBt`FC37sU8$x;uF4aWFsO+&$NOSvf&rxzO6A*yy9obAw4^rETnF zc#bT1AM_T@ zXu@cKOs_F)+)M+l4M+P{J@j`mcV%hIB0>Sd4W|_402d;ZJw1xNUoi+HGi;$x?VM2r zJcd54;BM+!#*wbb)$Xcb$h`GfU+?y;=~i^HMprEzTK16MGlwah_NXpvItFGZ-3*Q? zLlvqwRut*fQeL+V-ay{;Nlswwp%$7;l3namlXdE~nii^mnroIC?WE16)E)tV^_URd zq9<-SwC~c=iXZDHDSni5AjmRTvqIGwt(u0= zj67?^_L8gYo}15W7k5}s89!&HDNvTWk5wc#MJAo#94sc^s#`4x$Zm<#^+-*WphmjU zLnM+EVhLvSV?9v%zo-i zpJ(3UwwJ1z&gw-azsd;+!L2Xn)g&{TBW<~DjNNdD6;(bGkiIwIF-YUImUhKQjj93w zr^|hEd-TJIkazdAz>sy|W1S}bRB5-KharQ0o6>7iJAkGsc+2z!DvZ0rnZU`SR+WZ# zq1;tBSVOD6>+?4zCqBEL*X84lzSHWMmag<|V9h|`Z8XdF1Hd^>QR{i}2EU)^%lHm$ z@OH{v&u;-{Svbl8roCBMGvAq33eIx8*y0NrS^<%2c0squAAw7OKeK z=DevG-IcUjy=6BHc*1bwZXt0HVtT7mHhENasa>2*Rjb(y&s7O-0CHWlW}!~~d%IBE z@Kwy!_3FDlftlJ{!Ivx0UN?=fRPc3^MqdA;srZ77|}vql;LPSch~;E9*Qpo=S?R zxE)pCSFU(+P03hx-^y{V)$-1=ZnNK5-S{$-maAyOcp+N#R3FCV+um?0PDf+PWv5Mm zRZfqlkUOR2(#lEB?jOL^;}ztHL#3mE5G zB+4nvxjl9CRs-klXp+3`+>eAM#RuHXoH9<~LOzjPm*8x&u+vSGGe`Tz5`gU?6YIDN zn}`@(*V&+O(&WA5Che%(Glp*%EXnyFl-Uj(o6xGT-Z3A2o3*XYw-+s%cY^>&4|OQB zXn5@X&MK{t)7s%37%{49i^_)Ia%rsW`T|74U&b(MbzM=!~CEbW`re_*Nr_UF&`>hdG(69S!5z1y5MIIzPt z{i{!_(gF~i+$GGqp}Pht3Hn8YT<~8I+GhMw@m;d&zY>=`S62hVx!o3ZKqr+u(h6ow z^crmZBaTe~e%f|(K3(cw1^p~)@~Mj_&2Xwo#AAC9E3`Pnynx+pk~)C;2e`p8evfhS z@qMQH_%X!`SN32dFZi$Kx`Zd|-j*4hEoQu1quImXKCBCAV*9$_+Irn`97NBa)nomJ zbwF36mLJpteXSvz)kK{$)%s>w##H<$F}PErKDT?d+k<~>1vF%3kYyNNE?Fn!vp7`; zc0lZ)EIjAyESt|iULn&NAX#QU#E)8XMq?|?)NoIFH-%i1;8+hS?d}jdFAya1)lr|ToSlxbR=7U+le=h>b6nrONA?kq zoY1yodL+gnN|a!+<#_#NW5PM7lJm`Q2WwPAD(t$wAIzuW6r)GTumrVDzXA|2y&tZ1 zmbvkNLeP(+H@7XTgKu)f{h)0wUCmQC!M(Vurn-^&seARm;MUvGqJ^qqYH~m?cEXvP z38AhPg!GVnS*Cdu0}lUMVV`~1isxF{$GEj5&C3*2Pr7*A+OjXv2Gtv4?>Us}U8cRX z4Il()Uyc?+#vYL=lhRbxdI-P=K=JqSzJ{m09*Yz_ITyNbr=Y(Ys9;l2a`Bm9VJuMa z?{k5df3&pqeCCthY3lr3`3HG9$5}2`C`#HklZ1<}F8-5UuIFS)=@ZSBkO0gyK8-I9=0WPD| zdp1^ZgkaAMx{bkr$=7S&2TwIRQ9tbcG2jeUF77$eb?0i zT{fg=YcXEL!x3BiUX)>7>I>2%pS(Y~yrnfv*jOE-g=>X_^)%Bj^vNzJdlONxWi(Gq!2#2 z<1gwVB2xg0n)XQ(U$-=D>;m_9ZW`m$dosp>Y=wT_Cn?K59jj{jc-v=q)!!m@Abjjj zdq;jhi(R5CbAj>;U>E6b2SQc1DA}zE5_nqXK}=t?5MYu6TjpHH`3rcW9l;IKBPfGf zG`$u9P+E#1X6}m)o(0vqw{OK#=KaHE)(-%HtM;+DVMX7Bc(r(>H0FitWoi6M>L7vn z0Zoiyk4n|&zwsHhn;SZi!&Bspi)hMsHAGW54~906`S#`B_5 z6RCiOBi(7ObDZnHm<5+teaI=^7uylVe#kmrF}3ip;`dONBz6E&zne>UGQ=TzU#7XP ztgX_G{aDz9?bK6E^$dq=qCsA6>D_J;6rTq#EJpJUD?8_=^V(%fy^$Z?N&WMPB{}p9 z{6fdCvW8jwjs>9KhlPm)lM%o0-B7XW#P_o{Rvx~?%gQD=_oaq*trZM(mf0;{_K~kV z&+ZLTog}NuDLd>KD5=5?*Tny&Xf)C;V$K@J-qz>R)XU*lOP79+qitHIfDwefG!(vF zgdhb4n9)Bt7!h#zDNVWrao!~Zvo~l}$)%X&=iWp+K2u29;`g~D??QP2V0npE)7F8a zqU}iqt``fxqUqg#DYf%a2z1w4=}{H4fxO6kow5(@#*n;3HfiC5yp&(yYas{!_~mal zJ#jT}+V{iOW0!Zr4NUlKdf`6q=Jlsd`){g7q4ECB7aqrIo)n)uRhNi6DQ$OysgRW( zb0D*ZOe)WBw^PY7Ipv#u3mWU!Lm7D&c73o@bUh;Rk&k0u?JL}{aJHw?K zo8JUzc9^`}S=JIV<=v%LC}uayN9~Do{fP#hZ;Z5K^yc+Fa?(@-oXh(h>+t-Jm3W~z zw_0{65qmPXqI|g`5qrGsR9~mplofmX$XP=h?{87q1KS6}@}@^O+XkA(AIhN9kZsC~ zSse!6+GS0jeM#?n)9vHv%lQ5lap#8iG4Gt^U2}*c9ReTw7Z!o-1vex()a2=VK-~#3 z=n^!4BV9YCLtoJ75#!3SxhUq9r!Sok$eNeu0zm9~&xf^VCqzm3(^qI4`dXyVgz`t5Xjeft>EwKK~VB7Inp>6_W7VsH}VH;5}g!6N; zu=j(cmIyttzxHPX&dV{~Xp7D$bVC_U!k`IJqd!>5YGF5{f=5MrD!fzC)DWF6xO%^- z)Z3QP5=~4|fw=}EZoowikv!kJ;#Bl!{mR%phW%A<<2ahXL9}f9;&=Kc<25&`5&h!SYk&gy3P3v0vDsIGZ|_gP^i4?Xl2aaCLB}Lp zx`Z@rRqaqw>(lG5#s%9^uQTpYb?3*)apf@!!HZ{Ib&B+x^v{QGEC;eB``wEy{l-+Y z{oB}ikw9rn$)!IJ<3sbyFcZshV{~r=?a!vj0A#OTW_`S{1D(_H=gF3@qEk-r6Z zt%#}rq25c@o%pI%!%DRwC6!J7zeRagpVJxROnhGsB|isf+7-wB1DXyH;mNkfxB%ah zvaO(pERgu>!dqoiKFREWyqbmP0a|ewW=FF2>(tYD!`H{VLsB-juCK31r?i#mlFaN2 zMGZTf(m1Sd)D~#lilPBj7ppXDQOBZJT%~8q{>b~)(tYDw<(&LFzI{N3dT;G0J1(d^ zZ|JK%20`>^^@=H012E5}v6BN|dcbM!%lGvkX zt_cm7C@ibSyorVB+n{?>t`WdyW^S^Uo~tt-N?f%hn!xJPy@L)QnO?~f$Iix~a-A%d z+Xs>Nwn7(eytl88d4$=dm6S0uRql`G54McH|LKfD^fv$sS|zVRoIN^44gIho8Ga+c`tqwIWte_x7_|+m*GLifW1R_5sBX zc(!>j?I<4AtBuR>w2^1e6pHGFn4#9nHIePve6N0$BzFrin1HrySoRM*2|8$#Vq*;X zTI}TBkpJCYx03*hE)HEE`V8OYKDfhA8zzy9mAbwf!3uW3z6*~v(;-_SxBOx`@;v68 zy?M;VSK3OS%XtT$QXVw9UNq=`zeg?kVy-?TAKTNP-!7v>K4-r$qqW$f!i1QcVZPu( zs?-G3PtR z%P1)QuI<>-s`Mz;*>)Sav5#!*Y&8zijC`Pp8p-0}*updbhV_Ag0xAzg+K;U0%dmHt zO8t4>F%}~d)*KccNa%_UC3lUrZvB)G7|fZvxN(5Lc)}$qZ97>U>3|tc23L)v$0$Aa zv+wfHP$^%>y;xw5GxE7=Y~LgO;*87I>g!+1i|>k8${BZ59s58}ox73+s_Cp~F53 zhin~B51W`tR-JW|VVA<>0t~6(Eo0fW5lKFzEWtj?Q-_leIJxjZh3ofTXW>d)Ksi=i z?%hhnforoQh{TM{(|S@$E>QrVaKiY9VCD@&GH;7j3rYyAD6--Az;a@X9=OZ2O7sMeoAJ zYP46GMlIRbv`pr#;O40fHxZxU1p|KO6;%_5p19Q?nf&8TvM5T)PK6Qv3po7e6RT#2 zJe3~%pbm8r4;7R6)~ESzJl@LF!k$ooPN~Khk+=m7H|2u=eZP zfL5<;i>ZR-wonW0oojjs%0qA<4n`!@Ui6D0uH6e=bKTntKO3?hXGh4AqOKpen7p5u zgF7J6n0Us4FabW@m8A8x*asG1*qhk*_{7ltFwLm(E5+szMT7UnB)h@bNKEtkHM0tV z*ygEJsyOEWt6r*ub|PMD-qf(?Aatmeko0ydf5QRr`4wWfXrw{&6eJp3mG4)x0&Vqp zzMa3ZMpQ%WU%VbcWBa64fy&UzuFMe*o}D@zKLs6Yu2V#+;gW;yzndh+-;%ZUIQjza z6hd>LHtNQCfnK((dOotnfUQw$m0h9lieF?%Ln>Kp)9g~1sF%LyH+$quzbnz6~?ve=gy^P6U z_*NyN3==6uW^4t7_(DCLVh#+4Dd=ZHmm`Js)>U(;wbu`_CoOUvPJwecunAOZFL>aetW1r}|GQ>3VJ6F7yW@wwrc z^s^2@8w*p!_*q5o;PtCX7#FEt93V!R?mkjz(7sch)(rKZ#pUY{wPI48K?j1?Nl@x} zP~Tb8p_risf;X@Kj`j1O-yv7hT_qM~*pVtXkV8aAQEh;nt!)lxIIC^r?rYbQ1>VQM zD+?zh%Q&@@Hx`dS37>>~Vw&~DF?cCL%+T)*Y1R#Q7Wu;G%wyelNp~zxSXs2nKP_uJ z8aB~^o14x*d`#Zr@kY`WfB&!Kea-G^h}XV2`zzmBTE+4k-2xORbll>Zntpdh$kv(p zj-4LuT*rmtM81CUHjlvUwmGr;J0*eyMaeQt*mgloo?7uoA}E_b<(FEg^fjB1cc~e@ z7OaoFXBac4!>TUViYo6%5uSTih-a8DRJn+|l}mQ!TkUYXK_12hp^p(vC^Ztq8eDbj zi6wNKf<$aiZ&%I%3CU*cic82f6^02Ku>x|FZDY%n4FPB-1+2_94OxY)(Qfu8S{i~0 zL{bqEx~+A*2}n!{nuJ(tVA_W+lpnAkC%qjz3|@}z4K}es_!$(IWCmx$p|cocm(bNS z(-|k(j?R})C8*%t0NiuaG&SE)7>}y)Z}o796$)_4*EO4Oytph>1p?cpY9hL*SB5%l zmD0CMw}CLoUs+!;O|fZM`64;=ddX?0&AinN8kqvK(UYazsovHPK_i5Gc+? z=YPlt4^wrzy+M6HTg$`U(gsBoE{A(R%+W0K^^*U+Wsh5=zp6x5GfJ3%jQyGRB)>)m z5MsNAUf8JS92h*$xp^VG%W>7<%CVuzlTZ@t0@ zLbwmgecD2w&BtJleeb{bPZfK$5-pwrY91XKj0AKE@+32Z^+_IcR8S* zC2Jz=lwD%oY`aHrbJ2NMY!Ize607;)?Z>u%x!minA@IIT(%jEWxcW$tS7GGS~$@t<@ygL4x~{Ol+0xT|`b~8G z>GG%@*-|%gzsS`|h2x?!`~ zhsUUj|Myz$6ZTfKSRnczhzkG;uzrHef6DeA_t-m|oJ>(fd|&9#J0!Xz)`ZnP+Zd+` z`M(#Hz#b2iVm-h%?8p}9@87^L;9s-l1hpG|Ygi_i=E5sgvR3wDb)Flw11BqzL%@K- zfcpgU?p$^8=4F39OxhRwb(r{C$4%zK$VnB27Fo;%tG?%VC$0kReM?hSBgbs`TEq__ zsO8^kopjtgu7x{vmLs(2HJnmEi%QwrFy@@!0_EF7g>6P?wU-QMsY#Yw=e&UlgC3&R z@NM0Ob8Ta24)Nr=YVY89@oKaF{d3j$c&KsY&2xhWH#cY6S{qedWVej7<3=}dHyj!Jd0gT zz}8jZX&E%U!Jf^#)5&*mg!;;n^hfyZ%FGm?4C0L1S10tj-fCL!)VK*}rJt)XfCk>t z!hP)?`%q%#_w^!IrUrJwFAUm6_5IGLZ@~uBhAjo#nw;E!``%jX;3gzjxH*qNqIb&} zP~3yFIkQwUB=w{S5BYYir1_G)aWjvI=)U8_QuhYt;A3T0gGBAa`%>vX7xVdi2#{Q*#JE?h1H67##KDDn?S>nIRe|k>1Lz> zx~C#25#P7e-0T}{9;vU!(d%|dN6==Xa4%X69@|P+hwGB-C@@f<1S8u99^wgJ7{crW zScim)SxoUlJ2H~$KJG34z5hUb=zx%+rvl=-C@@dOyJbr6koOvMpK&8YkXnNfQt61tbh$l-&3U>1OZ1c2oN4lLaMnOojhW}w-Pj2 z-FAWn%~TQzjZG_+wV@3ktmEkmy^jsLgCR}J!qmbYpDIDr zeF^ReeCqOkx+WWPzZ~E0>b}dBg03Dti}bpqRzy5Hn-yE&^Y*!kNkk4sRtk_zQOX{U z;Z#7OSqw-a0N%YrR|ya;i&LVUTNqDUDp|kx*UAuzY#|%VD;#RLy5Nd5{C#fRND+Hm z0!6yW#9{IJGliparOqw=Wva(_Ec8SF^q|gG9?su6a-lEu1vb43_BpSpY%aj8z&suA zWwje=&D6Z*zl9GApf#Qoo=scLfyM@DC;K#jgS|VZxv{TF@W6wpbJ5Wc=?eWVJema# zcPH=Dngt|%eiilE<0_E~!r2pVCqJ8A_S*ZP`QSx~Qf{F5@Rp{o%S|L{LluHn`t+rZJ@%6<=`p*^XkOW*v=+ z+gOugCaw`xj8W`z@BT^g`8RoY^s2w_TG5)VXK4921kEElk2f$Oq=Z5}`)9X?vVBwkhI>)w z8-u$gmaW!qqL3Oyh39$&bXtj^vI^PM@Q$J>k7i=&dn>x+I&q<0^pQ5rU#5!ft_C&f zcczHS0h-bMb&{)VR%Y}~dzL-uK zj^gxIpQ15#X!%uRq_Gq}ES>!Vc@AYG(`<-dJP07uYD8uAY2y%N_}Q-G^ev}0%R%GJ zq692H=w$yOKC8)#d1OmuN|{liMcO6*i!*my7)zPv9N1f$#{sHbm>WH9=f^_S;yCl2(C$aTDWb83+JxTdO$22+qCIE!`$>h zHk8fPsLf;;(Gr5*Ak02YDqUdNpa+GTzS;;V?e}u%5xLCBVzq>o%aXWulDk0a?Z)ZqK)tyPW>QNGpi#JQ>TzJJPOS`85M4cOtr!0O z>t6Yp1D_!v+kH4Sr`70}h|_$`k3W6hJAlX_^k2p!a!b)a9Fe++hOtJ>B)i-Uf{ce@ zX}w}^)9*O1{~o)yDak|dt~eAuutEBz>u4&hz*u?p{rz`u9pN6ScgSaze_sR--7#i2 zdax44AV^bo!Q8@maWbBtne@M;AAYd4FeguN)LZd9yl8OxB`3Mgx{9LmnL*F?JSq6M zZUs>3oIF_(71<@t>3Oxq>$`>`lKL0gCTk0)$D09UD#^T? z(-xJ*+AdhX{`5*})-E-nBqtpgMfbF7E`I~$`+N@7kJ$s4~6ZVk#4O6InYj8XulX zoQ&70m%2XY0d%dYNg3m7brmY*)jvTU?wgv+c`a73b{@e9fG(n$o<=}69(Ye8PpWRRT zehiucmp;@!EVuSVpW39>BVO?nNaaSME?zajBHhMJil_PIiULxzSnDy){b^JGsd$~7 z)tik6qb4XrlpbkMPx0AUms!oOmRPD#Eh;09n9!%CEl}h z`KeMdKPe>xx-9=d;NIy=&p~cgB4wzkhqf$Rz_3x&!0{Pq1uVvHu_gJbNMTTh4J>Bi zTiTtkM@sf>ZJF*;ZMgDMEN4+V+K9EJEXB^vpHO$>n z@#>KldLI54L7xQ&E6j=TdP~cCVrGDDhq+X&nn$zS7wB zCoq}WUa?z=8a!RS#WDj59pIU(GRJ>wRG_u>NZ&kZW$4K4-TW1xxf3-1I@+WlpmDIM z6c5NQ5WbEEZx`HY&#z&0AeD+2yX{J1RW*!?$1L`b?lo6aPB(U6j1JLRE`R`K@ zRB)uOcJ=L#=GBtZ^C|h+`CDeRC-EEF{dZS6cp zLeN>Ndnn3n>IRU?*9Ja;Aq5eKf8Qo)JEDX=&WGlw;>+>nv$K#a^WyXxU9(>fY9@|Y z`K<-}g@OI^9>GAziojEnZG0b#a?evD%8g_&PYA#8XlA|NzSSX|-k`1?N9fLk^ET`u z{N8Q0>#?h0v25Q$QMp}p;T{NgV?iO}w655*G5;l_K4f2!Nx@~*uDkl3Zb#Pel!Cq* zE!xN6;@uXQPlDBhYyyA89y^{rJsvRIn4d5y}e`MA7 zqgj{lovRu^1*?MocxGkImHLQ$lOKNKYK(1o0bh9)?cUEh@eArVFXkG)$n`6?kU;w! zdJj#v7Now_tMhRT`h84N?m5mJS@00i5v?_RE&z*?pPXcD zf!rckj_fr5$I6Dvw+8Wn`%(Gvc-MU)Q>jmkkWYgwwpuso+UL-!=EjD$20z7@OId*d z*-PcbLn*&a|7^#WIQjs!0#BAx2LFk(&tIW_*SuKIKiEB;HOcC6$lN`lRf>-c0`#f$ zqg+$^NSwh}iS^UU;4GvPg4=$)Ldd^>j@dxI+dBy7l61&257dha?^=c`*HHVj`dH0z zZE4TAC%6+;^kSb=mi^>9fQSZ~={BkOUmg!6N;Q4gzT9$YOD6jQe{m%JH$Yk@ilmQa zr(_Agi2pW9*7C*s2rfwZx$O#HJ}4~a2j(Z)5`ZmB>UQnE)7^6|%Z@7UbL0h( zQh6ME_Rp3CASy<~jyQCh>&{vXDfUh(-{nMi=>qXAz2$|;)n6xXmB`;)sv!R_`s;G! z^Vb_r!aS8_`;%_0a31~B@myG}`OFMW8+({^XX(Ri3&r?yIUxf#SXB-P?tf z#Kz9SQ4|@Jb+8wLKQUy;*Jci*UFcTK0oY zk5pfcBTiSm(H4N-C4jtYtTy5WDs*@Pr1OOCxgy3Ky~+s2P_xnMysq_6tIH}mH0Kt| zXB7Tqml88&&8^k5d2v!>JS=b+Hq{z4l@a;QYiVf6Yp)EIAIZXhyUB4^R+wOR2Nhv$ z26==@qLC|SemuO^k1zH)1dWU0_Y#I`Pou95O&TlIRz9c@-W;EinkJ~uk&?@v5<%k}N%*dd*{7Vn=z-zk=CBd4lQ zpG@R^ZN&_DO*c!r%^O4^)0Gxpt7&E8u06Vy{lTm$um_>Jj+`HE?19Ja!A0GCp20r< zV258EL>|2cH7D1lsx!MhBCyW73jpCh(?m1Dl8l*+J)hispH8giVQm(&%pCGCX4}y& z9#+71Z4k%enoT>l*2imT6Ds(1%yap3-;W3>?Y=^20&`4Z0d1T38Qa#U4_Bz>t_-LL zy1!<@u7#oO&~D2G-7L8;BjnEI@0EdWb+6Uh8jJCS&XU#76ycBpvPtD^E-hex4ly5V;IuFnyc;(qe{ZB=x7fLdE5mIS5)1^ zkopJgJ_1tNaO%g3dEcy%=0jS44RCYFhkP>-s3VUp4ztHOdXkJW4`;XgkPDgBmd`%& z^}q7;A89AS<4H90MX9P_pzRH|ySNXrH1@f5DbfOL zlJ{c0?Dgo8mwaHY3D9MWiA#g_X@(OC1L*v>I3LtI82lst)j@3{|5Ka5ma0W-eQZ<@ zbn|n;RiQ>n7zR;~S}nKrt!^IV^%87`Z;%yHRURxowJ*ucoc1Q!rcUD!t15ts)htiQ zZihNdMB1>{XrzYiV{atVPMthsQ<~0ewp)BTUYx8XYS6Ed1mTrJ;M6i>8~A>ANVSaN z8pBROWV@9Q$uzA=hmo|J=ILu0u#=wEr3zlOChVI3NtHU6!jsn8_<>YWg+l0yaO?9V z;xePk^v5~Yo5Vf=x0;41ka@ca>@VEq_(H%;7y6O-E&vXT+J3RUe=eQ(QvIm$+)F8h z*N%eBGFiVzVz<>GD4%-0beM%nJWY1>d88&Cach(!f2V3$?(`x>FVAJ=oH==&PC45* z`Bb@LsvB=ATnHfQz zNpj{^#2Q)ZjC1npz+#SUhllOlJvOjtbZ{@DNEJ15@8&#ul%)DEX7|PNltE{t=jQCr*__?CCvMK}!sQ;4R2eAlT8*RizG<6K zRlDqOB312I$h#NmLFqCwI2Yh%S&L^9)slt0Nm&T*+kFa__J*%U{nDCB^&v0(UBwum z)8~G^iy4nz!ONys@ye?m2i|=xwm8j!&K!7PK>`^;mrgG>Resw{k7}r_E$heUDGj!_ zJvr-HWJuSrLyq^Xib-Z&#Aod^{>z>5C&35ncX_l!z46G#qlblRPw#{5l{pW`ID1&} z{H7H?5rMu0aA5jgQ|Yj1*BtfD$e;Vp=L5nqK$9i|*78yb{+z~czRso$(Ig9W{v`9b z@{N%22H>TRH^p}522-w{s8{)zvUWd;Q)@VL6{GpCu?ebS?o$+|sU1?Wa#F6N1G5j^Czaf!1>T*xg8NxI-V5Nq%2yWjcBuG)j! zePW}(JFeMUrd{q8Q*J%i=u*e_Ika zk)=n5h9HElLk7z6z7A>XHR9L^U*CN(jTya$xhY00_Q{KP31o(BU z&D5Vy7OcGiw{HW!T9{$JU|VW6Jo2NFYJQvK#2prA<5+HVyaG(*^>g?`jg(-j>4on~ zZ#{o-l%rsfO0#Mf0ap>n7B9o_ls26sRvU zQd^9pPHv()EP?E1|Jri}`wnR%;;{~-RpPj_z4TA@Hf$2>EVp?zWFx-!h;`je=K_c~27+jrTQO6-_RLfA-9Z}DA4Y9i_;`== zEtDB6zeu3i9WqI9VrpszEZtiJj&pYqa?G_NU&~$fx3>d|^;Dfip>m5e_k~#&=}UUm z2%Qm$*gs{xb-0M$l_z+_P}Yu|y}!F*UmKdN+;Jc~HZ|LyH25NUv(@F+`td?kxLj?d1Ilw}7NJu)LvGkYgNB`JQ>?fy7uc)+xSZ{eB? zCPT?rZUFZ5OenZCb7McS5_)zqy^nAy*~P)^6b!)Sp!o zbqeBUJo{}C#|=|ZU?|X~=NvHroV8;kxwQZ=+4ATi7GNl|BejEmcWKbo`lCYP4|)E7 sQr$KJj?Q0%6#u(v*Kz%Q--c+af#M-C+*xnH9Q>CPR(8kAf4C6yPmEe7zyJUM diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 1c49bdd..45e6182 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -70,6 +70,11 @@ 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; From 4d1c59f6726c38b615709aea798087f022aa0463 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 16 Jan 2023 22:03:40 +0100 Subject: [PATCH 019/305] Redundand benchmarks removed. --- .../RecyclableCollectionsBenchmarks.List.cs | 9 +-------- ...ecyclableCollectionsBenchmarks.RecyclableArrayList.cs | 7 ------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 84387a1..9b3388f 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -4,20 +4,13 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = true)] public void List_Create() { var list = new List(); DoNothing(list); } - //[Benchmark] - public void List_Create_0Capacity() - { - var list = new List(0); - DoNothing(list); - } - [Benchmark] public void List_Create_WithCapacity() { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index fac626f..c5a70b4 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -39,13 +39,6 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - //[Benchmark] - public void RecyclableArrayList_Create_0Capacity() - { - using var list = new RecyclableArrayList(0); - DoNothing(list); - } - [Benchmark] public void RecyclableArrayList_Create_WithCapacity() { From bc4f0576def4e1dbf88e2c04491e21b1b9ef15d5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 16 Jan 2023 22:55:47 +0100 Subject: [PATCH 020/305] RecyclableArrayList parameterless constructor benchmarks added --- ...lableArrayList_CreateEmptyListBenchmarks.png | Bin 0 -> 15888 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Recyclable.Collections/RecyclableArrayList_CreateEmptyListBenchmarks.png diff --git a/Recyclable.Collections/RecyclableArrayList_CreateEmptyListBenchmarks.png b/Recyclable.Collections/RecyclableArrayList_CreateEmptyListBenchmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..60eb66f9a2c8162c9c2ac300ede78eb926c5b0f0 GIT binary patch literal 15888 zcmeIZXIPV4+cu~oqM{%h0g)2es0gSL*aqnWNwjm_PF)u#()lbG5b3bDitThsQeL^Jg!g zJ$CHac}-cq=L+F~S)rmJUDwqGbCX{fpSzGSg7Sq1Bd6^(Va4I`P z$XNZSu zwdA$h186Nd_>)2vTlkDJ9b7vRYZl@59&X>N#<(-oH7ux~P6gENY(Joi(9WSyfP=~@ z1^8njKx_M_)d6Y<0g6t|^S5Q^!_YVK0=6J>Bh%UQ=hh-il1QRc^ffvToa`q>qmE#Gw7W^d1`qsc^R2SM8RwUqi@343@!Ula9*{0sn6e!gxyn1aX zn#7itRQRWRR+pY5ti;WN?3Wa!6IksGW`tqG(e0!7g7NeNwkL=OF8Z-S{T6W?DE@CX z*I0fij=Uea`}kAPQ^s|#6eogWx&Pv>tC&uTpipkW{Anj}*HDinS>-6j&S9U-S5B_1 zkf*FN*3UX>7mX~nx##q~IUlu|I$GZZP87gjkd8-(4f*4xG;!4?-7fAwdZEMIoTHlBZDXK3?IH!If5Et<|f{Epv zfU!el$)?YP#oLJOUkx`viYDb5+PD-s>7+NXBE4!x+02aG5cmcUg!arP$eH?cpu>Yd zV_a+oA{#k)^Sk#_-!vAK)Su#h+*a0`?8p6ho^~=$YW+LX%Q*A6fB$i(W7p%6MSX>% zdbf&rr)!pK_j83}bl|Zr3K+VGoS#(N0+4(X7mH4Wg~sizlfmJ5G!pxB=o7p_#3|`Z z;k5Ppg9>kT$;NAs_cu^(tKV~$KnuALThlvi(R#V#_mw@b@GUSOi08fM+tNIUp4$!6 z_J6o^wupD3UfaA!=Jm~lOgZG@3NQ71f5wD12v5umU*dw16^B~Sxamu+w@QzsY$j-b ztVCnK-SisRw0FRm<50;uKOrbx#+0d(PB^p!B;LO1d}F7EBb(0>XPu5+NLs>X7#{94s7SqxHXU(e>OAC^#+He76zhM!L^m{3q0 z%~c)00|6K4A;7l9wSv_&b8~B)A~hAWcQ^XO;NH+mW6r2J0_@pfcO#!1Wy;R*Rq#12 z;v%bJm}p|jnf*#C}k5>0!t?Q6(!a0UjZ6BWjXdw=^3pRf^ƞC=%K^sf%uBtQ6=CyMh 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 E4U6(9RR910 literal 0 HcmV?d00001 From f3198a45a49286b50e7321c526d6abfa04a45961 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 16 Jan 2023 22:56:13 +0100 Subject: [PATCH 021/305] Smaller improvements and additional benchmarks setup --- .../BenchmarkBase.cs | 10 +++++----- .../RecyclableCollectionsBenchmarks.Array.cs | 2 +- .../RecyclableCollectionsBenchmarks.List.cs | 8 ++++---- ...CollectionsBenchmarks.RecyclableArrayList.cs | 17 ++++++++--------- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index f5dcafe..ac7e1cf 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -4,8 +4,8 @@ namespace Recyclable.Collections.Benchmarks { public class BenchmarkBase { - [Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public int TestObjectCount; + [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + public int TestObjectCount = 1; // = 1_000_000; public int BlockSize => TestObjectCount switch { 0 => 1, @@ -14,13 +14,13 @@ namespace Recyclable.Collections.Benchmarks }; protected object[]? _testObjects; - protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); + protected object[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); protected List? _testObjectsAsList; - protected List TestObjectsAsList => _testObjectsAsList ??= TestObjects.ToList(); + protected List TestObjectsAsList => _testObjectsAsList ?? throw new NullReferenceException("Something went wront and the field is not initialized"); protected RecyclableArrayList? _testObjectsAsRecyclableArrayList; - protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ??= TestObjects.ToRecyclableArrayList(); + protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ?? 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/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index cab42a8..3ebe96d 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -7,7 +7,7 @@ namespace Recyclable.Collections.Benchmarks { private object[] _testArray; - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void Array_Create_WithCapacity() { var data = new object[TestObjectCount]; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 9b3388f..4f62236 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Create() { var list = new List(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark] public void List_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = true)] public void List_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index c5a70b4..2640941 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -9,25 +9,24 @@ namespace Recyclable.Collections.Benchmarks [GlobalSetup] public void Setup() { + Console.WriteLine("******* GLOBAL SETUP RAISED *******"); + _testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); _testRecyclableArrayList = new(TestObjects, TestObjectCount); _testRecyclableList = new(TestObjects, TestObjectCount); _testArray = TestObjects.ToArray(); - _ = TestObjectsAsList; - _ = TestObjectsAsRecyclableArrayList; - _ = TestObjectsAsIEnumerable; + _testObjectsAsList = TestObjects.ToList(); + _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); } [GlobalCleanup] public void Cleanup() { + Console.WriteLine("******* GLOBAL CLEANUP *******"); _testRecyclableArrayList?.Dispose(); - _testRecyclableArrayList = default; _testRecyclableList?.Dispose(); - _testRecyclableList = default; TestObjectsAsRecyclableArrayList?.Dispose(); _testObjectsAsRecyclableArrayList = default; - _testArray = default; _testObjectsAsList = default; _testObjects = default; } @@ -39,14 +38,14 @@ namespace Recyclable.Collections.Benchmarks 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; @@ -58,7 +57,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; From 0c3847a9bf5eddf411cf913d4a16a0a173f61d7c Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 16 Jan 2023 22:58:12 +0100 Subject: [PATCH 022/305] Fixing startup issue --- Recyclable.Collections.Benchmarks/BenchmarkBase.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index ac7e1cf..e2a824b 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -27,11 +27,6 @@ namespace Recyclable.Collections.Benchmarks { _ = obj; } - - public BenchmarkBase() - { - _ = TestObjects; - } } //[MemoryDiagnoser] From 6ef79d22031c6fe6a298b34c99ac73e76a3da463 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 21 Jan 2023 18:50:21 +0100 Subject: [PATCH 023/305] Solutions from RecyclableArreyList were copied to RecylableList. Initial version. --- README.md | 33 +- .../BenchmarkBase.cs | 4 +- Recyclable.Collections.Benchmarks/Program.cs | 6 +- .../RecyclableCollectionsBenchmarks.List.cs | 6 +- ...llectionsBenchmarks.RecyclableArrayList.cs | 4 +- ...bleCollectionsBenchmarks.RecyclableList.cs | 17 +- Recyclable.Collections/RecyclableArrayList.cs | 73 +-- Recyclable.Collections/RecyclableList.cs | 451 +++++++++++++----- .../RecyclableListExtensions.cs | 84 ++-- Recyclable.Collections/RecyclableStack.cs | 2 +- .../RecyclableListTests.cs | 2 +- 11 files changed, 453 insertions(+), 229 deletions(-) diff --git a/README.md b/README.md index 9a9705b..973068b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,39 @@ # Recyclable.Collections `Recyclable.Collections` project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements `IList`'s interface and is targeted as direct replacements of `List`, `SortableList`, `PriorityQueue` & similar. +## Included +* `RecyclableArrayList` +* `RecyclableList` +* `RecyclableSortableList` + * Range: `long` + * Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` +* `RecyclableQueue` + * Range: `long` + * Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` +* `RecyclableStack` + * Range: `long` + * Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` + # Characteristicts of the classes -* All classes provide large storage capabilities, in practice limited only by the memory available in your system. -* All data is stored in blocks, with the provided `int blockSize` or the default, currently being `10,240` items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list. +## Common +* All classes implement `IDisposable` interface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missing `using` clause. But considering that `Dispose` will be called by `GC` anyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition of `using`. * Memory blocks are created in 2 ways, depending on `blockSize` value * when `blockSize < 100` ⇨ blocks are created as regular `arrays` * when `blockSize >= 100` ⇨ blocks are taken from and returned to `ArrayPool.Shared`, when blocks are removed -* All classes implement `IDisposable` interface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missing `using` clause. But considering that `Dispose` will be called by `GC` anyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition of `using`. +* Array pools are shared between the same `T` type. I.e. `List` will use a different pool from `List` and so on. For high concurrency environments you may want to provide your own pools, when this feature becomes available in the upcoming releases. +* Trying to access `this[int index]`, `this[long index]`, `Count`, `LongCount` etc. when `Capacity == 0` will raise `NullReferenceException`. This is by design to remove all non-critical code from the constructor. + +## `RecyclableArrayList` +* Range: `int` +* Interfaces: `IList`, `IEnumerable`, `IDisposable` +This is the direct equivalent of `List` class, except that the arrays are taken from the shared pool, instead of directly being allocated. It proves to out perform `List` & the standard arrays in certains operations and is fairly close in others. But they may perform worse in certain scenarios. If you're interested in details, please refer to benchmarks. + +## `RecyclableList`, `RecyclableSortableList`, `RecyclableQueue`, `RecyclableStack` +* Range: `long` +* Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` +* All classes provide large storage capabilities, in practice limited only by the memory available in your system. +* All data is stored in blocks, with the provided `int blockSize` or the default, currently being `10,240` items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list. * There is no memory copying to increase the capacity of the recyclable classes, anymore. If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes. * `Insert`, `Remove` & `RemoveAt` methods raise `NotSupportedException`. This is by design in the current version, because it involves performance degradadion. I'm planning works around this in the upcoming weeks. If you need that sooner, you're welcomed to file a PR with the proposed changes. @@ -16,7 +41,7 @@ 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 some 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 & proved to perform worse. For this reason, properties & methods expected to be on the hotpath duplicate some code. +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. ## 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. diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index e2a824b..3c10498 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -4,8 +4,8 @@ namespace Recyclable.Collections.Benchmarks { public class BenchmarkBase { - [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public int TestObjectCount = 1; // = 1_000_000; + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + public int TestObjectCount = 1_000_000; public int BlockSize => TestObjectCount switch { 0 => 1, diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 52450ac..d5cb50b 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -3,12 +3,14 @@ using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); -BenchmarkRunner.Run(); +//BenchmarkRunner.Run(); //BenchmarkRunner.Run(); -//var benchmark = new RecyclableCollectionsBenchmarks(); +var benchmark = new RecyclableCollectionsBenchmarks(); +benchmark.Setup(); //benchmark.RecyclableArrayList_AddRange_WithCapacity(); //benchmark.RecyclableArrayList_Add_WithCapacity(); +benchmark.RecyclableList_Add(); //BenchmarkRunner.Run(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 4f62236..8df675e 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -4,14 +4,14 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = true)] public void List_Create() { var list = new List(); DoNothing(list); } - //[Benchmark] + [Benchmark] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 2640941..dfd26e8 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -31,14 +31,14 @@ namespace Recyclable.Collections.Benchmarks _testObjects = default; } - //[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); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 89f305b..37e8da2 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -6,28 +6,21 @@ namespace Recyclable.Collections.Benchmarks { private RecyclableList _testRecyclableList; - //[Benchmark] + [Benchmark] public void RecyclableList_Create() { using var list = new RecyclableList(); DoNothing(list); } - //[Benchmark] - public void RecyclableList_Create_0Capacity() - { - using var list = new RecyclableList(totalItemsCount: 0); - DoNothing(list); - } - - //[Benchmark] + [Benchmark] public void RecyclableList_Create_WithCapacity() { - using var list = new RecyclableList(totalItemsCount: TestObjectCount); + using var list = new RecyclableList(initialCapacity: TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark] public void RecyclableList_Add() { var data = TestObjects; @@ -39,7 +32,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 8294b0a..b811fbd 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; - protected T[] _memory; + protected T[] _memoryBlock; private static void ThrowArgumentOutOfRangeException() { @@ -21,33 +21,33 @@ namespace Recyclable.Collections { ArrayPool arrayPool = _arrayPool; var sourceLength = source?.Length ?? 0; - T[] newArray = newSize.RentArrayFromPool(arrayPool); + T[] newMemoryBlock = newSize.RentArrayFromPool(arrayPool); switch (newSize >= sourceLength) { case true: if (sourceLength > 0) { Memory sourceMemory = new(source); - Memory newArrayMemory = new(newArray); + Memory newArrayMemory = new(newMemoryBlock); sourceMemory.CopyTo(newArrayMemory); source!.ReturnToPool(arrayPool); } - return newArray; + return newMemoryBlock; case false: var sourceSpan = new Span(source)[..newSize]; - Span newArraySpan = new(newArray); + Span newArraySpan = new(newMemoryBlock); sourceSpan.CopyTo(newArraySpan); source!.ReturnToPool(arrayPool); - return newArray; + return newMemoryBlock; } } protected int EnsureCapacity(in int requestedCapacity) { int oldCapacity = _capacity; - ref T[] memory = ref _memory; + ref T[] memory = ref _memoryBlock; int newCapacity; switch (oldCapacity > 0) @@ -84,8 +84,8 @@ namespace Recyclable.Collections { if (initialCapacity > 0) { - _memory = SetNewLength(_memory, initialCapacity); - _capacity = _memory.Length; + _memoryBlock = SetNewLength(_memoryBlock, initialCapacity); + _capacity = _memoryBlock.Length; } } @@ -119,15 +119,15 @@ namespace Recyclable.Collections public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { - _memory = SetNewLength(_memory, initialCapacity); - _capacity = _memory.Length; + _memoryBlock = SetNewLength(_memoryBlock, initialCapacity); + _capacity = _memoryBlock.Length; AddRange(source); } public T this[int index] { - get => _memory[index]; - set => new Span(_memory)[index] = value; + get => _memoryBlock[index]; + set => new Span(_memoryBlock)[index] = value; } protected int _capacity; @@ -138,8 +138,8 @@ namespace Recyclable.Collections { if (_capacity != value) { - _memory = SetNewLength(_memory, value); - _capacity = _memory.Length; + _memoryBlock = SetNewLength(_memoryBlock, value); + _capacity = _memoryBlock.Length; } } } @@ -156,13 +156,13 @@ namespace Recyclable.Collections //[MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { - int requestedCapacity = _count + 1; - if (_capacity < requestedCapacity) + int requiredCapacity = _count + 1; + if (_capacity < requiredCapacity) { - _ = EnsureCapacity(requestedCapacity); + _ = EnsureCapacity(requiredCapacity); } - _memory[_count++] = item; + _memoryBlock[_count++] = item; } public void AddRange(in T[] items) @@ -174,7 +174,7 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - var targetSpan = new Span(_memory)[_count..]; + var targetSpan = new Span(_memoryBlock)[_count..]; Span itemsSpan = new(items); itemsSpan.CopyTo(targetSpan); _count = targetCapacity; @@ -189,7 +189,7 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - items.CopyTo(_memory, _count); + items.CopyTo(_memoryBlock, _count); _count = targetCapacity; } @@ -202,7 +202,7 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - items.CopyTo(_memory, _count); + items.CopyTo(_memoryBlock, _count); _count = targetCapacity; } @@ -215,8 +215,8 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - var targetSpan = new Span(_memory)[_count..]; - Span itemsSpan = new(items._memory); + var targetSpan = new Span(_memoryBlock)[_count..]; + Span itemsSpan = new(items._memoryBlock); itemsSpan.CopyTo(targetSpan); _count = targetCapacity; } @@ -253,7 +253,7 @@ namespace Recyclable.Collections if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { _ = EnsureCapacity(targetIndex + requiredAdditionalCapacity); - memorySpan = new(_memory); + memorySpan = new(_memoryBlock); foreach (var item in source) { memorySpan[targetIndex++] = item; @@ -266,7 +266,7 @@ namespace Recyclable.Collections int i; using var enumerator = source.GetEnumerator(); - memorySpan = new(_memory); + memorySpan = new(_memoryBlock); if (enumerator.MoveNext()) { int available = capacity - targetIndex; @@ -275,7 +275,7 @@ namespace Recyclable.Collections if (targetIndex + growByCount > capacity) { capacity = EnsureCapacity(capacity + growByCount); - memorySpan = new(_memory); + memorySpan = new(_memoryBlock); available = capacity - targetIndex; } @@ -303,14 +303,14 @@ namespace Recyclable.Collections _count = 0; } - public bool Contains(T item) => _memory.Contains(item); + public bool Contains(T item) => _memoryBlock.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => _memory.CopyTo(array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => _memoryBlock.CopyTo(array, arrayIndex); protected static IEnumerable Enumerate(RecyclableArrayList list) { int count = list._count; - var memory = list._memory; + var memory = list._memoryBlock; for (var i = 0; i < count; i++) { yield return memory[i]; @@ -319,7 +319,7 @@ namespace Recyclable.Collections public int IndexOf(T itemToFind) { - Span memorySpan = new(_memory); + Span memorySpan = new(_memoryBlock); int itemCount = _count; var equalityComparer = _equalityComparer; for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) @@ -345,12 +345,12 @@ namespace Recyclable.Collections if (oldCount > 0) { - var sourceSpan = new Span(_memory)[index..oldCount]; - var targetSpan = new Span(_memory)[(index + 1)..]; + var sourceSpan = new Span(_memoryBlock)[index..oldCount]; + var targetSpan = new Span(_memoryBlock)[(index + 1)..]; sourceSpan.CopyTo(targetSpan); } - _memory[index] = item; + _memoryBlock[index] = item; _count++; } @@ -365,6 +365,7 @@ namespace Recyclable.Collections return false; } + public void RemoveAt(int index) { int oldCount = _count; @@ -381,7 +382,7 @@ namespace Recyclable.Collections return; } - _memory.CopyItems(index, oldCountMinus1, ref _memory); + _memoryBlock.CopyItems(index, oldCountMinus1, ref _memoryBlock); _count--; } @@ -394,7 +395,7 @@ namespace Recyclable.Collections { Clear(); _capacity = 0; - _memory.ReturnToPool(_arrayPool); + _memoryBlock.ReturnToPool(_arrayPool); GC.SuppressFinalize(this); } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 45e6182..5263a80 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,20 +1,279 @@ -using System.Buffers; +using System; +using System.Buffers; using System.Collections; +using System.Runtime.CompilerServices; 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 T[][] _emptyBlockArray = new T[0][]; private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; - private const int _minPooledArraySize = 100; private readonly int _blockSize; - private readonly RecyclableArrayList _memoryBlocks; + protected T[][] _memoryBlocks; - private static void AddCapacity(RecyclableList list, RecyclableArrayList memoryBlocks, int additionalCapacity) + private long _capacity; + public long Capacity { - T[] newMemoryBlock = RentArrayFromPool(additionalCapacity); - memoryBlocks.Add(newMemoryBlock); - list.Capacity += additionalCapacity; + get => _capacity; + protected set => _capacity = value; + } + + public int Count => (int)LongCount; + + protected long _longCount; + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public bool IsReadOnly { get; } = false; + public int BlockCount => _memoryBlocks.Length; + + private static void RemoveAt(RecyclableList list, long index) + { + long oldCount = list._longCount; + long oldCountMinus1 = oldCount - 1; + if (index != oldCountMinus1) + { + ThrowArgumentOutOfRangeException(); + } + + list._longCount--; + if (list._capacity - oldCountMinus1 == list._blockSize) + { + list.RemoveBlock(list._memoryBlocks.Length - 1); + } + } + + private static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException("index"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static T[][] SetNewLength(in T[][]? source, in int blockSize, in long newSize) + { + ArrayPool arrayPool = _arrayPool; + ArrayPool blockArrayPool = _blockArrayPool; + var sourceBlockCount = source?.Length ?? 0; + int requiredBlockCount = (int)(newSize / blockSize) + (newSize % blockSize > 0 ? 1 : 0); + Span newArraySpan; + T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool); + switch (requiredBlockCount >= sourceBlockCount) + { + case true: + if (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); + } + + for (int i = 0; i < newArraySpan.Length; i++) + { + newArraySpan[i] = blockSize.RentArrayFromPool(blockArrayPool); + } + + return newMemoryBlocks; + + case false: + var sourceSpan = new Span(source)[..requiredBlockCount]; + newArraySpan = new Span(newMemoryBlocks); + sourceSpan.CopyTo(newArraySpan); + sourceSpan = new Span(source)[newArraySpan.Length..]; + for (int i = 0; i < sourceSpan.Length; i++) + { + sourceSpan[i].ReturnToPool(_blockArrayPool); + } + + source!.ReturnToPool(arrayPool); + return newMemoryBlocks; + } + } + + protected long EnsureCapacity(in long requestedCapacity) + { + long oldCapacity = _capacity; + ref T[][] memory = ref _memoryBlocks; + + long newCapacity; + switch (oldCapacity > 0) + { + case true: + newCapacity = oldCapacity; + while (newCapacity < requestedCapacity) + { + newCapacity *= 2; + } + + break; + + case false: + newCapacity = requestedCapacity; + break; + } + + memory = SetNewLength(memory, _blockSize, newCapacity); + newCapacity = memory.Length; + _capacity = newCapacity; + 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) + { + _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, initialCapacity.Value); + _capacity = _memoryBlocks.Length * blockSize; + } + else + { + _memoryBlocks = _emptyBlockArray; + } + } + +#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) + { + _memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value); + _capacity = _memoryBlocks.Length * blockSize; + } + else + { + _memoryBlocks = _emptyBlockArray; + } + + AddRange(this, source); + } + + public T this[long index] + { + get => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize]; + set => new Span(_memoryBlocks[(int)(index / _blockSize)])[(int)index % _blockSize] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index / _blockSize][index % _blockSize]; + set => new Span(_memoryBlocks[index / _blockSize])[index % _blockSize] = value; + } + + public void Add(T item) + { + int blockSize = _blockSize; + long oldCount = _longCount; + long requiredCapacity = oldCount + 1; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(requiredCapacity); + } + + _memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item; + _longCount++; + } + + public void AddRange(in T[] items) + { + long sourceItemsCount = items.LongLength; + long oldLongCount = _longCount; + long targetCapacity = oldLongCount + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + 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++) + { + targetBlockSpan[targetItemIdx++] = items[i]; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + targetItemIdx = 0; + } + } + + _longCount = targetCapacity; + } + + public void AddRange(in List items) + { + long sourceItemsCount = items.Count; + long oldLongCount = _longCount; + long targetCapacity = oldLongCount + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + 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 (int i = 0; i < sourceItemsCount; i++) + { + targetBlockSpan[targetItemIdx++] = items[i]; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + targetItemIdx = 0; + } + } + + _longCount = targetCapacity; + } + + public void AddRange(in IList items) + { + long sourceItemsCount = items.Count; + long oldLongCount = _longCount; + long targetCapacity = oldLongCount + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + 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 (int i = 0; i < sourceItemsCount; i++) + { + targetBlockSpan[targetItemIdx++] = items[i]; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + targetItemIdx = 0; + } + } + + _longCount = targetCapacity; } private static void AddRange(RecyclableList destination, IEnumerable source) @@ -25,125 +284,16 @@ namespace Recyclable.Collections } } - private static void RemoveAt(RecyclableList list, long index) - { - if (index == list.LongCount - 1) - { - list.LongCount--; - if (list.Capacity - list.LongCount == list._blockSize) - { - list.RemoveBlock(list.BlockCount - 1); - } - } - else - { - throw new NotSupportedException("Only removal of the last element is supported"); - } - } - - private static T[] RentArrayFromPool(int minSize) => (minSize >= _minPooledArraySize) - ? ArrayPool.Shared.Rent(minSize) - : new T[minSize]; - - private static void ReturnToPool(in T[] array) - { - if (array.LongLength is < _minPooledArraySize or > int.MaxValue) - { - return; - } - - ArrayPool.Shared.Return(array); - } - - private static void RemoveBlock(RecyclableList owner, RecyclableArrayList memoryBlocks, int blockSize, int index) - { - try - { - ReturnToPool(memoryBlocks[index]); - memoryBlocks.RemoveAt(index); - } - finally - { - owner.Capacity -= blockSize; - } - } - - 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 RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? totalItemsCount = default) - { - _blockSize = blockSize; - _memoryBlocks = SetupMemoryBlocks(this, blockSize, totalItemsCount); - } - - public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? totalItemsCount = default) - { - _blockSize = blockSize; - _memoryBlocks = SetupMemoryBlocks(this, blockSize, totalItemsCount); - AddRange(this, source); - } - - public T this[long index] - { - get => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize]; - set => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize] = value; - } - - public T this[int index] - { - get => _memoryBlocks[index / _blockSize][index % _blockSize]; - set => _memoryBlocks[index / _blockSize][index % _blockSize] = value; - } - - public long Capacity { get; protected set; } - public int Count => (int)LongCount; - public long LongCount { get; set; } - public bool IsReadOnly { get; } = false; - public int BlockCount => _memoryBlocks.Count; - - public void Add(T item) - { - long oldCount = LongCount; - int blockSize = _blockSize; - if (oldCount == Capacity) - { - AddCapacity(this, _memoryBlocks, blockSize); - } - - _memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item; - LongCount++; - } - public void Clear() { try { // Remove in reversed order for performance savings - while (_memoryBlocks.Count > 0) + while (_memoryBlocks.Length > 0) { try { - RemoveBlock(this, _memoryBlocks, _blockSize, _memoryBlocks.Count - 1); + _memoryBlocks = RemoveBlock(this, _memoryBlocks, _blockSize, _memoryBlocks.Length - 1); } catch (Exception) { @@ -154,36 +304,77 @@ namespace Recyclable.Collections } finally { - LongCount = 0; - _memoryBlocks.Clear(); + _longCount = 0; } } - public bool Contains(T item) => _memoryBlocks.Contains(item); + 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 IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer); - + public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(_longCount, item, _equalityComparer); 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) => _memoryBlocks = RemoveBlock(this, _memoryBlocks, _blockSize, index); public void RemoveAt(int index) => RemoveAt(this, index); public void RemoveAt(long index) => RemoveAt(this, index); IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - public void Dispose() + //private static void AddCapacity(RecyclableList list, RecyclableArrayList memoryBlocks, int additionalCapacity) + //{ + // T[] newMemoryBlock = RentArrayFromPool(additionalCapacity); + // memoryBlocks.Add(newMemoryBlock); + // list.Capacity += additionalCapacity; + //} + + private static T[][] RemoveBlock(RecyclableList owner, in T[][] memoryBlocks, int blockSize, int blockIndex) { - Clear(); - GC.SuppressFinalize(this); + try + { + var memoryBlocksSpan = new Span(memoryBlocks); + return SetNewLength(memoryBlocks, blockSize, memoryBlocks.Length - 1); + } + finally + { + owner._capacity -= blockSize; + } } - public void RemoveBlock(int index) => RemoveBlock(this, _memoryBlocks, _blockSize, index); + //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) + { + Clear(); + _capacity = 0; + _memoryBlocks.ReturnToPool(_arrayPool); + GC.SuppressFinalize(this); + } + } } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 9dc4d46..2799c22 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -15,17 +15,17 @@ namespace Recyclable.Collections } } - public static void CopyTo(this RecyclableList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + public static void CopyTo(this T[][] arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) { Span arrayMemory = destinationArray.AsSpan(); arrayMemory = arrayMemory[destinationArrayIndex..]; int startingArrayIndex = (int)(startingIndex / blockSize); startingIndex %= blockSize; - if (arrays.LongCount > 0) + if (arrays.LongLength > 0) { ReadOnlySpan sourceMemory = arrays[0].AsSpan(); var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); - if (arrays.LongCount == 1) + if (arrays.LongLength == 1) { maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); } @@ -35,7 +35,7 @@ namespace Recyclable.Collections arrayMemory = arrayMemory[maxToCopy..]; } - for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Count - 1 && arrayMemory.Length > 0; arrayIdx++) + for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Length - 1 && arrayMemory.Length > 0; arrayIdx++) { ReadOnlySpan sourceMemory = arrays[arrayIdx].AsSpan(); var maxToCopy = Math.Min(blockSize, arrayMemory.Length); @@ -44,7 +44,7 @@ namespace Recyclable.Collections arrayMemory = arrayMemory[maxToCopy..]; } - if (arrays.Count > 1 && arrayMemory.Length > 0) + if (arrays.Length > 1 && arrayMemory.Length > 0) { ReadOnlySpan sourceMemory = arrays[^1].AsSpan(); var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length); @@ -56,35 +56,35 @@ namespace Recyclable.Collections //[MethodImpl(MethodImplOptions.AggressiveInlining)] //public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); - //public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) - //{ - // long currentCount = 0; - // for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) - // { - // var array = arrays[arrayIdx]; - // currentCount += chunkSize; - // switch (currentCount < totalCount) - // { - // case true: - // for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) - // { - // yield return array[valueIdx]; - // } + public static IEnumerable Enumerate(this T[][] arrays, int chunkSize, long totalCount) + { + long currentCount = 0; + for (var arrayIdx = 0; arrayIdx < arrays.Length && currentCount < totalCount; arrayIdx++) + { + var array = arrays[arrayIdx]; + currentCount += chunkSize; + switch (currentCount < totalCount) + { + case true: + for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) + { + yield return array[valueIdx]; + } - // break; + break; - // case false: - // var partialCount = (int)(totalCount % chunkSize); - // int maxCount = partialCount > 0 ? partialCount : chunkSize; - // for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) - // { - // yield return array[valueIdx]; - // } + case false: + var partialCount = (int)(totalCount % chunkSize); + int maxCount = partialCount > 0 ? partialCount : chunkSize; + for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) + { + yield return array[valueIdx]; + } - // break; - // } - // } - //} + break; + } + } + } //[MethodImpl(MethodImplOptions.AggressiveInlining)] //public static T LimitTo(this T value, T maxValue, IComparer comparer) @@ -107,14 +107,26 @@ namespace Recyclable.Collections return -1; } - public static long LongIndexOf(this RecyclableArrayList arrays, int itemsCount, T itemToFind, IEqualityComparer comparer) + public static long LongIndexOf(this T[][] arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) { - for (var itemIdx = 0; itemIdx < itemsCount; itemIdx++) + var arraysSpan = new Span(arrays); + var arraysCount = arraysSpan.Length; + for (int arrayIdx = 0, scannedCount = 0; arrayIdx < arraysCount; arrayIdx++) { - var item = arrays[itemIdx]; - if (comparer.Equals(itemToFind, item)) + var itemsSpan = new Span(arraysSpan[arrayIdx]); + var arrayItemsCount = itemsSpan.Length; + for (var itemIdx = 0; itemIdx < arrayItemsCount; itemIdx++) { - return itemIdx; + if (comparer.Equals(itemToFind, itemsSpan[itemIdx])) + { + return scannedCount; + } + + scannedCount++; + if (scannedCount == itemsCount) + { + return -1; + } } } diff --git a/Recyclable.Collections/RecyclableStack.cs b/Recyclable.Collections/RecyclableStack.cs index c8af95c..e103f4a 100644 --- a/Recyclable.Collections/RecyclableStack.cs +++ b/Recyclable.Collections/RecyclableStack.cs @@ -47,7 +47,7 @@ namespace Recyclable.Collections { var toRemove = List[LongCount - 1]; LongCount--; - if (List.Capacity - LongCount == _blockSize) + if (List.Capacity * _blockSize - LongCount == _blockSize) { List.RemoveBlock(List.BlockCount - 1); } diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 43bef10..7cff822 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -156,7 +156,7 @@ namespace Recyclable.CollectionsTests using var list = NewRecyclableList; // Act - _ = Assert.Throws(() => list.RemoveAt(0)); + _ = Assert.Throws(() => list.RemoveAt(0)); } [Fact] From b3380b1f35bef481e1b0ec4ae2a8765782355bcd Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 21 Jan 2023 20:02:00 +0100 Subject: [PATCH 024/305] Small cleanup. RecyclableCollectionsBenchmarks properties & fields moved to RecyclableCollectionsBenchmark.cs file. --- Recyclable.Collections.Benchmarks/Program.cs | 2 +- .../RecyclableCollectionsBenchmarks.Array.cs | 4 +- .../RecyclableCollectionsBenchmarks.List.cs | 8 ++-- ...llectionsBenchmarks.RecyclableArrayList.cs | 27 ------------- ...bleCollectionsBenchmarks.RecyclableList.cs | 2 - .../RecyclableCollectionsBenchmarks.cs | 40 +++++++++++++++++++ .../RefVsInstanceMemberBenchmarks.cs | 6 --- 7 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index d5cb50b..a25e11f 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -10,7 +10,7 @@ var benchmark = new RecyclableCollectionsBenchmarks(); benchmark.Setup(); //benchmark.RecyclableArrayList_AddRange_WithCapacity(); //benchmark.RecyclableArrayList_Add_WithCapacity(); -benchmark.RecyclableList_Add(); +benchmark.RecyclableList_Add_WithCapacity(); //BenchmarkRunner.Run(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index 3ebe96d..2615e22 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -5,9 +5,7 @@ namespace Recyclable.Collections.Benchmarks [MemoryDiagnoser] public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private object[] _testArray; - - //[Benchmark(Baseline = true)] + //[Benchmark(Baseline = false)] public void Array_Create_WithCapacity() { var data = new object[TestObjectCount]; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 8df675e..2996574 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Create() { var list = new List(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index dfd26e8..49945f1 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -4,33 +4,6 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private RecyclableArrayList _testRecyclableArrayList; - - [GlobalSetup] - public void Setup() - { - Console.WriteLine("******* GLOBAL SETUP RAISED *******"); - _testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); - _testRecyclableArrayList = new(TestObjects, TestObjectCount); - _testRecyclableList = new(TestObjects, TestObjectCount); - _testArray = TestObjects.ToArray(); - _testObjectsAsList = TestObjects.ToList(); - _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); - } - - - [GlobalCleanup] - public void Cleanup() - { - Console.WriteLine("******* GLOBAL CLEANUP *******"); - _testRecyclableArrayList?.Dispose(); - _testRecyclableList?.Dispose(); - TestObjectsAsRecyclableArrayList?.Dispose(); - _testObjectsAsRecyclableArrayList = default; - _testObjectsAsList = default; - _testObjects = default; - } - [Benchmark] public void RecyclableArrayList_Create() { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 37e8da2..e253cd5 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -4,8 +4,6 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private RecyclableList _testRecyclableList; - [Benchmark] public void RecyclableList_Create() { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs new file mode 100644 index 0000000..9ab8cd9 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs @@ -0,0 +1,40 @@ +using BenchmarkDotNet.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + private object[] _testArray; + private RecyclableArrayList _testRecyclableArrayList; + private RecyclableList _testRecyclableList; + + [GlobalSetup] + public void Setup() + { + Console.WriteLine("******* GLOBAL SETUP RAISED *******"); + _testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); + _testRecyclableArrayList = new(TestObjects, TestObjectCount); + _testRecyclableList = new(TestObjects, TestObjectCount); + _testArray = TestObjects.ToArray(); + _testObjectsAsList = TestObjects.ToList(); + _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); + } + + [GlobalCleanup] + public void Cleanup() + { + Console.WriteLine("******* GLOBAL CLEANUP *******"); + _testRecyclableArrayList?.Dispose(); + _testRecyclableList?.Dispose(); + TestObjectsAsRecyclableArrayList?.Dispose(); + _testObjectsAsRecyclableArrayList = default; + _testObjectsAsList = default; + _testObjects = default; + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs index 2851a58..cd21368 100644 --- a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs @@ -1,10 +1,4 @@ using BenchmarkDotNet.Attributes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Metadata.Ecma335; -using System.Text; -using System.Threading.Tasks; namespace Recyclable.Collections.Benchmarks { From 576ac9ccfacd4ca44a085a44f2c8d08216d44246 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 22 Jan 2023 01:06:21 +0100 Subject: [PATCH 025/305] RecyclableList.RemoveBlock renamed to ReleaseBlock. RecyclableList.Clear will no longer create new arrays. It will only release the array to the pool & decrease capacity. Fixed possibly infinite loop in Clear. --- Recyclable.Collections/RecyclableList.cs | 37 ++++++++++++----------- Recyclable.Collections/RecyclableStack.cs | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 5263a80..7d098d3 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -43,9 +43,11 @@ namespace Recyclable.Collections } list._longCount--; - if (list._capacity - oldCountMinus1 == list._blockSize) + int blockSize = list._blockSize; + if ((list._capacity * blockSize) - oldCountMinus1 == blockSize) { - list.RemoveBlock(list._memoryBlocks.Length - 1); + T[][] memoryBlocks = list._memoryBlocks; + ReleaseBlock(list, memoryBlocks, blockSize, memoryBlocks.Length - 1); } } @@ -61,6 +63,11 @@ namespace Recyclable.Collections ArrayPool blockArrayPool = _blockArrayPool; var sourceBlockCount = source?.Length ?? 0; int requiredBlockCount = (int)(newSize / blockSize) + (newSize % blockSize > 0 ? 1 : 0); + if (requiredBlockCount == sourceBlockCount) + { + return source!; + } + Span newArraySpan; T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool); switch (requiredBlockCount >= sourceBlockCount) @@ -123,8 +130,9 @@ namespace Recyclable.Collections break; } - memory = SetNewLength(memory, _blockSize, newCapacity); - newCapacity = memory.Length; + int blockSize = _blockSize; + memory = SetNewLength(memory, blockSize, newCapacity); + newCapacity = memory.Length * blockSize; _capacity = newCapacity; return newCapacity; } @@ -289,17 +297,20 @@ namespace Recyclable.Collections try { // Remove in reversed order for performance savings - while (_memoryBlocks.Length > 0) + var toRemoveIdx = _memoryBlocks.Length - 1; + while (toRemoveIdx >= 0) { try { - _memoryBlocks = RemoveBlock(this, _memoryBlocks, _blockSize, _memoryBlocks.Length - 1); + ReleaseBlock(this, _memoryBlocks, _blockSize, toRemoveIdx); } catch (Exception) { // We want to try returning as many arrays, as possible, before // the list is cleared. } + + toRemoveIdx--; } } finally @@ -318,25 +329,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) => _memoryBlocks = RemoveBlock(this, _memoryBlocks, _blockSize, index); + public void RemoveBlock(int index) => ReleaseBlock(this, _memoryBlocks, _blockSize, index); 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 AddCapacity(RecyclableList list, RecyclableArrayList memoryBlocks, int additionalCapacity) - //{ - // T[] newMemoryBlock = RentArrayFromPool(additionalCapacity); - // memoryBlocks.Add(newMemoryBlock); - // list.Capacity += additionalCapacity; - //} - - private static T[][] RemoveBlock(RecyclableList owner, in T[][] memoryBlocks, int blockSize, int blockIndex) + private static void ReleaseBlock(RecyclableList owner, in T[][] memoryBlocks, int blockSize, int blockIndex) { try { - var memoryBlocksSpan = new Span(memoryBlocks); - return SetNewLength(memoryBlocks, blockSize, memoryBlocks.Length - 1); + memoryBlocks[blockIndex].ReturnToPool(_blockArrayPool); } finally { diff --git a/Recyclable.Collections/RecyclableStack.cs b/Recyclable.Collections/RecyclableStack.cs index e103f4a..c915341 100644 --- a/Recyclable.Collections/RecyclableStack.cs +++ b/Recyclable.Collections/RecyclableStack.cs @@ -47,7 +47,7 @@ namespace Recyclable.Collections { var toRemove = List[LongCount - 1]; LongCount--; - if (List.Capacity * _blockSize - LongCount == _blockSize) + if ((List.Capacity * _blockSize) - LongCount == _blockSize) { List.RemoveBlock(List.BlockCount - 1); } From 7c78b6f87ed4191175a5cf0db3dda696b30b83bc Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 22 Jan 2023 23:13:00 +0100 Subject: [PATCH 026/305] 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 027/305] 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 028/305] 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 029/305] 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 030/305] 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 031/305] 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 032/305] 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 033/305] 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 034/305] 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 035/305] 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 036/305] _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 037/305] 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 038/305] 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 039/305] 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 040/305] `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 041/305] 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 042/305] 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 043/305] 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 044/305] 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 045/305] 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 046/305] 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 047/305] 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 048/305] 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 049/305] 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 050/305] 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 051/305] 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 052/305] 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 053/305] 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 054/305] 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 055/305] 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 056/305] 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 057/305] 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 058/305] 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 059/305] 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 060/305] 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 061/305] 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 062/305] 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 063/305] 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 064/305] 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 065/305] 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 066/305] 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 067/305] 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 068/305] 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 069/305] 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 070/305] 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 071/305] 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 072/305] 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 073/305] 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 074/305] 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 075/305] 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 076/305] 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 077/305] 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 078/305] 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 079/305] 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 080/305] 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 081/305] 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 082/305] 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) { From 97c0b87c3189dad35b31c32a73679d3d12fdb1a7 Mon Sep 17 00:00:00 2001 From: Memek Date: Mon, 6 Feb 2023 15:28:25 +0100 Subject: [PATCH 083/305] Added new tests to RecyclableArrayListTest for Clear and IndexOf. --- .../RecyclableArrayListTests.cs | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 66ff6a6..1ba81a2 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -6,7 +6,7 @@ namespace Recyclable.CollectionsTests public class RecyclableArrayListTests { private readonly static IEnumerable _testData = Enumerable.Range(1, 20); - + [Fact] public void AddCountShouldSucceed() { @@ -37,7 +37,7 @@ namespace Recyclable.CollectionsTests // Act list.AddRange(testData); - + // Validate _ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only"); _ = list.Should().HaveCount(testData.Length) @@ -254,5 +254,36 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(testData) .And.BeEquivalentTo(testData); } + [Fact] + public void ClearShouldRemoveAllItems() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableArrayList(testData); + _ = list.Count.Should().NotBe(0); + + // Act + list.Clear(); + + // Validate + _ = list.Count.Should().Be(0); + _ = list.Should().HaveCount(0); + + } + + [Fact] + public void IndexOfShouldFindTheIndexes() + { + // Prepare + var testData = _testData.ToArray(); + using var list = new RecyclableArrayList(testData); + + // Act & Validate + foreach (var index in _testData.ToArray()) + { + var actual = list.IndexOf(index); + _ = actual.Should().Be(index - 1); + } + } } -} +} \ No newline at end of file From 23e92b0521974f4cd202fdffaca83746efe3b422 Mon Sep 17 00:00:00 2001 From: Memek Date: Mon, 6 Feb 2023 18:02:50 +0100 Subject: [PATCH 084/305] Tests for RecyclableArrayList.Contains and RecyclableList.Clear added. --- .../RecyclableArrayListTests.cs | 19 ++++++++++++++++++- .../RecyclableListTests.cs | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 1ba81a2..4f43224 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -17,7 +17,8 @@ namespace Recyclable.CollectionsTests // Act foreach (var item in testData) { - list.Add(item); + + _ = list.Count.Should().Be(item); } @@ -285,5 +286,21 @@ namespace Recyclable.CollectionsTests _ = actual.Should().Be(index - 1); } } + + [Fact] + public void ContainsShouldFindAllItems() + { + // Prepare + var testData = _testData.ToArray(); + using var list = new RecyclableArrayList(testData); + _ = 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(); + } + } } } \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index e89378a..1159dc8 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -137,7 +137,7 @@ namespace Recyclable.CollectionsTests { // Prepare using var list = NewRecyclableList; - + // Act var actual = new int[list.LongCount]; list.CopyTo(actual, 0); @@ -229,5 +229,19 @@ namespace Recyclable.CollectionsTests // Act list.Dispose(); } + + [Fact] + public void ClearShouldRemoveAllItems() + { + // Prepare + using var list = NewRecyclableList; + _ = list.LongCount.Should().NotBe(0); + + // Act + list.Clear(); + + // Validate + _ = list.LongCount.Should().Be(0); + } } } \ No newline at end of file From 37b01147791499dbe34f1d58034de9dbfcf86c22 Mon Sep 17 00:00:00 2001 From: Memek Date: Mon, 6 Feb 2023 21:11:21 +0100 Subject: [PATCH 085/305] Test RecyclabeArrayList.AddShouldAcceptDuplicates added. --- .../RecyclableArrayListTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 4f43224..2bcad4a 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -302,5 +302,23 @@ namespace Recyclable.CollectionsTests _ = list.Contains(item).Should().BeTrue(); } } + + [Fact] + public void AddShouldAcceptDuplicates() + { + // Prepare + int[] testNumbers = new[] { 1, 2, 2, 3 }; + + // Act + using var list = new RecyclableArrayList(); + foreach (var index in testNumbers) + { + list.Add(index); + } + + // Validate + _ = list.Count.Should().Be(testNumbers.Length); + _ = list.Should().BeEquivalentTo(testNumbers); + } } } \ No newline at end of file From 1eefa2d84fe41fa4e355c934e71c7a2dccbb0c5a Mon Sep 17 00:00:00 2001 From: Memek Date: Tue, 7 Feb 2023 17:01:50 +0100 Subject: [PATCH 086/305] AddCountShouldSuceed test fixed. --- Recyclable.CollectionsTests/RecyclableArrayListTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 2bcad4a..34af720 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -17,8 +17,7 @@ namespace Recyclable.CollectionsTests // Act foreach (var item in testData) { - - + list.Add(item); _ = list.Count.Should().Be(item); } From 32d915087f7334718f4bc61ffc2908278fa1741f Mon Sep 17 00:00:00 2001 From: Memek Date: Tue, 7 Feb 2023 17:08:01 +0100 Subject: [PATCH 087/305] Test RecyclableList.AddRangeShouldAddItemsInCorrectOrderWhenSourceIsArray and AddRangeShouldAddItemsInCorrectOrderWhenSourceIsList added. --- .../RecyclableListTests.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 1159dc8..56e79c7 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -243,5 +243,40 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(0); } + + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsArray() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableList(); + + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().Be(16384,"when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsList() + { + // Prepare + var testData = _testData.ToList(); + var list = new RecyclableList(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().Be(16384, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Count) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } } -} \ No newline at end of file +} From a37f572518179544afe7c8dbd1d56a0dfb4f0398 Mon Sep 17 00:00:00 2001 From: Memek Date: Tue, 7 Feb 2023 19:42:00 +0100 Subject: [PATCH 088/305] RecyclableList.InsertAt test added. --- Recyclable.CollectionsTests/RecyclableListTests.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 56e79c7..9416e5d 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -256,7 +256,7 @@ namespace Recyclable.CollectionsTests list.AddRange(testData); // Validate - _ = list.Capacity.Should().Be(16384,"when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Capacity.Should().Be(16384, "when capacity == 0, then we allocate as much memory as needed, only"); _ = list.Should().HaveCount(testData.Length) .And.ContainInConsecutiveOrder(testData) .And.BeEquivalentTo(testData); @@ -278,5 +278,16 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(testData) .And.BeEquivalentTo(testData); } + + [Fact] + public void InsertAtTheBeginningShouldMoveItems() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableList(); + + // Act + _ = Assert.Throws(() => list.Insert(0, -1)); + } } } From 230401a56b871eefea6264649dbe7b515858718d Mon Sep 17 00:00:00 2001 From: Memek Date: Wed, 8 Feb 2023 14:46:47 +0100 Subject: [PATCH 089/305] Test RecyclableList.LongIndexOfShouldReturnCorrectIndexes added. --- .../RecyclableListTests.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 9416e5d..fd85871 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -289,5 +289,24 @@ namespace Recyclable.CollectionsTests // Act _ = Assert.Throws(() => list.Insert(0, -1)); } + + [Fact] + public void LongIndexOfShouldReturnCorrectIndexes() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableList(testData); + + // Act & Validate + foreach (var item in testData) + { + // Act + var index = list.LongIndexOf(item); + + // Validate + _ = index.Should().Be(item - 1); + } + } + } -} +} \ No newline at end of file From d4a63d5d33e639e24e7789f071db17e566471fe9 Mon Sep 17 00:00:00 2001 From: Memek Date: Wed, 8 Feb 2023 15:43:42 +0100 Subject: [PATCH 090/305] Test RecyclableList.ConstructorSourceShouldInitializeList added. --- .../RecyclableListTests.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index fd85871..36ab1c0 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -169,7 +169,7 @@ namespace Recyclable.CollectionsTests using var list = NewRecyclableList; // Act & Validate - foreach(var index in _testData) + foreach (var index in _testData) { var actual = list.IndexOf(index); _ = actual.Should().Be(index - 1); @@ -308,5 +308,19 @@ namespace Recyclable.CollectionsTests } } + [Fact] + public void ConstructorSourceShouldInitializeList() + { + // Prepare + var testData = _testData.ToArray(); + + // Act + var list = new RecyclableList(testData); + + // Validate + _ = list.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } } } \ No newline at end of file From 50353aecc2fabd100b111adb01539f32b1ebbdc2 Mon Sep 17 00:00:00 2001 From: Memek Date: Wed, 8 Feb 2023 16:31:13 +0100 Subject: [PATCH 091/305] Test RecyclableArrayList.ConstructorShouldAcceptDuplicates added. --- .../RecyclableArrayListTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 34af720..ea2b392 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -319,5 +319,19 @@ namespace Recyclable.CollectionsTests _ = list.Count.Should().Be(testNumbers.Length); _ = list.Should().BeEquivalentTo(testNumbers); } + + [Fact] + public void ConstructorShouldAcceptDuplicates() + { + // Prepare + int[] testNumbers = new[] { 1, 2, 2, 3 }; + + // Act + using var list = new RecyclableArrayList(testNumbers); + + // Validate + _ = list.Count.Should().Be(testNumbers.Length); + _ = list.Should().BeEquivalentTo(testNumbers); + } } } \ No newline at end of file From 681cd179d2891f350b8d2d76ea663ee83033593e Mon Sep 17 00:00:00 2001 From: Memek Date: Wed, 8 Feb 2023 22:50:08 +0100 Subject: [PATCH 092/305] Test RecyclableArrayList.ContainsShouldNotFindNonExistingItems and ContainsShouldNotFindAnythingWhenListEmpty added. --- .../RecyclableArrayListTests.cs | 63 ++++++++++++++----- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index ea2b392..7b6f475 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -303,35 +303,66 @@ namespace Recyclable.CollectionsTests } [Fact] - public void AddShouldAcceptDuplicates() + public void ContainsShouldNotFindNonExistingItems() { // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; + var testData = _testData.ToArray(); + using var list = new RecyclableArrayList(testData); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act - using var list = new RecyclableArrayList(); - foreach (var index in testNumbers) + foreach (var item in testData) { - list.Add(index); + // Validate + _ = list.Contains(-item).Should().BeFalse(); } // Validate - _ = list.Count.Should().Be(testNumbers.Length); - _ = list.Should().BeEquivalentTo(testNumbers); + _ = list.Contains(0).Should().BeFalse(); } [Fact] - public void ConstructorShouldAcceptDuplicates() + public void ContainsShouldNotFindAnythingWhenListEmpty() { // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; - - // Act - using var list = new RecyclableArrayList(testNumbers); + var testData = _testData.ToArray(); + using var list = new RecyclableArrayList(testData); // Validate - _ = list.Count.Should().Be(testNumbers.Length); - _ = list.Should().BeEquivalentTo(testNumbers); + _ = list.Contains(0).Should().BeFalse(); + _ = list.Contains(1).Should().BeTrue(); } - } -} \ No newline at end of file + + [Fact] + public void AddShouldAcceptDuplicates() + { + // Prepare + int[] testNumbers = new[] { 1, 2, 2, 3 }; + + // Act + using var list = new RecyclableArrayList(); + foreach (var index in testNumbers) + { + list.Add(index); + } + + // Validate + _ = list.Count.Should().Be(testNumbers.Length); + _ = list.Should().BeEquivalentTo(testNumbers); + } + + [Fact] + public void ConstructorShouldAcceptDuplicates() + { + // Prepare + int[] testNumbers = new[] { 1, 2, 2, 3 }; + + // Act + using var list = new RecyclableArrayList(testNumbers); + + // Validate + _ = list.Count.Should().Be(testNumbers.Length); + _ = list.Should().BeEquivalentTo(testNumbers); + } + } + } \ No newline at end of file From 5d9526a9f9bb96ee1cf5ffa1530927e04d2f2efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 1 Mar 2023 00:22:27 +0100 Subject: [PATCH 093/305] Fixed _lastBlockIndex assignment. --- Recyclable.Collections/RecyclableList.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 5fecbe4..c26038c 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -287,7 +287,7 @@ namespace Recyclable.Collections } _longCount = targetCapacity; - _lastBlockIndex = targetBlockIdx - 1; + _lastBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; } @@ -324,7 +324,7 @@ namespace Recyclable.Collections } _longCount = targetCapacity; - _lastBlockIndex = targetBlockIdx - 1; + _lastBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; } @@ -361,7 +361,7 @@ namespace Recyclable.Collections } _longCount = targetCapacity; - _lastBlockIndex = targetBlockIdx - 1; + _lastBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; } @@ -484,7 +484,7 @@ namespace Recyclable.Collections } _longCount = targetItemIdx; - _lastBlockIndex = targetBlockIdx - 1; + _lastBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; } From 573e387a75d1b72c05c174b2ba1293eeadf27965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 1 Mar 2023 00:25:06 +0100 Subject: [PATCH 094/305] RecyclableList.Contains performance improvement +1%. Other versions kept for reference, if needed. Pending cleanup. --- Recyclable.Collections/RecyclableList.cs | 279 ++++++++++++++++++++++- 1 file changed, 272 insertions(+), 7 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index c26038c..ec7c490 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -7,6 +7,7 @@ namespace Recyclable.Collections public class RecyclableList : IDisposable, IList { private const int ItemNotFoundIndex = -1; + private const int BatchSize = 8; private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; @@ -515,20 +516,284 @@ namespace Recyclable.Collections return false; } - int itemIndex, blockIndex = 0, lastBlockIndex = _lastBlockIndex, blockSize = _blockSize; + // VERSION 2 + //using var safeToContinue = new ManualResetEventSlim(false); + //using var foundEvent = new ManualResetEventSlim(false); + //using var countdownEvent = new CountdownEvent(((_lastBlockIndex + 1) / BatchSize) + ((_lastBlockIndex + 1) % BatchSize) > 0 ? 1 : 0); + //int lastBlockIndex = _lastBlockIndex; + //T[][] memoryBlocks = _memoryBlocks; + //int batchOffset = 0; + //while (batchOffset < memoryBlocks.Length) + //{ + // var toCopy = Math.Min(BatchSize, memoryBlocks.Length - batchOffset); + // //var batch = memoryBlocks[0..toCopy].ToArray(); + // _ = Task.Run(() => + // { + // Span batchSpan = new(memoryBlocks, batchOffset, toCopy); //new(batch); + // safeToContinue.Set(); + // for (var memoryBlockIndex = 0; memoryBlockIndex < batchSpan.Length; memoryBlockIndex++) + // { + // if (Array.IndexOf(batchSpan[memoryBlockIndex], item, 0, _blockSize) >= 0) + // { + // foundEvent.Set(); + // break; + // } + // else if (foundEvent.IsSet) + // { + // return; + // } + // } + + // if (!foundEvent.IsSet) + // { + // _ = countdownEvent.Signal(); + // } + // }); + + // while (!safeToContinue.IsSet) + // { + // // Intentionally left empty + // } + + // safeToContinue.Reset(); + // if (foundEvent.IsSet) + // { + // return true; + // } + + // batchOffset += toCopy; + // //memoryBlocks = memoryBlocks[toCopy..]; + //} + + //_ = WaitHandle.WaitAny(new WaitHandle[] { foundEvent.WaitHandle, countdownEvent.WaitHandle }); + //return foundEvent.IsSet || Array.IndexOf(_memoryBlocks[_lastBlockIndex], item, 0, _nextItemIndex) >= 0; + + + + // VERSION 1 + //int blockSize = _blockSize; + //int itemIndex = 0; + //int blockIndex = 0; + //int lastBlockIndex = _lastBlockIndex; + + //Span memoryBlocksSpan = new(_memoryBlocks); + //Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); + //if (item != null) + //{ + // int midPoint = blockSize / 4; + // while (blockIndex < lastBlockIndex) + // { + // if (item.Equals(blockArraySpan[itemIndex])) + // { + // return true; + // } + + // if (item.Equals(blockArraySpan[midPoint + itemIndex])) + // { + // return true; + // } + + // if (item.Equals(blockArraySpan[(midPoint * 2) + itemIndex])) + // { + // return true; + // } + + // if (item.Equals(blockArraySpan[(midPoint * 3) + itemIndex])) + // { + // return true; + // } + + // //if (item.Equals(blockArraySpan[(midPoint * 4) + itemIndex])) + // //{ + // // return true; + // //} + + // //if (item.Equals(blockArraySpan[(midPoint * 5) + itemIndex])) + // //{ + // // return true; + // //} + + // //if (item.Equals(blockArraySpan[(midPoint * 6) + itemIndex])) + // //{ + // // return true; + // //} + + // //if (item.Equals(blockArraySpan[(midPoint * 7) + itemIndex])) + // //{ + // // return true; + // //} + + // itemIndex++; + // if (itemIndex >= midPoint) + // { + // blockIndex++; + // blockArraySpan = new(memoryBlocksSpan[blockIndex]); + // 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[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; + // } + //} + + //return false; + + // VERSION 3 Span memoryBlocksSpan = new(_memoryBlocks); - while (blockIndex < lastBlockIndex) + int lastBlockIndex = _lastBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) { - itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); - if (itemIndex >= 0) + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) { return true; } - - blockIndex++; } - return Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _nextItemIndex) >= 0; + return Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); From f38ee0d49821d268b9631936a4cb1126dccc0928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 1 Mar 2023 00:41:16 +0100 Subject: [PATCH 095/305] Contains benchmarks will now always search for 10% of the test objects count. --- .../RecyclableCollectionsBenchmarks.List.cs | 16 ++++++++-------- ...clableCollectionsBenchmarks.RecyclableList.cs | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index b6cb1a4..19b8269 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -113,11 +113,11 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark(Baseline = true)] - public void List_Contains_1_000_FirstItems() + public void List_Contains_FirstItems() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = 1_000; + var dataCount = TestObjectCount / 10; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[i])); @@ -125,11 +125,11 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void List_Contains_1_000_LastItems() + public void List_Contains_LastItems() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = 1_000; + var dataCount = TestObjectCount / 10; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[^(i + 1)])); @@ -137,11 +137,11 @@ namespace Recyclable.Collections.Benchmarks } //[Benchmark(Baseline = true)] - public void List_IndexOf_1_000_FirstItems() + public void List_IndexOf_FirstItems() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = 1_000; + var dataCount = TestObjectCount / 10; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -149,11 +149,11 @@ namespace Recyclable.Collections.Benchmarks } //[Benchmark] - public void List_IndexOf_1_000_LastItems() + public void List_IndexOf_LastItems() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = 1_000; + var dataCount = TestObjectCount / 10; 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 f8fb793..27e7d32 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -79,11 +79,11 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void RecyclableList_Contains_1_000_FirstItems() + public void RecyclableList_Contains_FirstItems() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = 1000; + var dataCount = TestObjectCount / 10; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[i])); @@ -91,11 +91,11 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void RecyclableList_Contains_1_000_LastItems() + public void RecyclableList_Contains_LastItems() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = 1_000; + var dataCount = TestObjectCount / 10; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[^(i + 1)])); @@ -103,11 +103,11 @@ namespace Recyclable.Collections.Benchmarks } //[Benchmark] - public void RecyclableList_IndexOf_1_000_FirstItems() + public void RecyclableList_IndexOf_FirstItems() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = 1000; + var dataCount = TestObjectCount / 10; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -115,11 +115,11 @@ namespace Recyclable.Collections.Benchmarks } //[Benchmark] - public void RecyclableList_IndexOf_1_000_LastItems() + public void RecyclableList_IndexOf_LastItems() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = 1_000; + var dataCount = TestObjectCount / 10; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[^(i + 1)])); From 692891cca9481ecacc061a748f934d96b8146836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 1 Mar 2023 00:42:38 +0100 Subject: [PATCH 096/305] Pass block size when creating RecyclableList. --- .../RecyclableCollectionsBenchmarks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs index 3c21495..7c4e9de 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs @@ -14,7 +14,7 @@ namespace Recyclable.Collections.Benchmarks Console.WriteLine("******* GLOBAL SETUP RAISED *******"); _testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); _testRecyclableArrayList = new(TestObjects, initialCapacity: TestObjectCount); - _testRecyclableList = new(TestObjects, expectedItemsCount: TestObjectCount); + _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); _testArray = TestObjects.ToArray(); _testObjectsAsList = TestObjects.ToList(); _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); From 4ea8e20f0accc7230eede55be47966954f016858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 1 Mar 2023 00:44:08 +0100 Subject: [PATCH 097/305] Compilation error fix when using manual benchmark execution. Additional benchmark method added. --- Recyclable.Collections.Benchmarks/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index c5afe52..95a70b3 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -10,7 +10,7 @@ using Recyclable.Collections.Benchmarks; internal class Program { - void RunBenchmark() + static void RunBenchmark() { var benchmark = new RecyclableCollectionsBenchmarks(); benchmark.Setup(); @@ -24,6 +24,7 @@ internal class Program //benchmark.RecyclableList_Create_WithCapacity(); //benchmark.RecyclableList_Add(); //benchmark.RecyclableList_Add_WithCapacity(); + benchmark.RecyclableList_Contains_LastItems(); } benchmark.Cleanup(); From bbbc47bd42b3c90c5d8516d64bebd35547a0b9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 1 Mar 2023 00:45:28 +0100 Subject: [PATCH 098/305] BenchmarkBase.BlockSize parameterized. --- Recyclable.Collections.Benchmarks/BenchmarkBase.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index 8fd7802..8dd01ee 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -6,11 +6,14 @@ namespace Recyclable.Collections.Benchmarks { //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] public int TestObjectCount = 1_000_000; + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] + public int Divider = 10; public int BlockSize => TestObjectCount switch { 0 => 1, > 0 and <= 10 => TestObjectCount, - _ => TestObjectCount / 10 + _ => TestObjectCount / Divider }; protected object[]? _testObjects; From 3552dd00d1a5b7d9940a632f36507e46a9731bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 1 Mar 2023 23:09:48 +0100 Subject: [PATCH 099/305] Test object count increased to 100_000 to better cover cases. --- Recyclable.CollectionsTests/RecyclableListTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 36ab1c0..95d59cd 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -5,7 +5,7 @@ namespace Recyclable.CollectionsTests { public class RecyclableListTests { - private const int _totalObjectCount = 5_000; + private const int _totalObjectCount = 100_000; private static RecyclableList NewRecyclableList => CreateReversedRecyclableList(_testData); private static readonly IEnumerable _testData = Enumerable.Range(1, _totalObjectCount); private static RecyclableList CreateReversedRecyclableList(IEnumerable source) => new(source, 1024); @@ -97,7 +97,7 @@ namespace Recyclable.CollectionsTests foreach (var item in testData) { // Validate - _ = list.Contains(item).Should().BeTrue(); + _ = list.Contains(item).Should().BeTrue($"we searched for {item}"); } } @@ -256,7 +256,7 @@ namespace Recyclable.CollectionsTests list.AddRange(testData); // Validate - _ = list.Capacity.Should().Be(16384, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); _ = list.Should().HaveCount(testData.Length) .And.ContainInConsecutiveOrder(testData) .And.BeEquivalentTo(testData); @@ -273,7 +273,7 @@ namespace Recyclable.CollectionsTests list.AddRange(testData); // Validate - _ = list.Capacity.Should().Be(16384, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); _ = list.Should().HaveCount(testData.Count) .And.ContainInConsecutiveOrder(testData) .And.BeEquivalentTo(testData); From 248dff8fda0d51effbc7a24188eba9bf96b0393f Mon Sep 17 00:00:00 2001 From: Memek Date: Thu, 2 Mar 2023 17:47:25 +0100 Subject: [PATCH 100/305] BenchmarkBase fixed to cover case when BockSize is 0. --- Recyclable.Collections.Benchmarks/BenchmarkBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index 8dd01ee..4b49a48 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -13,8 +13,8 @@ namespace Recyclable.Collections.Benchmarks { 0 => 1, > 0 and <= 10 => TestObjectCount, - _ => TestObjectCount / Divider - }; + _ => TestObjectCount / Divider > 0 ? TestObjectCount / Divider : TestObjectCount + }; protected object[]? _testObjects; protected object[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); From cbd9850f58d75c21f4976367528466db2a23a93d Mon Sep 17 00:00:00 2001 From: Memek Date: Thu, 2 Mar 2023 18:54:35 +0100 Subject: [PATCH 101/305] RecyclableCollectionsBenchmarks.List_Contains_FirstItems, . List_Contains_LastItems, .RecyclableList_Contains_FirstItems, .RecyclableList_Contains_LastItems fixed to always search for at least 1 element. --- .../RecyclableCollectionsBenchmarks.List.cs | 4 ++-- .../RecyclableCollectionsBenchmarks.RecyclableList.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 19b8269..c28d9b0 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 / 10; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; 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 / 10; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[^(i + 1)])); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 27e7d32..4c0dc89 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 / 10; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; 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 / 10; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { DoNothing(list.Contains(data[^(i + 1)])); From be9dfaf43a574e951a8988fb3d7c60e4c9ddedc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 3 Mar 2023 01:31:14 +0100 Subject: [PATCH 102/305] Code cleanup. Unnecessary versions removed. --- Recyclable.Collections/RecyclableList.cs | 267 ----------------------- 1 file changed, 267 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index ec7c490..2007beb 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -516,273 +516,6 @@ namespace Recyclable.Collections return false; } - // VERSION 2 - //using var safeToContinue = new ManualResetEventSlim(false); - //using var foundEvent = new ManualResetEventSlim(false); - //using var countdownEvent = new CountdownEvent(((_lastBlockIndex + 1) / BatchSize) + ((_lastBlockIndex + 1) % BatchSize) > 0 ? 1 : 0); - //int lastBlockIndex = _lastBlockIndex; - //T[][] memoryBlocks = _memoryBlocks; - //int batchOffset = 0; - //while (batchOffset < memoryBlocks.Length) - //{ - // var toCopy = Math.Min(BatchSize, memoryBlocks.Length - batchOffset); - // //var batch = memoryBlocks[0..toCopy].ToArray(); - // _ = Task.Run(() => - // { - // Span batchSpan = new(memoryBlocks, batchOffset, toCopy); //new(batch); - // safeToContinue.Set(); - // for (var memoryBlockIndex = 0; memoryBlockIndex < batchSpan.Length; memoryBlockIndex++) - // { - // if (Array.IndexOf(batchSpan[memoryBlockIndex], item, 0, _blockSize) >= 0) - // { - // foundEvent.Set(); - // break; - // } - // else if (foundEvent.IsSet) - // { - // return; - // } - // } - - // if (!foundEvent.IsSet) - // { - // _ = countdownEvent.Signal(); - // } - // }); - - // while (!safeToContinue.IsSet) - // { - // // Intentionally left empty - // } - - // safeToContinue.Reset(); - // if (foundEvent.IsSet) - // { - // return true; - // } - - // batchOffset += toCopy; - // //memoryBlocks = memoryBlocks[toCopy..]; - //} - - //_ = WaitHandle.WaitAny(new WaitHandle[] { foundEvent.WaitHandle, countdownEvent.WaitHandle }); - //return foundEvent.IsSet || Array.IndexOf(_memoryBlocks[_lastBlockIndex], item, 0, _nextItemIndex) >= 0; - - - - // VERSION 1 - //int blockSize = _blockSize; - //int itemIndex = 0; - //int blockIndex = 0; - //int lastBlockIndex = _lastBlockIndex; - - //Span memoryBlocksSpan = new(_memoryBlocks); - //Span blockArraySpan = new(memoryBlocksSpan[blockIndex]); - //if (item != null) - //{ - // int midPoint = blockSize / 4; - // while (blockIndex < lastBlockIndex) - // { - // if (item.Equals(blockArraySpan[itemIndex])) - // { - // return true; - // } - - // if (item.Equals(blockArraySpan[midPoint + itemIndex])) - // { - // return true; - // } - - // if (item.Equals(blockArraySpan[(midPoint * 2) + itemIndex])) - // { - // return true; - // } - - // if (item.Equals(blockArraySpan[(midPoint * 3) + itemIndex])) - // { - // return true; - // } - - // //if (item.Equals(blockArraySpan[(midPoint * 4) + itemIndex])) - // //{ - // // return true; - // //} - - // //if (item.Equals(blockArraySpan[(midPoint * 5) + itemIndex])) - // //{ - // // return true; - // //} - - // //if (item.Equals(blockArraySpan[(midPoint * 6) + itemIndex])) - // //{ - // // return true; - // //} - - // //if (item.Equals(blockArraySpan[(midPoint * 7) + itemIndex])) - // //{ - // // return true; - // //} - - // itemIndex++; - // if (itemIndex >= midPoint) - // { - // blockIndex++; - // blockArraySpan = new(memoryBlocksSpan[blockIndex]); - // 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[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; - // } - //} - - //return false; - - // VERSION 3 Span memoryBlocksSpan = new(_memoryBlocks); int lastBlockIndex = _lastBlockIndex; for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) From 9676ee74859cebcde7885cffb9d4554ac1a380c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 3 Mar 2023 01:33:41 +0100 Subject: [PATCH 103/305] Performance improvement for low no. of items --- Recyclable.Collections/RecyclableList.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 2007beb..da48779 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -516,6 +516,11 @@ namespace Recyclable.Collections return false; } + if (_lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; + } + Span memoryBlocksSpan = new(_memoryBlocks); int lastBlockIndex = _lastBlockIndex; for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) From a50ef109892611aa402fc0d6a7e9db17464ddeea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Mar 2023 14:33:43 +0100 Subject: [PATCH 104/305] RecyclableList.IndexOf performance improvements. --- Recyclable.Collections/RecyclableList.cs | 32 +++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index da48779..42e719b 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -545,24 +545,26 @@ namespace Recyclable.Collections { return ItemNotFoundIndex; } - - int itemIndex, blockIndex = 0, lastBlockIndex = _lastBlockIndex, blockSize = _blockSize; - Span memoryBlocksSpan = new(_memoryBlocks); - while (blockIndex < lastBlockIndex) + + if (_lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0)) { - itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); - if (itemIndex >= 0) - { - return (blockIndex * blockSize) + itemIndex; - } - - blockIndex++; + return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount); } - itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _nextItemIndex); - return itemIndex >= 0 - ? (blockIndex * blockSize) + itemIndex - : ItemNotFoundIndex; + Span memoryBlocksSpan = new(_memoryBlocks); + const int blockIndex = 0, lastBlockIndex = 1, itemIndex = 2, blockSize = 3; + Span localVars = stackalloc int[4] { 0, _lastBlockIndex, ItemNotFoundIndex, _blockSize }; + for (localVars[blockIndex] = 0; localVars[blockIndex] < localVars[lastBlockIndex]; localVars[blockIndex]++) + { + localVars[itemIndex] = Array.IndexOf(memoryBlocksSpan[localVars[blockIndex]], item, 0, localVars[blockSize]); + if (localVars[itemIndex] >= 0) + { + return localVars[itemIndex] + (localVars[blockIndex] * localVars[blockSize]); + } + } + + localVars[itemIndex] = Array.IndexOf(memoryBlocksSpan[localVars[lastBlockIndex]], item, 0, _nextItemIndex); + return localVars[itemIndex] >= 0 ? localVars[itemIndex] + (localVars[blockIndex] * localVars[blockSize]) : ItemNotFoundIndex; } public void Insert(int index, T item) => throw new NotSupportedException(); From ec355160f90c49f7b589a28a0bc333683e14a8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Mar 2023 18:50:09 +0100 Subject: [PATCH 105/305] IndexOf & LongIndexOf made alike. Small refactoring. --- Recyclable.Collections/RecyclableList.cs | 79 +++++++------------ .../RecyclableListTests.cs | 6 +- 2 files changed, 30 insertions(+), 55 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 42e719b..87ed1ea 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -77,6 +77,23 @@ namespace Recyclable.Collections throw new ArgumentOutOfRangeException("index"); } + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks); + int itemIndex; + for (var blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + (blockIndex * blockSize); + } + } + + itemIndex = Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex); + return itemIndex >= 0 ? itemIndex + (lastBlockIndex * blockSize) : itemIndex; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, in long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { @@ -539,60 +556,18 @@ namespace Recyclable.Collections public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(T item) - { - if (_longCount == 0) - { - return ItemNotFoundIndex; - } - - if (_lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0)) - { - return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount); - } - - Span memoryBlocksSpan = new(_memoryBlocks); - const int blockIndex = 0, lastBlockIndex = 1, itemIndex = 2, blockSize = 3; - Span localVars = stackalloc int[4] { 0, _lastBlockIndex, ItemNotFoundIndex, _blockSize }; - for (localVars[blockIndex] = 0; localVars[blockIndex] < localVars[lastBlockIndex]; localVars[blockIndex]++) - { - localVars[itemIndex] = Array.IndexOf(memoryBlocksSpan[localVars[blockIndex]], item, 0, localVars[blockSize]); - if (localVars[itemIndex] >= 0) - { - return localVars[itemIndex] + (localVars[blockIndex] * localVars[blockSize]); - } - } - - localVars[itemIndex] = Array.IndexOf(memoryBlocksSpan[localVars[lastBlockIndex]], item, 0, _nextItemIndex); - return localVars[itemIndex] >= 0 ? localVars[itemIndex] + (localVars[blockIndex] * localVars[blockSize]) : ItemNotFoundIndex; - } + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : (int)DoIndexOf(item, _memoryBlocks, _lastBlockIndex, _blockSize, _nextItemIndex); public void Insert(int index, T item) => throw new NotSupportedException(); - 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 long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : DoIndexOf(item, _memoryBlocks, _lastBlockIndex, _blockSize, _nextItemIndex); public bool Remove(T item) => throw new NotSupportedException(); public void RemoveBlock(int index) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 95d59cd..d0ced6d 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -169,10 +169,10 @@ namespace Recyclable.CollectionsTests using var list = NewRecyclableList; // Act & Validate - foreach (var index in _testData) + foreach (var item in _testData) { - var actual = list.IndexOf(index); - _ = actual.Should().Be(index - 1); + var actual = list.IndexOf(item); + _ = actual.Should().Be(item - 1); } } From 2558f579342c4e28509b726e55c3d65a3111a54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Mar 2023 18:51:06 +0100 Subject: [PATCH 106/305] RecyclableList.Remove modified to allow removing the last item. --- Recyclable.Collections/RecyclableList.cs | 13 ++++++++++- .../RecyclableListTests.cs | 23 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 87ed1ea..cb8b308 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -569,7 +569,18 @@ namespace Recyclable.Collections ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) : DoIndexOf(item, _memoryBlocks, _lastBlockIndex, _blockSize, _nextItemIndex); - public bool Remove(T item) => throw new NotSupportedException(); + public bool Remove(T item) + { + var itemIndex = LongIndexOf(item); + if (itemIndex >= 0) + { + RemoveAt(this, itemIndex, _defaultBlockArrayPool); + return true; + } + + return false; + } + public void RemoveBlock(int index) { if (_blockSize < RecyclableDefaults.MinPooledArrayLength) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index d0ced6d..3089573 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -186,6 +186,27 @@ namespace Recyclable.CollectionsTests _ = Assert.Throws(() => list.Insert(0, -1)); } + [Fact] + public void RemoveShouldRemoveLastItem() + { + // Prepare + using var list = NewRecyclableList; + + int removedCount = 0; + var testData = _testData.ToArray(); + foreach (var item in _testData.Reverse()) + { + // Act & Validate + _ = list.Remove(item).Should().BeTrue($"we search for {item} which should exist"); + removedCount++; + var expectedList = testData[0..(testData.Length - removedCount)]; + _ = list.LongCount.Should().Be(expectedList.Length); + _ = list.Should().ContainInConsecutiveOrder(expectedList); + } + + _ = list.Should().BeEmpty(); + } + [Fact] public void RemoveShouldRaiseNotSupportedException() { @@ -193,7 +214,7 @@ namespace Recyclable.CollectionsTests using var list = NewRecyclableList; // Act - _ = Assert.Throws(() => list.Remove(_testData.First())); + _ = Assert.Throws(() => list.Remove(_testData.First())); } [Fact] From ba17f6972fa5d16598bbed89e654368adf225e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Mar 2023 18:54:33 +0100 Subject: [PATCH 107/305] RecyclableList_IndexOf_FirstItems, RecyclableList_IndexOf_LastItems, List_IndexOf_FirstItems & List_IndexOf_LastItems benchmarks fixed to always search for some items, if the no. of test cases is low. --- .../RecyclableCollectionsBenchmarks.List.cs | 12 ++++++------ ...RecyclableCollectionsBenchmarks.RecyclableList.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index c28d9b0..ed6c06e 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -112,7 +112,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(data.Count); } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Contains_FirstItems() { var data = TestObjects; @@ -124,7 +124,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void List_Contains_LastItems() { var data = TestObjects; @@ -136,24 +136,24 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = true)] public void List_IndexOf_FirstItems() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = TestObjectCount / 10; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); } } - //[Benchmark] + [Benchmark] public void List_IndexOf_LastItems() { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = TestObjectCount / 10; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; 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 4c0dc89..5cb1f8b 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -78,7 +78,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(data.LongCount); } - [Benchmark] + //[Benchmark] public void RecyclableList_Contains_FirstItems() { var data = TestObjects; @@ -90,7 +90,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_Contains_LastItems() { var data = TestObjects; @@ -102,24 +102,24 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_IndexOf_FirstItems() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); } } - //[Benchmark] + [Benchmark] public void RecyclableList_IndexOf_LastItems() { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[^(i + 1)])); From 73e07c917d8e54709f37feb90638b8ff83c09919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Mar 2023 18:55:23 +0100 Subject: [PATCH 108/305] RecyclableList_LongIndexOf_FirstItems & RecyclableList_LongIndexOf_LastItems benchmarks added --- ...bleCollectionsBenchmarks.RecyclableList.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 5cb1f8b..2bdedf3 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -125,5 +125,29 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.IndexOf(data[^(i + 1)])); } } + + //[Benchmark] + public void RecyclableList_LongIndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.LongIndexOf(data[i])); + } + } + + //[Benchmark] + public void RecyclableList_LongIndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.LongIndexOf(data[^(i + 1)])); + } + } } } From a15f2431fc633dd7ffad47fcc3a7f4845e3e6668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Mar 2023 19:01:53 +0100 Subject: [PATCH 109/305] Tabs fixed. New benchmark params added --- Recyclable.Collections.Benchmarks/BenchmarkBase.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index 4b49a48..c1244b2 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -4,17 +4,20 @@ namespace Recyclable.Collections.Benchmarks { public class BenchmarkBase { - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] public int TestObjectCount = 1_000_000; //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] + //[Params(10, 20, 50, 100)] + //[Params(1, 10)] public int Divider = 10; public int BlockSize => TestObjectCount switch { 0 => 1, > 0 and <= 10 => TestObjectCount, - _ => TestObjectCount / Divider > 0 ? TestObjectCount / Divider : TestObjectCount - }; + _ => TestObjectCount / Divider > 0 ? TestObjectCount / Divider : TestObjectCount + }; protected object[]? _testObjects; protected object[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); From 0cd68fbb7ca8dad84d3daa4eb02ce3c74c3e3775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Mar 2023 19:02:37 +0100 Subject: [PATCH 110/305] New benchmark cases added --- Recyclable.Collections.Benchmarks/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 95a70b3..3bc30a1 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -24,7 +24,8 @@ internal class Program //benchmark.RecyclableList_Create_WithCapacity(); //benchmark.RecyclableList_Add(); //benchmark.RecyclableList_Add_WithCapacity(); - benchmark.RecyclableList_Contains_LastItems(); + //benchmark.RecyclableList_Contains_LastItems(); + benchmark.RecyclableList_IndexOf_FirstItems(); } benchmark.Cleanup(); From 02138dfac664814408a7dfe4c82cfb3899d143f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 5 Mar 2023 19:19:54 +0100 Subject: [PATCH 111/305] New tests added for IndexOf & LongIndexOf --- .../RecyclableListTests.cs | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 3089573..a407527 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -163,7 +163,7 @@ namespace Recyclable.CollectionsTests } [Fact] - public void IndexOfShouldFindTheIndexes() + public void IndexOfShouldReturnCorrectIndexes() { // Prepare using var list = NewRecyclableList; @@ -176,6 +176,36 @@ namespace Recyclable.CollectionsTests } } + [Fact] + public void IndexOfShouldNotFindNonExistingItems() + { + // 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.IndexOf(-item).Should().Be(-1); + } + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + } + + [Fact] + public void IndexOfShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableList(); + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + _ = list.IndexOf(1).Should().Be(-1); + } + [Fact] public void InsertShouldRaiseNotSupportedException() { @@ -329,6 +359,37 @@ namespace Recyclable.CollectionsTests } } + [Fact] + public void LongIndexOfShouldNotFindNonExistingItems() + { + // 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.LongIndexOf(-item).Should().Be(-1); + } + + // Validate + _ = list.LongIndexOf(0).Should().Be(-1); + } + + [Fact] + public void LongIndexOfShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableList(); + + // Validate + _ = list.LongIndexOf(0).Should().Be(-1); + _ = list.LongIndexOf(1).Should().Be(-1); + } + + [Fact] public void ConstructorSourceShouldInitializeList() { From 3ce3becfbda6c135e147399d2565db296a7d6381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 10 Mar 2023 16:52:36 +0100 Subject: [PATCH 112/305] RecyclableList performance improvements & fixes. New tests added. --- Recyclable.Collections/RecyclableArrayList.cs | 2 + Recyclable.Collections/RecyclableList.cs | 161 +++++++++++++++--- .../RecyclableListTests.cs | 82 ++++++++- 3 files changed, 218 insertions(+), 27 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 581916f..21bd7ce 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -293,6 +293,8 @@ namespace Recyclable.Collections _count = targetItemIdx; } + public T[] AsArray() => _memoryBlock; + public void Clear() { _count = 0; diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index cb8b308..dafb764 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -274,39 +274,143 @@ namespace Recyclable.Collections public void AddRange(in T[] items) { - long sourceItemsCount = items.LongLength; - long oldLongCount = _longCount; - long targetCapacity = oldLongCount + sourceItemsCount; + long targetCapacity = _longCount + items.LongLength; if (_capacity < targetCapacity) { _ = EnsureCapacity(targetCapacity); } - int blockSize = _blockSize; - int targetItemIdx = (int)(oldLongCount % blockSize); - int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); + int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy = 0; + Span itemsSpan = new(items); Span memoryBlocksSpan = new(_memoryBlocks); - int memoryBlocksCount = memoryBlocksSpan.Length; - Span targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]); - for (var i = 0L; i < sourceItemsCount; i++) + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (!itemsSpan.IsEmpty) { - targetBlockSpan[targetItemIdx++] = items[i]; - if (targetItemIdx == blockSize) + if (targetBlockArraySpan.Length < itemsSpan.Length) { - targetBlockIdx++; - if (targetBlockIdx == memoryBlocksCount) + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex]); + } + else + { + toCopy = itemsSpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[..toCopy]; + } + + itemsSpan = itemsSpan[toCopy..]; + } + + _nextItemIndex = toCopy; + _longCount = targetCapacity; + _lastBlockIndex = targetBlockIndex; + } + + public void AddRange(in RecyclableArrayList items) + { + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy = 0; + Span itemsSpan = new(items.AsArray()); + Span memoryBlocksSpan = new(_memoryBlocks); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (!itemsSpan.IsEmpty) + { + if (targetBlockArraySpan.Length < itemsSpan.Length) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex]); + } + else + { + toCopy = itemsSpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[..toCopy]; + } + + itemsSpan = itemsSpan[toCopy..]; + } + + _nextItemIndex = toCopy; + _longCount = targetCapacity; + _lastBlockIndex = targetBlockIndex; + + } + + public void AddRange(in RecyclableList items) + { + long itemsCount = items.LongCount; + long targetCapacity = _longCount + itemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _lastBlockIndex, + toCopy = 0; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) { - break; + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); } - targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); - targetItemIdx = 0; + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex]); + + } } } + if (targetCapacity % blockSize == 0) + { + _nextItemIndex = 0; + _lastBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = (int)(targetCapacity % blockSize); + _lastBlockIndex = targetBlockIndex; + + } + _longCount = targetCapacity; - _lastBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; + } public void AddRange(in List items) @@ -324,11 +428,12 @@ namespace Recyclable.Collections int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); Span memoryBlocksSpan = new(_memoryBlocks); var memoryBlocksCount = memoryBlocksSpan.Length; - Span targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]); + Span targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (int i = 0; i < sourceItemsCount; i++) { - targetBlockSpan[targetItemIdx++] = items[i]; - if (targetItemIdx == blockSize) + targetBlockSpan[targetItemIdx] = items[i]; + if (targetItemIdx + 1 == blockSize) { targetBlockIdx++; if (targetBlockIdx == memoryBlocksCount) @@ -336,9 +441,13 @@ namespace Recyclable.Collections break; } - targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); + targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); targetItemIdx = 0; } + else + { + targetItemIdx++; + } } _longCount = targetCapacity; @@ -383,7 +492,7 @@ namespace Recyclable.Collections _nextItemIndex = targetItemIdx; } - private void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { if (source is T[] sourceArray) { @@ -403,6 +512,12 @@ namespace Recyclable.Collections return; } + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + if (source is IList sourceIList) { AddRange(sourceIList); diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index a407527..8258ea0 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -50,6 +50,25 @@ namespace Recyclable.CollectionsTests _ = list.Should().BeEquivalentTo(testNumbers); } + [Fact] + public void AddShouldAddItemWhenAfterAddRange() + { + // Prepare + int[] testNumbers = new[] { 1, 2, 2, 3 }; + + // Act + using var list = new RecyclableList(); + list.AddRange(_testData); + foreach (var index in testNumbers) + { + list.Add(index); + } + + // Validate + _ = list.LongCount.Should().Be(_totalObjectCount + testNumbers.Length); + _ = list.Should().BeEquivalentTo(_testData.Concat(testNumbers)); + } + [Fact] public void ConstructorShouldAcceptDuplicates() { @@ -300,7 +319,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableList(); + using var list = new RecyclableList(); // Act @@ -309,8 +328,8 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); _ = list.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); } [Fact] @@ -318,7 +337,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToList(); - var list = new RecyclableList(); + using var list = new RecyclableList(); // Act list.AddRange(testData); @@ -330,6 +349,61 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(testData); } + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsRecyclableList() + { + // Prepare + var testData = _testData.ToRecyclableList(); + using var list = new RecyclableList(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Count) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + + [Fact] + public void AddRangeShouldNotOverrideItemsWhenSourceIsArray() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableList(); + + // Act + list.AddRange(testData); + list.AddRange(testData); + + // Validate + var expectedData = testData.Concat(testData).ToArray(); + _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(expectedData.Length) + .And.ContainInConsecutiveOrder(expectedData) + .And.BeEquivalentTo(expectedData); + } + + [Fact] + public void AddRangeShouldNotOverrideItemsWhenSourceIsRecyclableList() + { + // Prepare + using var testData = _testData.ToRecyclableList(); + using var list = new RecyclableList(); + + // Act + list.AddRange(testData); + list.AddRange(testData); + + // Validate + var expectedData = _testData.Concat(_testData).ToArray(); + _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(expectedData.Length) + .And.ContainInConsecutiveOrder(expectedData) + .And.BeEquivalentTo(expectedData); + } + [Fact] public void InsertAtTheBeginningShouldMoveItems() { From 41ad48d4e6c086d710a7d4ca638a7c4e3ce63332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 10 Mar 2023 17:09:53 +0100 Subject: [PATCH 113/305] New benchmarks addedo --- Recyclable.Collections.Benchmarks/Program.cs | 3 +- .../RecyclableCollectionsBenchmarks.List.cs | 16 +++--- ...llectionsBenchmarks.RecyclableArrayList.cs | 12 ++-- ...bleCollectionsBenchmarks.RecyclableList.cs | 57 +++++++++++++++++-- 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 3bc30a1..93579d5 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -25,7 +25,8 @@ internal class Program //benchmark.RecyclableList_Add(); //benchmark.RecyclableList_Add_WithCapacity(); //benchmark.RecyclableList_Contains_LastItems(); - benchmark.RecyclableList_IndexOf_FirstItems(); + //benchmark.RecyclableList_IndexOf_FirstItems(); + benchmark.RecyclableList_AddRangeWhenSourceIsList(); } benchmark.Cleanup(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index ed6c06e..2367de1 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = true)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void List_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void List_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void List_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -74,7 +74,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -136,7 +136,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_IndexOf_FirstItems() { var data = TestObjects; @@ -148,7 +148,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void List_IndexOf_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index e970042..f25652d 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -74,7 +74,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; @@ -82,7 +82,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_AddRange_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 2bdedf3..7e4c38c 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(minBlockSize: 102_400, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(list); } @@ -35,13 +35,62 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(minBlockSize: 102_400, expectedItemsCount: dataCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); } } + [Benchmark] + public void RecyclableList_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsList; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableList_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableList_AddRangeWhenSourceIsRecyclableArrayList() + { + var data = TestObjectsAsRecyclableArrayList; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + //[Benchmark] public void RecyclableList_GetItem() { @@ -102,7 +151,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_IndexOf_FirstItems() { var data = TestObjects; @@ -114,7 +163,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_IndexOf_LastItems() { var data = TestObjects; From 948bbaff5c6a0d5a98dadedaee662a6e17b7d601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 10 Mar 2023 17:10:19 +0100 Subject: [PATCH 114/305] New benchmarks added --- Recyclable.Collections/Recyclable.Collections.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Recyclable.Collections/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj index c7aa76b..e9f7027 100644 --- a/Recyclable.Collections/Recyclable.Collections.csproj +++ b/Recyclable.Collections/Recyclable.Collections.csproj @@ -30,4 +30,8 @@ PreserveNewest + + + + From 9c640f42515d3495d74f15fe6d5a88fc1f94e7ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 10 Mar 2023 18:30:41 +0100 Subject: [PATCH 115/305] RecyclableList.AddRange(List items) performance improvements. --- 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 dafb764..828eb63 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -342,7 +342,6 @@ namespace Recyclable.Collections _nextItemIndex = toCopy; _longCount = targetCapacity; _lastBlockIndex = targetBlockIndex; - } public void AddRange(in RecyclableList items) @@ -413,46 +412,47 @@ namespace Recyclable.Collections } - public void AddRange(in List items) + public void AddRange(List items) { - long sourceItemsCount = items.Count; - long oldLongCount = _longCount; - long targetCapacity = oldLongCount + sourceItemsCount; + long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { _ = EnsureCapacity(targetCapacity); } - int blockSize = _blockSize; - int targetItemIdx = (int)(oldLongCount % blockSize); - int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - Span memoryBlocksSpan = new(_memoryBlocks); - var memoryBlocksCount = memoryBlocksSpan.Length; - Span targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy = 0, + copiedCount = 0, targetItemIndex = _nextItemIndex, totalItemsCount = items.Count; - for (int i = 0; i < sourceItemsCount; i++) + Span memoryBlocksSpan = new(_memoryBlocks); + T[] targetBlockArray = memoryBlocksSpan[targetBlockIndex]; + while (true) { - targetBlockSpan[targetItemIdx] = items[i]; - if (targetItemIdx + 1 == blockSize) + if (blockSize - targetItemIndex <= totalItemsCount - copiedCount) { - targetBlockIdx++; - if (targetBlockIdx == memoryBlocksCount) + toCopy = blockSize - targetItemIndex; + items.CopyTo(copiedCount, targetBlockArray, targetItemIndex, toCopy); + targetItemIndex = 0; + targetBlockIndex++; + if (targetBlockIndex > memoryBlocksSpan.Length) { break; } - targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - targetItemIdx = 0; + targetBlockArray = memoryBlocksSpan[targetBlockIndex]; + copiedCount += toCopy; } else { - targetItemIdx++; + toCopy = totalItemsCount - copiedCount; + items.CopyTo(copiedCount, targetBlockArray, targetItemIndex, toCopy); + targetItemIndex = toCopy; + break; } } _longCount = targetCapacity; - _lastBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; + _lastBlockIndex = targetBlockIndex; + _nextItemIndex = targetItemIndex; } public void AddRange(in IList items) From f76276c7a490a877096b892608d739e317db917e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 15:26:39 +0100 Subject: [PATCH 116/305] RecyclableArrayList.CopyTo fixed to copy only the no. of items added to the list. --- 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 21bd7ce..a559252 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -302,7 +302,7 @@ namespace Recyclable.Collections public bool Contains(T item) => _memoryBlock.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => _memoryBlock.CopyTo(array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_memoryBlock, 0, array, arrayIndex, _count); protected static IEnumerable Enumerate(RecyclableArrayList list) { From e7e670ae3564f3f62dd8cbacc8ed90691210489c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 15:51:52 +0100 Subject: [PATCH 117/305] RecyclableList.AddRange(T[] items) performance improvements. --- Recyclable.Collections/RecyclableList.cs | 29 ++++++++---------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 828eb63..91dcc24 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -8,6 +8,7 @@ namespace Recyclable.Collections { private const int ItemNotFoundIndex = -1; private const int BatchSize = 8; + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; @@ -280,30 +281,20 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy = 0; + int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex; Span itemsSpan = new(items); - Span memoryBlocksSpan = new(_memoryBlocks); + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (!itemsSpan.IsEmpty) + while (targetBlockArraySpan.Length <= itemsSpan.Length) { - if (targetBlockArraySpan.Length < itemsSpan.Length) - { - toCopy = targetBlockArraySpan.Length; - itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); - targetBlockIndex++; - targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex]); - } - else - { - toCopy = itemsSpan.Length; - itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); - targetBlockArraySpan = targetBlockArraySpan[..toCopy]; - } - - itemsSpan = itemsSpan[toCopy..]; + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); } - _nextItemIndex = toCopy; + _nextItemIndex = itemsSpan.Length; + itemsSpan[..itemsSpan.Length].CopyTo(targetBlockArraySpan); _longCount = targetCapacity; _lastBlockIndex = targetBlockIndex; } From 1d6168430044ba78a505c08bd1157bf0bff644ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 15:58:50 +0100 Subject: [PATCH 118/305] RecyclableList.AddRange(RecyclableArrayList items) performance improvements & simplification. --- Recyclable.Collections/RecyclableList.cs | 32 ++++++++---------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 91dcc24..03a5ae2 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -299,7 +299,7 @@ namespace Recyclable.Collections _lastBlockIndex = targetBlockIndex; } - public void AddRange(in RecyclableArrayList items) + public void AddRange(RecyclableArrayList items) { long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) @@ -307,30 +307,20 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy = 0; - Span itemsSpan = new(items.AsArray()); - Span memoryBlocksSpan = new(_memoryBlocks); + int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex; + Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (!itemsSpan.IsEmpty) + while (targetBlockArraySpan.Length <= itemsSpan.Length) { - if (targetBlockArraySpan.Length < itemsSpan.Length) - { - toCopy = targetBlockArraySpan.Length; - itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); - targetBlockIndex++; - targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex]); - } - else - { - toCopy = itemsSpan.Length; - itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); - targetBlockArraySpan = targetBlockArraySpan[..toCopy]; - } - - itemsSpan = itemsSpan[toCopy..]; + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); } - _nextItemIndex = toCopy; + _nextItemIndex = itemsSpan.Length; + itemsSpan[..itemsSpan.Length].CopyTo(targetBlockArraySpan); _longCount = targetCapacity; _lastBlockIndex = targetBlockIndex; } From 8dfdc6f088a47cb95c05baa8bdf77dd3cc066281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 16:43:14 +0100 Subject: [PATCH 119/305] Removed unnecessary Span creation in RecyclableList.AddRange(T[] items) & .AddRange(RecyclableArrayList items) --- 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 03a5ae2..cce540b 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -294,7 +294,7 @@ namespace Recyclable.Collections } _nextItemIndex = itemsSpan.Length; - itemsSpan[..itemsSpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; _lastBlockIndex = targetBlockIndex; } @@ -320,7 +320,7 @@ namespace Recyclable.Collections } _nextItemIndex = itemsSpan.Length; - itemsSpan[..itemsSpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; _lastBlockIndex = targetBlockIndex; } From a1187a06a4816da1ef52eb202aec3a08ecbe460a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 17:12:31 +0100 Subject: [PATCH 120/305] RecyclableList.AddRange(List items) performance improvements --- Recyclable.Collections/RecyclableList.cs | 88 ++++++++++++++++-------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index cce540b..b2a580c 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -390,7 +390,6 @@ namespace Recyclable.Collections } _longCount = targetCapacity; - } public void AddRange(List items) @@ -401,39 +400,72 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy = 0, - copiedCount = 0, targetItemIndex = _nextItemIndex, totalItemsCount = items.Count; + int blockSize = _blockSize, + targetBlockIndex = _lastBlockIndex, + copied = blockSize - _nextItemIndex, + itemsCount = items.Count; - Span memoryBlocksSpan = new(_memoryBlocks); - T[] targetBlockArray = memoryBlocksSpan[targetBlockIndex]; - while (true) + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex++], _nextItemIndex, copied); + while (blockSize <= itemsCount - copied) { - if (blockSize - targetItemIndex <= totalItemsCount - copiedCount) - { - toCopy = blockSize - targetItemIndex; - items.CopyTo(copiedCount, targetBlockArray, targetItemIndex, toCopy); - targetItemIndex = 0; - targetBlockIndex++; - if (targetBlockIndex > memoryBlocksSpan.Length) - { - break; - } + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } - targetBlockArray = memoryBlocksSpan[targetBlockIndex]; - copiedCount += toCopy; - } - else - { - toCopy = totalItemsCount - copiedCount; - items.CopyTo(copiedCount, targetBlockArray, targetItemIndex, toCopy); - targetItemIndex = toCopy; - break; - } + if (itemsCount - copied != 0) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + else + { + _nextItemIndex = 0; } - _longCount = targetCapacity; _lastBlockIndex = targetBlockIndex; - _nextItemIndex = targetItemIndex; + _longCount = targetCapacity; + + // VERSION 2 + //long targetCapacity = _longCount + items.Count; + //if (_capacity < targetCapacity) + //{ + // _ = EnsureCapacity(targetCapacity); + //} + + //int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy, + // copiedCount = 0, targetItemIndex = _nextItemIndex, totalItemsCount = items.Count; + + //Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + //T[] targetBlockArray = memoryBlocksSpan[targetBlockIndex]; + //while (true) + //{ + // if (blockSize - targetItemIndex <= totalItemsCount - copiedCount) + // { + // toCopy = blockSize - targetItemIndex; + // items.CopyTo(copiedCount, targetBlockArray, targetItemIndex, toCopy); + // targetItemIndex = 0; + // targetBlockIndex++; + // if (targetBlockIndex > memoryBlocksSpan.Length) + // { + // break; + // } + + // targetBlockArray = memoryBlocksSpan[targetBlockIndex]; + // copiedCount += toCopy; + // } + // else + // { + // toCopy = totalItemsCount - copiedCount; + // items.CopyTo(copiedCount, targetBlockArray, targetItemIndex, toCopy); + // targetItemIndex = toCopy; + // break; + // } + //} + + //_longCount = targetCapacity; + //_lastBlockIndex = targetBlockIndex; + //_nextItemIndex = targetItemIndex; } public void AddRange(in IList items) From 689061f512f2c9afab371bfdff759516b399854a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 17:14:10 +0100 Subject: [PATCH 121/305] Cleanup - Version 2 removed --- Recyclable.Collections/RecyclableList.cs | 41 ------------------------ 1 file changed, 41 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index b2a580c..c4a3a7d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -425,47 +425,6 @@ namespace Recyclable.Collections _lastBlockIndex = targetBlockIndex; _longCount = targetCapacity; - - // VERSION 2 - //long targetCapacity = _longCount + items.Count; - //if (_capacity < targetCapacity) - //{ - // _ = EnsureCapacity(targetCapacity); - //} - - //int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, toCopy, - // copiedCount = 0, targetItemIndex = _nextItemIndex, totalItemsCount = items.Count; - - //Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); - //T[] targetBlockArray = memoryBlocksSpan[targetBlockIndex]; - //while (true) - //{ - // if (blockSize - targetItemIndex <= totalItemsCount - copiedCount) - // { - // toCopy = blockSize - targetItemIndex; - // items.CopyTo(copiedCount, targetBlockArray, targetItemIndex, toCopy); - // targetItemIndex = 0; - // targetBlockIndex++; - // if (targetBlockIndex > memoryBlocksSpan.Length) - // { - // break; - // } - - // targetBlockArray = memoryBlocksSpan[targetBlockIndex]; - // copiedCount += toCopy; - // } - // else - // { - // toCopy = totalItemsCount - copiedCount; - // items.CopyTo(copiedCount, targetBlockArray, targetItemIndex, toCopy); - // targetItemIndex = toCopy; - // break; - // } - //} - - //_longCount = targetCapacity; - //_lastBlockIndex = targetBlockIndex; - //_nextItemIndex = targetItemIndex; } public void AddRange(in IList items) From a7d6a9b7e510c04e64f40aa4ed9fc914885a9688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 19:33:40 +0100 Subject: [PATCH 122/305] RecyclableList.AddRange(T[] items) fixed to break the loop when we reach the last block. --- Recyclable.Collections/RecyclableList.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index c4a3a7d..6609e9e 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -281,15 +281,23 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex; + int blockSize = _blockSize, + targetBlockIndex = _lastBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + Span itemsSpan = new(items); - Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); while (targetBlockArraySpan.Length <= itemsSpan.Length) { itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); } From 44c8286f85512bd03d41e41b30b1969672ef9d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 19:38:50 +0100 Subject: [PATCH 123/305] RecyclableList.RecyclableList.AddRange(RecyclableArrayList items) fixed to break the loop when we reach the last block. --- Recyclable.Collections/RecyclableList.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 6609e9e..cf9fccb 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -315,15 +315,23 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex; + int blockSize = _blockSize, + targetBlockIndex = _lastBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + Span itemsSpan = new(items.AsArray(), 0, items.Count); - Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); while (targetBlockArraySpan.Length <= itemsSpan.Length) { itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); } From fb3290f8538b21a46628fa8f6c0327907ced60e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 19:51:51 +0100 Subject: [PATCH 124/305] RecyclableList.AddRange(IList items) performance improvements --- Recyclable.Collections/RecyclableList.cs | 52 ++++++++++++++---------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index cf9fccb..2473f63 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -443,41 +443,51 @@ namespace Recyclable.Collections _longCount = targetCapacity; } - public void AddRange(in IList items) + public void AddRange(IList items) { - long sourceItemsCount = items.Count; - long oldLongCount = _longCount; - long targetCapacity = oldLongCount + sourceItemsCount; + long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { _ = EnsureCapacity(targetCapacity); } - int blockSize = _blockSize; - int targetItemIdx = (int)(oldLongCount % blockSize); - int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - Span memoryBlocksSpan = new(_memoryBlocks); - var memoryBlocksCount = memoryBlocksSpan.Length; - Span targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]); - for (int i = 0; i < sourceItemsCount; i++) + int blockSize = _blockSize, + targetBlockIndex = _lastBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; + try { - targetBlockSpan[targetItemIdx++] = items[i]; - if (targetItemIdx == blockSize) + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) { - targetBlockIdx++; - if (targetBlockIdx == memoryBlocksCount) + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) { break; } - targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); - targetItemIdx = 0; + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _lastBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(itemsBuffer); } } - - _longCount = targetCapacity; - _lastBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; } public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) From e968b47863c02af5e96e4b2317fde0f501c9ec64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 20:02:02 +0100 Subject: [PATCH 125/305] RecyclableList.AddRange(RecyclableList items) code standardized. --- Recyclable.Collections/RecyclableList.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 2473f63..3c8c682 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -341,10 +341,11 @@ namespace Recyclable.Collections _lastBlockIndex = targetBlockIndex; } - public void AddRange(in RecyclableList items) + public void AddRange(RecyclableList items) { - long itemsCount = items.LongCount; - long targetCapacity = _longCount + itemsCount; + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + if (_capacity < targetCapacity) { _ = EnsureCapacity(targetCapacity); @@ -353,11 +354,11 @@ namespace Recyclable.Collections int blockSize = _blockSize, sourceBlockIndex = 0, targetBlockIndex = _lastBlockIndex, - toCopy = 0; + toCopy; - Span sourceMemoryBlocksSpan = new(items._memoryBlocks); + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); - Span targetMemoryBlocksSpan = new(_memoryBlocks); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); long copiedCount = 0; while (copiedCount < itemsCount) @@ -388,7 +389,6 @@ namespace Recyclable.Collections { targetBlockIndex++; targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex]); - } } } From f08633da61191108675b39647a6ccb3213e82c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 20:02:58 +0100 Subject: [PATCH 126/305] New unit tests added. --- .../RecyclableListTests.cs | 96 ++++++++++++++++++- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 8258ea0..2a21ca8 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -328,8 +328,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); _ = list.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); + .And.ContainInConsecutiveOrder(testData); } [Fact] @@ -345,8 +344,40 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); _ = list.Should().HaveCount(testData.Count) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); + .And.ContainInConsecutiveOrder(testData); + } + + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsIList() + { + // Prepare + var testData = (IList)_testData.ToList(); + using var list = new RecyclableList(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().BeGreaterThanOrEqualTo(testData.Count); + _ = list.Should().HaveCount(testData.Count) + .And.ContainInConsecutiveOrder(testData); + } + + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsRecyclableArrayList() + { + // Prepare + var testData = _testData.ToRecyclableArrayList(); + using var list = new RecyclableList(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Count) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); } [Fact] @@ -385,6 +416,63 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(expectedData); } + [Fact] + public void AddRangeShouldNotOverrideItemsWhenSourceIsIList() + { + // Prepare + var testData = (IList)_testData.ToList(); + var list = new RecyclableList(); + + // Act + list.AddRange(testData); + list.AddRange(testData); + + // Validate + var expectedData = testData.Concat(testData).ToArray(); + _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(expectedData.Length) + .And.ContainInConsecutiveOrder(expectedData) + .And.BeEquivalentTo(expectedData); + } + + [Fact] + public void AddRangeShouldNotOverrideItemsWhenSourceIsList() + { + // Prepare + var testData = _testData.ToList(); + var list = new RecyclableList(); + + // Act + list.AddRange(testData); + list.AddRange(testData); + + // Validate + var expectedData = testData.Concat(testData).ToArray(); + _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(expectedData.Length) + .And.ContainInConsecutiveOrder(expectedData) + .And.BeEquivalentTo(expectedData); + } + + [Fact] + public void AddRangeShouldNotOverrideItemsWhenSourceIsRecyclableArrayList() + { + // Prepare + using var testData = _testData.ToRecyclableArrayList(); + using var list = new RecyclableList(); + + // Act + list.AddRange(testData); + list.AddRange(testData); + + // Validate + var expectedData = _testData.Concat(_testData).ToArray(); + _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(expectedData.Length) + .And.ContainInConsecutiveOrder(expectedData) + .And.BeEquivalentTo(expectedData); + } + [Fact] public void AddRangeShouldNotOverrideItemsWhenSourceIsRecyclableList() { From 625b44d34101cb312fe07069079831d4e49a487f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 20:04:36 +0100 Subject: [PATCH 127/305] xxx_AddRangeWhenSourceIsIList benchmarks will now use TestObjectsAsRecyclableList, to avoid any hidden List optimizations --- .../RecyclableCollectionsBenchmarks.List.cs | 6 +++--- .../RecyclableCollectionsBenchmarks.RecyclableArrayList.cs | 2 +- .../RecyclableCollectionsBenchmarks.RecyclableList.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 2367de1..8426065 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + [Benchmark(Baseline = true)] public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -61,7 +61,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void List_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsList; + var data = (IList)TestObjectsAsRecyclableList; var list = new List(); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index f25652d..779a6ad 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -61,7 +61,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsList; + var data = (IList)TestObjectsAsRecyclableList; using var list = new RecyclableArrayList(); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 7e4c38c..8758cc2 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -61,7 +61,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void RecyclableList_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsList; + var data = (IList)TestObjectsAsRecyclableList; using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } From 9b9b68346247903a78f1422643fd629ff86dd4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 22:09:36 +0100 Subject: [PATCH 128/305] RecyclableList.AddRange(RecyclableList items) fixed to copy items only within block size & skip the rest. --- 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 3c8c682..754b83e 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -388,7 +388,7 @@ namespace Recyclable.Collections if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) { targetBlockIndex++; - targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex]); + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); } } } @@ -532,7 +532,6 @@ namespace Recyclable.Collections Span blockArraySpan; if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { - int requiredCapacity = targetItemIdx + requiredAdditionalCapacity; if (capacity < requiredCapacity) { From 1ea0af8d344bf9db7e5308c95b1e9fa268d464d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 22:12:50 +0100 Subject: [PATCH 129/305] RecyclableListExtensions.CopyTo fixed to copy items from the last block in the range --- Recyclable.Collections/RecyclableListExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index e873729..7075a32 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -17,12 +17,12 @@ namespace Recyclable.Collections public static void CopyTo(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex) { - Memory sourceBlockMemory; - Memory destinationArrayMemory; + Span sourceBlockMemory; + Span 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); + destinationArrayMemory = new Span(destinationArray, destinationArrayIndex, destinationArray.Length - destinationArrayIndex); int memoryBlockIndex; for (memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++) { @@ -33,7 +33,7 @@ namespace Recyclable.Collections if (itemsCount % blockSize > 0) { - sourceBlockMemory = new(sourceMemoryBlocksSpan[^1], 0, (int)(itemsCount % blockSize)); + sourceBlockMemory = new(sourceMemoryBlocksSpan[lastBlockIndex], 0, (int)(itemsCount % blockSize)); sourceBlockMemory.CopyTo(destinationArrayMemory); } } From 91fae49bdb812f568bbe03fd846b684a71c1db90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 23:15:04 +0100 Subject: [PATCH 130/305] RecyclableList.AddRange(IEnumerable source) fixed to properly add items when we can get non-enumerated count. --- Recyclable.Collections/RecyclableList.cs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 754b83e..1451540 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -525,42 +525,40 @@ namespace Recyclable.Collections long capacity = _capacity; long oldLongCount = _longCount; int blockSize = _blockSize; - int targetItemIdx = (int)(oldLongCount % blockSize); - int targetBlockIdx = (targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0); + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _lastBlockIndex; Span memoryBlocksSpan; Span blockArraySpan; if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { - int requiredCapacity = targetItemIdx + requiredAdditionalCapacity; + long requiredCapacity = oldLongCount + requiredAdditionalCapacity; if (capacity < requiredCapacity) { _ = EnsureCapacity(requiredCapacity); blockSize = _blockSize; - targetBlockIdx = (targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0); - targetItemIdx = (int)(oldLongCount % blockSize); } - memoryBlocksSpan = new(_memoryBlocks); - var memoryBlocksCount = memoryBlocksSpan.Length; - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx]); + var memoryBlocksCount = _memoryBlocks.Length; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); foreach (var item in source) { blockArraySpan[targetItemIdx++] = item; if (targetItemIdx == blockSize) - { + { targetBlockIdx++; + targetItemIdx = 0; if (targetBlockIdx == memoryBlocksCount) { break; } - targetItemIdx = 0; - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx]); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); } } - _longCount = (targetBlockIdx * blockSize) + targetItemIdx; + _longCount = requiredCapacity; _lastBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; return; From 25a85ec76311a97675fa0fd3f83402bd1039a531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 23:16:15 +0100 Subject: [PATCH 131/305] New tests added. AddRangeShouldAddItemsInCorrectOrder & AddRangeShouldNotOverrideItems converted to theories with [MemberData] --- Recyclable.CollectionsTests/CustomIList.cs | 33 +++ .../RecyclableListTests.cs | 253 +++++++----------- 2 files changed, 128 insertions(+), 158 deletions(-) create mode 100644 Recyclable.CollectionsTests/CustomIList.cs diff --git a/Recyclable.CollectionsTests/CustomIList.cs b/Recyclable.CollectionsTests/CustomIList.cs new file mode 100644 index 0000000..6d191b8 --- /dev/null +++ b/Recyclable.CollectionsTests/CustomIList.cs @@ -0,0 +1,33 @@ +using System.Collections; + +namespace Recyclable.CollectionsTests +{ + public class CustomIList : IList + { + protected List _list; + + public CustomIList() + { + _list = new List(); + } + + public CustomIList(IEnumerable items) + { + _list = new List(items); + } + + public T this[int index] { get => _list[index]; set => _list[index] = value; } + public int Count => _list.Count; + public bool IsReadOnly => false; + public void Add(T item) => _list.Add(item); + public void Clear() => _list.Clear(); + public bool Contains(T item) => _list.Contains(item); + public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + public int IndexOf(T item) => _list.IndexOf(item); + public void Insert(int index, T item) => _list.Insert(index, item); + public bool Remove(T item) => _list.Remove(item); + public void RemoveAt(int index) => _list.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + } +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 2a21ca8..e6c927a 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -314,99 +314,112 @@ namespace Recyclable.CollectionsTests _ = list.LongCount.Should().Be(0); } - [Fact] - public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsArray() + public static IEnumerable AddRangeCollectionTypes => new[] { - // Prepare - var testData = _testData.ToArray(); - using var list = new RecyclableList(); + new object[] { "int[]", _testData.ToArray() }, + new object[] { "List", _testData.ToList() }, + new object[] { "RecyclableArrayList", _testData.ToRecyclableArrayList() }, + new object[] { "RecyclableList", _testData.ToRecyclableList() }, + new object[] { "IList", new CustomIList(_testData) }, + new object[] { "IEnumerable", _testData }, + }; + [Theory] + [MemberData(nameof(AddRangeCollectionTypes))] + public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData) + { + try + { + // Prepare + using var list = new RecyclableList(); - // Act - list.AddRange(testData); + // Act + if (testData is int[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableArrayList testDataRecyclableArrayList) + { + list.AddRange(testDataRecyclableArrayList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } - // Validate - _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData); + // Validate + int expectedItemsCount = testData.Count(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); + _ = list.Should().HaveCount(expectedItemsCount) + .And.ContainInConsecutiveOrder(testData); + } + finally + { + if (testData is IDisposable disposable) + { + disposable.Dispose(); + } + } } - [Fact] - public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsList() + [Theory] + [MemberData(nameof(AddRangeCollectionTypes))] + public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData) { // Prepare - var testData = _testData.ToList(); - using var list = new RecyclableList(); - - // Act - list.AddRange(testData); - - // Validate - _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(testData.Count) - .And.ContainInConsecutiveOrder(testData); - } - - [Fact] - public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsIList() - { - // Prepare - var testData = (IList)_testData.ToList(); - using var list = new RecyclableList(); - - // Act - list.AddRange(testData); - - // Validate - _ = list.Capacity.Should().BeGreaterThanOrEqualTo(testData.Count); - _ = list.Should().HaveCount(testData.Count) - .And.ContainInConsecutiveOrder(testData); - } - - [Fact] - public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsRecyclableArrayList() - { - // Prepare - var testData = _testData.ToRecyclableArrayList(); - using var list = new RecyclableList(); - - // Act - list.AddRange(testData); - - // Validate - _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(testData.Count) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); - } - - [Fact] - public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsRecyclableList() - { - // Prepare - var testData = _testData.ToRecyclableList(); - using var list = new RecyclableList(); - - // Act - list.AddRange(testData); - - // Validate - _ = list.Capacity.Should().Be(114_688, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(testData.Count) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); - } - - [Fact] - public void AddRangeShouldNotOverrideItemsWhenSourceIsArray() - { - // Prepare - var testData = _testData.ToArray(); var list = new RecyclableList(); // Act - list.AddRange(testData); - list.AddRange(testData); + if (testData is int[] testDataArray) + { + list.AddRange(testDataArray); + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + list.AddRange(testDataList); + } + else if (testData is RecyclableArrayList testDataRecyclableArrayList) + { + list.AddRange(testDataRecyclableArrayList); + list.AddRange(testDataRecyclableArrayList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } // Validate var expectedData = testData.Concat(testData).ToArray(); @@ -416,82 +429,6 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(expectedData); } - [Fact] - public void AddRangeShouldNotOverrideItemsWhenSourceIsIList() - { - // Prepare - var testData = (IList)_testData.ToList(); - var list = new RecyclableList(); - - // Act - list.AddRange(testData); - list.AddRange(testData); - - // Validate - var expectedData = testData.Concat(testData).ToArray(); - _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(expectedData.Length) - .And.ContainInConsecutiveOrder(expectedData) - .And.BeEquivalentTo(expectedData); - } - - [Fact] - public void AddRangeShouldNotOverrideItemsWhenSourceIsList() - { - // Prepare - var testData = _testData.ToList(); - var list = new RecyclableList(); - - // Act - list.AddRange(testData); - list.AddRange(testData); - - // Validate - var expectedData = testData.Concat(testData).ToArray(); - _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(expectedData.Length) - .And.ContainInConsecutiveOrder(expectedData) - .And.BeEquivalentTo(expectedData); - } - - [Fact] - public void AddRangeShouldNotOverrideItemsWhenSourceIsRecyclableArrayList() - { - // Prepare - using var testData = _testData.ToRecyclableArrayList(); - using var list = new RecyclableList(); - - // Act - list.AddRange(testData); - list.AddRange(testData); - - // Validate - var expectedData = _testData.Concat(_testData).ToArray(); - _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(expectedData.Length) - .And.ContainInConsecutiveOrder(expectedData) - .And.BeEquivalentTo(expectedData); - } - - [Fact] - public void AddRangeShouldNotOverrideItemsWhenSourceIsRecyclableList() - { - // Prepare - using var testData = _testData.ToRecyclableList(); - using var list = new RecyclableList(); - - // Act - list.AddRange(testData); - list.AddRange(testData); - - // Validate - var expectedData = _testData.Concat(_testData).ToArray(); - _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(expectedData.Length) - .And.ContainInConsecutiveOrder(expectedData) - .And.BeEquivalentTo(expectedData); - } - [Fact] public void InsertAtTheBeginningShouldMoveItems() { From 025cdb307366c6ad36b130fa642523e34df16ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 11 Mar 2023 23:19:59 +0100 Subject: [PATCH 132/305] Tiny cleanup --- Recyclable.CollectionsTests/RecyclableListTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index e6c927a..ad9b1b9 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -150,7 +150,6 @@ namespace Recyclable.CollectionsTests _ = list.Contains(1).Should().BeFalse(); } - [Fact] public void CopyToShouldCopyAllItems() { @@ -488,7 +487,6 @@ namespace Recyclable.CollectionsTests _ = list.LongIndexOf(1).Should().Be(-1); } - [Fact] public void ConstructorSourceShouldInitializeList() { From efb393e13fb0046177bcedb329f8ca2f0d9ce3a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 12 Mar 2023 03:23:13 +0100 Subject: [PATCH 133/305] Recyclable.AddRange fixed to ignore empty sources --- Recyclable.Collections/RecyclableList.cs | 25 +++++++++ .../EnumerableWithoutCount.cs | 17 ++++++ .../RecyclableListTests.cs | 53 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 Recyclable.CollectionsTests/EnumerableWithoutCount.cs diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 1451540..a862503 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -275,6 +275,11 @@ namespace Recyclable.Collections public void AddRange(in T[] items) { + if (items.LongLength == 0) + { + return; + } + long targetCapacity = _longCount + items.LongLength; if (_capacity < targetCapacity) { @@ -309,6 +314,11 @@ namespace Recyclable.Collections public void AddRange(RecyclableArrayList items) { + if (items.Count == 0) + { + return; + } + long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { @@ -343,6 +353,11 @@ namespace Recyclable.Collections public void AddRange(RecyclableList items) { + if (items.LongCount == 0) + { + return; + } + long itemsCount = items.LongCount, targetCapacity = _longCount + itemsCount; @@ -410,6 +425,11 @@ namespace Recyclable.Collections public void AddRange(List items) { + if (items.Count == 0) + { + return; + } + long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { @@ -445,6 +465,11 @@ namespace Recyclable.Collections public void AddRange(IList items) { + if (items.Count == 0) + { + return; + } + long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { diff --git a/Recyclable.CollectionsTests/EnumerableWithoutCount.cs b/Recyclable.CollectionsTests/EnumerableWithoutCount.cs new file mode 100644 index 0000000..b6c08b3 --- /dev/null +++ b/Recyclable.CollectionsTests/EnumerableWithoutCount.cs @@ -0,0 +1,17 @@ +using System.Collections; + +namespace Recyclable.CollectionsTests +{ + public class EnumerableWithoutCount : IEnumerable + { + private IEnumerable _testData; + + public EnumerableWithoutCount(IEnumerable testData) + { + _testData = testData; + } + + public IEnumerator GetEnumerator() => _testData.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _testData.GetEnumerator(); + } +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index ad9b1b9..8b538cd 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -323,6 +323,17 @@ namespace Recyclable.CollectionsTests new object[] { "IEnumerable", _testData }, }; + public static IEnumerable AddRangeEmptyCollectionTypes => new[] + { + new object[] { "int[]", Array.Empty() }, + new object[] { "List", Array.Empty().ToList() }, + new object[] { "RecyclableArrayList", Array.Empty().ToRecyclableArrayList() }, + new object[] { "RecyclableList", Array.Empty().ToRecyclableList() }, + new object[] { "IList", new CustomIList(Array.Empty()) }, + new object[] { "IEnumerable with non-enumerated count", Array.Empty().AsEnumerable() }, + new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(Array.Empty()) } + }; + [Theory] [MemberData(nameof(AddRangeCollectionTypes))] public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData) @@ -428,6 +439,48 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(expectedData); } + [Theory] + [MemberData(nameof(AddRangeEmptyCollectionTypes))] + public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) + { + // Prepare + var list = new RecyclableList(); + + // Act + if (testData is int[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableArrayList testDataRecyclableArrayList) + { + list.AddRange(testDataRecyclableArrayList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + _ = list.Capacity.Should().Be(0); + _ = list.Should().BeEmpty().And.HaveCount(0); + } + [Fact] public void InsertAtTheBeginningShouldMoveItems() { From c211fb6ee484e1cda07cfbf8b3feec14f8ca3271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 12 Mar 2023 03:44:07 +0100 Subject: [PATCH 134/305] RecyclableList.AddRange(IEnumerable source) performance improvements. Small source code cleanup. --- Recyclable.Collections/RecyclableList.cs | 57 ++++++++++++------- .../RecyclableListTests.cs | 6 +- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index a862503..3a4a6d1 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -548,7 +548,7 @@ namespace Recyclable.Collections } long capacity = _capacity; - long oldLongCount = _longCount; + long copied = _longCount; int blockSize = _blockSize; int targetItemIdx = _nextItemIndex; int targetBlockIdx = _lastBlockIndex; @@ -557,7 +557,7 @@ namespace Recyclable.Collections Span blockArraySpan; if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { - long requiredCapacity = oldLongCount + requiredAdditionalCapacity; + long requiredCapacity = copied + requiredAdditionalCapacity; if (capacity < requiredCapacity) { _ = EnsureCapacity(requiredCapacity); @@ -594,51 +594,66 @@ namespace Recyclable.Collections if (enumerator.MoveNext()) { - long available = capacity - targetItemIdx; - memoryBlocksSpan = new(_memoryBlocks); + long available = capacity - copied, + initialCount = copied; + + memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); var memoryBlocksCount = memoryBlocksSpan.Length; while (true) { - if (targetItemIdx + growByCount > capacity) + if (copied + growByCount > capacity) { capacity = EnsureCapacity(capacity + growByCount); - memoryBlocksSpan = new(_memoryBlocks); - memoryBlocksCount = memoryBlocksSpan.Length; - available = capacity - targetItemIdx; + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; } - blockArraySpan = memoryBlocksSpan[targetBlockIdx]; - for (i = 0; i < available; i++) + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) { blockArraySpan[targetItemIdx++] = enumerator.Current; + if (!enumerator.MoveNext()) { - break; + copied += i; + if (targetItemIdx < blockSize) + { + _lastBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _lastBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied - _longCount; + return; } if (targetItemIdx == blockSize) { targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) { break; } - targetItemIdx = 0; - blockArraySpan = new(memoryBlocksSpan[targetBlockIdx]); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); } } - if (i < available) - { - break; - } + copied += available; } } - - _longCount = targetItemIdx; - _lastBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; } public void Clear() diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 8b538cd..8cbac3b 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -320,7 +320,8 @@ namespace Recyclable.CollectionsTests new object[] { "RecyclableArrayList", _testData.ToRecyclableArrayList() }, new object[] { "RecyclableList", _testData.ToRecyclableList() }, new object[] { "IList", new CustomIList(_testData) }, - new object[] { "IEnumerable", _testData }, + new object[] { "IEnumerable with non-enumerated count", _testData }, + new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(_testData) } }; public static IEnumerable AddRangeEmptyCollectionTypes => new[] @@ -433,7 +434,8 @@ namespace Recyclable.CollectionsTests // Validate var expectedData = testData.Concat(testData).ToArray(); - _ = list.Capacity.Should().Be(229376L, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedData.Length) + .And.BeLessThan((int)(expectedData.Length * 1.5)); _ = list.Should().HaveCount(expectedData.Length) .And.ContainInConsecutiveOrder(expectedData) .And.BeEquivalentTo(expectedData); From 5572c7c57ca8856d58c679e0434b94ccee10ba26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 12 Mar 2023 03:45:41 +0100 Subject: [PATCH 135/305] Tiny cleanup. AddRange will now favor RecyclableList & RecyclableArrayList parameters. --- 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 3a4a6d1..cff289f 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -517,15 +517,9 @@ namespace Recyclable.Collections public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { - if (source is T[] sourceArray) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceArray); - return; - } - - if (source is List sourceList) - { - AddRange(sourceList); + AddRange(sourceRecyclableList); return; } @@ -535,9 +529,15 @@ namespace Recyclable.Collections return; } - if (source is RecyclableList sourceRecyclableList) + if (source is T[] sourceArray) { - AddRange(sourceRecyclableList); + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); return; } From addef85027aaaf5a6ab0efcac37cbbca46668a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 12 Mar 2023 15:41:29 +0100 Subject: [PATCH 136/305] RecyclableList.AddRange(IEnumerable source) performance improvements. --- Recyclable.Collections/RecyclableList.cs | 218 ++++++++++++----------- 1 file changed, 116 insertions(+), 102 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index cff289f..dca2c75 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -78,6 +78,120 @@ namespace Recyclable.Collections throw new ArgumentOutOfRangeException("index"); } + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _lastBlockIndex; + + IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _lastBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _lastBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _lastBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = _memoryBlocks.Length; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _lastBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) { Span memoryBlocksSpan = new(memoryBlocks); @@ -547,113 +661,13 @@ namespace Recyclable.Collections return; } - long capacity = _capacity; - long copied = _longCount; - int blockSize = _blockSize; - int targetItemIdx = _nextItemIndex; - int targetBlockIdx = _lastBlockIndex; - - Span memoryBlocksSpan; - Span blockArraySpan; if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { - long requiredCapacity = copied + requiredAdditionalCapacity; - if (capacity < requiredCapacity) - { - _ = EnsureCapacity(requiredCapacity); - blockSize = _blockSize; - } - - var memoryBlocksCount = _memoryBlocks.Length; - memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - foreach (var item in source) - { - blockArraySpan[targetItemIdx++] = item; - if (targetItemIdx == blockSize) - { - targetBlockIdx++; - targetItemIdx = 0; - if (targetBlockIdx == memoryBlocksCount) - { - break; - } - - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - } - } - - _longCount = requiredCapacity; - _lastBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; + AddRangeWithKnownCount(source, requiredAdditionalCapacity); return; } - long i; - using var enumerator = source.GetEnumerator(); - - if (enumerator.MoveNext()) - { - long available = capacity - copied, - initialCount = copied; - - memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); - var memoryBlocksCount = memoryBlocksSpan.Length; - while (true) - { - if (copied + growByCount > capacity) - { - capacity = EnsureCapacity(capacity + growByCount); - memoryBlocksCount = _memoryBlocks.Length; - if (blockSize != _blockSize) - { - blockSize = _blockSize; - } - - memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); - available = capacity - copied; - } - - blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - for (i = 1; i <= available; i++) - { - blockArraySpan[targetItemIdx++] = enumerator.Current; - - if (!enumerator.MoveNext()) - { - copied += i; - if (targetItemIdx < blockSize) - { - _lastBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; - } - else - { - _lastBlockIndex = targetBlockIdx + 1; - _nextItemIndex = 0; - } - - _longCount += copied - _longCount; - return; - } - - if (targetItemIdx == blockSize) - { - targetBlockIdx++; - targetItemIdx = 0; - - if (targetBlockIdx == memoryBlocksCount) - { - break; - } - - blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - } - } - - copied += available; - } - } + AddRangeEnumerated(source, growByCount); } public void Clear() From bf392d60ed20bd3ef3919ef331f28e55ea03fc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 12 Mar 2023 16:41:28 +0100 Subject: [PATCH 137/305] Tiny cleanup - "in" removed, where doesn't add value. --- 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 dca2c75..706b33d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -210,7 +210,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, in long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { const int sourceBlockCount = 0, requiredBlockCount = 1, uninitializedBlocksCount = 2, i = 3; @@ -274,7 +274,7 @@ namespace Recyclable.Collections return newMemoryBlocks; } - protected long EnsureCapacity(in long requestedCapacity) + protected long EnsureCapacity(long requestedCapacity) { T[][] memory = _memoryBlocks; long newCapacity; From bdf69f300d64d8122204f29885ea356d90df1b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 12 Mar 2023 18:01:30 +0100 Subject: [PATCH 138/305] Added test cases for various block sizes & items counts. --- .../RecyclableListExtensions.cs | 2 +- .../RecyclableListTests.cs | 111 +++++++++++++----- 2 files changed, 85 insertions(+), 28 deletions(-) diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 7075a32..d9a19a1 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -137,7 +137,7 @@ namespace Recyclable.Collections public static RecyclableArrayList ToRecyclableArrayList(this RecyclableArrayList values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + public static RecyclableList ToRecyclableList(this IEnumerable values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] RentArrayFromPool(this int minSize, ArrayPool arrayPool) => (minSize >= _minPooledArraySize) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 8cbac3b..5125fd3 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -10,44 +10,93 @@ namespace Recyclable.CollectionsTests private static readonly IEnumerable _testData = Enumerable.Range(1, _totalObjectCount); private static RecyclableList CreateReversedRecyclableList(IEnumerable source) => new(source, 1024); - [Fact] - public void AddShouldAddItems() + private static IEnumerable ItemsCountVariants => new[] + { + 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, 8_192, 16_384, + 32_768, 65_536, 131_072, 262_144, 524_288, 1_048_576, 10_000_000 + }; + + private static IEnumerable BlockSizeVariants = new[] + { + 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, + 8_192, 16_384, 32_768, 65_536, 131_072, 262_144, 524_288, 1_048_576 + }; + + public static IEnumerable TargetTestDataVariants() + { + foreach (var itemsCount in ItemsCountVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + yield return new object[] { itemsCount, targetBlockSize }; + } + } + } + + public static IEnumerable TestDataVariants() + { + foreach (var itemsCount in ItemsCountVariants) + { + foreach (var sourceBlockSize in BlockSizeVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + yield return new object[] { itemsCount, sourceBlockSize, targetBlockSize }; + } + } + } + } + + public static IEnumerable TestDataVariantsWithData() + { + foreach (var testData in AddRangeCollectionTypes) + { + foreach (var itemsCount in ItemsCountVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + yield return new object[] { testData[0], testData[1], itemsCount, targetBlockSize }; + } + } + } + } + + [Theory] + [MemberData(nameof(TargetTestDataVariants))] + public void AddShouldAddItems(int itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(); + using var list = new RecyclableList(minBlockSize: targetBlockSize); + var testData = Enumerable.Range(1, itemsCount); // Act - const int itemsCount = 5; - foreach (var index in Enumerable.Range(1, itemsCount)) + foreach (var index in testData) { list.Add(index); } // Validate _ = list.LongCount.Should().Be(itemsCount, "we added so many items"); - for (var index = 0; index < itemsCount; index++) - { - var actual = list[index]; - _ = actual.Should().Be(index + 1); - } + _ = list.Should().ContainInConsecutiveOrder(testData); } - [Fact] - public void AddShouldAcceptDuplicates() + [Theory] + [MemberData(nameof(TargetTestDataVariants))] + public void AddShouldAcceptDuplicates(int itemsCount, int targetBlockSize) { - // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; - + IEnumerable testNumbers = Enumerable.Range(1, itemsCount) + .Concat(Enumerable.Range(1, itemsCount)) + .ToArray(); // Act - using var list = new RecyclableList(); + using var list = new RecyclableList(minBlockSize: targetBlockSize); foreach (var index in testNumbers) { list.Add(index); } // Validate - _ = list.LongCount.Should().Be(testNumbers.Length); - _ = list.Should().BeEquivalentTo(testNumbers); + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().ContainInConsecutiveOrder(testNumbers); } [Fact] @@ -313,16 +362,24 @@ namespace Recyclable.CollectionsTests _ = list.LongCount.Should().Be(0); } - public static IEnumerable AddRangeCollectionTypes => new[] + public static IEnumerable AddRangeCollectionTypes { - new object[] { "int[]", _testData.ToArray() }, - new object[] { "List", _testData.ToList() }, - new object[] { "RecyclableArrayList", _testData.ToRecyclableArrayList() }, - new object[] { "RecyclableList", _testData.ToRecyclableList() }, - new object[] { "IList", new CustomIList(_testData) }, - new object[] { "IEnumerable with non-enumerated count", _testData }, - new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(_testData) } - }; + get + { + yield return new object[] { "int[]", _testData.ToArray() }; + yield return new object[] { "List", _testData.ToList() }; + yield return new object[] { "RecyclableArrayList", _testData.ToRecyclableArrayList() }; + foreach (var sourceBlockSize in BlockSizeVariants) + { + yield return new object[] { $"RecyclableList(minBlockSize: {sourceBlockSize})", _testData.ToRecyclableList(sourceBlockSize) }; + + } + + yield return new object[] { "IList", new CustomIList(_testData) }; + yield return new object[] { "IEnumerable with non-enumerated count", _testData }; + yield return new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(_testData) }; + } + } public static IEnumerable AddRangeEmptyCollectionTypes => new[] { From ca6ee45bb6578ba456ecdadc2a1e95aed38171f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 16 Mar 2023 02:20:16 +0100 Subject: [PATCH 139/305] RecyclableCollections.AddRange(List items) fixes to exceptions found by tests. --- Recyclable.Collections/RecyclableList.cs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 706b33d..94e9787 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -32,6 +32,7 @@ namespace Recyclable.Collections public int Count => (int)_longCount; protected long _longCount; + public long LongCount { get => _longCount; @@ -552,26 +553,32 @@ namespace Recyclable.Collections int blockSize = _blockSize, targetBlockIndex = _lastBlockIndex, - copied = blockSize - _nextItemIndex, - itemsCount = items.Count; + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); - items.CopyTo(0, memoryBlocksSpan[targetBlockIndex++], _nextItemIndex, copied); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + while (blockSize <= itemsCount - copied) { items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); copied += blockSize; } - if (itemsCount - copied != 0) + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) { _nextItemIndex = itemsCount - copied; items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); } - else - { - _nextItemIndex = 0; - } _lastBlockIndex = targetBlockIndex; _longCount = targetCapacity; From c2eefdfe3b75c5f8f61ed6285bd53dd420ed0ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 16 Mar 2023 02:21:14 +0100 Subject: [PATCH 140/305] RecyclableCollections.Contains(T item) fixes to exceptions found by tests. --- 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 94e9787..6c4323e 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -719,7 +719,7 @@ namespace Recyclable.Collections } } - return Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); From cf284865e9d6846f01579066f632bceb3c073344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 16 Mar 2023 02:24:18 +0100 Subject: [PATCH 141/305] RecyclableList finalizer added to make sure that the recyclable arrays are correctly returned. --- Recyclable.Collections/RecyclableList.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 6c4323e..a67aeb7 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -779,8 +779,15 @@ namespace Recyclable.Collections IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + ~RecyclableList() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + public void Dispose() { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method if (_capacity > 0) { Clear(); From 5235ea12bcc6d206fed35f17acf10f6ff9fe9312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 17 Mar 2023 01:40:56 +0100 Subject: [PATCH 142/305] RecyclableListExtensions.CopyTo fixed to not raise exceptions in some configurations. --- .../RecyclableListExtensions.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index d9a19a1..4b51d43 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -17,12 +17,17 @@ namespace Recyclable.Collections public static void CopyTo(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex) { + if (itemsCount <= 0) + { + return; + } + Span sourceBlockMemory; Span 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 Span(destinationArray, destinationArrayIndex, destinationArray.Length - destinationArrayIndex); + int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize > 0 ? 1 : 0)) - 1; + Span sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1); + destinationArrayMemory = new Span(destinationArray, destinationArrayIndex, (int)Math.Min(destinationArray.Length - destinationArrayIndex, itemsCount)); int memoryBlockIndex; for (memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++) { @@ -31,9 +36,9 @@ namespace Recyclable.Collections destinationArrayMemory = destinationArrayMemory[blockSize..]; } - if (itemsCount % blockSize > 0) + if (itemsCount - (lastBlockIndex * blockSize) > 0) { - sourceBlockMemory = new(sourceMemoryBlocksSpan[lastBlockIndex], 0, (int)(itemsCount % blockSize)); + sourceBlockMemory = new(sourceMemoryBlocksSpan[lastBlockIndex], 0, (int)(itemsCount - (lastBlockIndex * blockSize))); sourceBlockMemory.CopyTo(destinationArrayMemory); } } From 3e8f03e59f7fce1b3d670a812bf2528b98f9639c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 17 Mar 2023 01:42:30 +0100 Subject: [PATCH 143/305] RecyclableList tests extended & parameterized for various combinations of block sizes in source & target lists, including different no. of items. --- .../RecyclableListTestData.cs | 177 +++++++ .../RecyclableListTests.cs | 496 ++++++++---------- 2 files changed, 393 insertions(+), 280 deletions(-) create mode 100644 Recyclable.CollectionsTests/RecyclableListTestData.cs diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs new file mode 100644 index 0000000..812ef35 --- /dev/null +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -0,0 +1,177 @@ +using Recyclable.Collections; + +namespace Recyclable.CollectionsTests +{ + public class RecyclableListTestData : IDisposable + { + private static void DisposeTestData(IEnumerable? testCases) + { + if (testCases != null) + { + foreach (var testData in testCases) + { + foreach (var item in testData) + { + if (item is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } + } + + private static void DisposeTestData(IEnumerable>? testCases) + { + if (testCases != null) + { + foreach (var testData in testCases) + { + if (testData is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } + + private static Dictionary>? _testCases = new(); + + public RecyclableListTestData() + { + _testCases ??= new(); + } + + private static IEnumerable DoCreateTestData(long itemsCount) + { + for (long index = 1; index <= itemsCount; index++) + { + yield return index; + } + } + + public static IEnumerable CreateTestData(long itemsCount) + { + if (_testCases!.TryGetValue(itemsCount, out var testCases)) + { + return testCases; + } + + testCases = DoCreateTestData(itemsCount).ToArray(); + _testCases.Add(itemsCount, testCases); + return testCases; + } + + public void Dispose() + { + DisposeTestData(_testCases?.Values); + DisposeTestData(_sourceDataVariants); + DisposeTestData(_emptySourceDataVariants); + _testCases = null; + _sourceDataVariants = null; + _targetDataVariants = null; + _testDataVariants = null; + _sourceTargetDataVariants = null; + _emptySourceDataVariants = null; + GC.SuppressFinalize(this); + } + + public static readonly IEnumerable ItemsCountVariants = new long[] + { + 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, 8_192, 16_384, + 32_768, 65_536, 131_072, 262_144, 524_288, 1_048_576, 10_000_000 + }; + + public static readonly IEnumerable BlockSizeVariants = new int[] + { + 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, + 8_192, 16_384, 32_768, 65_536, 131_072, 262_144, 524_288, 1_048_576 + }; + + private static IEnumerable GetTargetDataVariants() + { + foreach (var itemsCount in ItemsCountVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + yield return new object[] { itemsCount, targetBlockSize }; + } + } + } + + private static IEnumerable? _targetDataVariants; + public static IEnumerable TargetDataVariants => _targetDataVariants ??= GetTargetDataVariants().ToArray(); + + private static IEnumerable GetTestDataVariants() + { + foreach (var itemsCount in ItemsCountVariants) + { + foreach (var sourceBlockSize in BlockSizeVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + yield return new object[] { itemsCount, sourceBlockSize, targetBlockSize }; + } + } + } + } + + private static IEnumerable? _testDataVariants; + public static IEnumerable TestDataVariants => _testDataVariants ??= GetTestDataVariants().ToArray(); + + private static IEnumerable CreateSourceDataVariants() + { + foreach (var itemsCount in ItemsCountVariants) + { + yield return new object[] { $"int[{itemsCount}]", CreateTestData(itemsCount).ToArray(), itemsCount }; + yield return new object[] { $"List(itemsCount: {itemsCount}", CreateTestData(itemsCount).ToList(), itemsCount }; + yield return new object[] { $"RecyclableArrayList(itemsCount: {itemsCount})", CreateTestData(itemsCount).ToRecyclableArrayList(), itemsCount }; + foreach (var sourceBlockSize in BlockSizeVariants) + { + yield return new object[] + { + $"RecyclableList(itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize})", + CreateTestData(itemsCount).ToRecyclableList(sourceBlockSize), + itemsCount + }; + + } + + yield return new object[] { $"IList(itemsCount: {itemsCount})", new CustomIList(CreateTestData(itemsCount)), itemsCount }; + yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) with non-enumerated count", CreateTestData(itemsCount), itemsCount }; + yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) without non-enumerated count", new EnumerableWithoutCount(CreateTestData(itemsCount)), itemsCount }; + } + } + + private static IEnumerable? _sourceDataVariants; + public static IEnumerable SourceDataVariants => _sourceDataVariants ??= CreateSourceDataVariants().ToArray(); + + private static IEnumerable CreateSourceEmptyDataVariants() => new[] + { + new object[] { "int[]", Array.Empty() }, + new object[] { "List", Array.Empty().ToList() }, + new object[] { "RecyclableArrayList", Array.Empty().ToRecyclableArrayList() }, + new object[] { "RecyclableList", Array.Empty().ToRecyclableList() }, + new object[] { "IList", new CustomIList(Array.Empty()) }, + new object[] { "IEnumerable with non-enumerated count", Array.Empty().AsEnumerable() }, + new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(Array.Empty()) } + }; + + private static IEnumerable? _emptySourceDataVariants; + public static IEnumerable EmptySourceDataVariants => _emptySourceDataVariants ??= CreateSourceEmptyDataVariants().ToArray(); + + private static IEnumerable CreateSourceTargetDataVariants() + { + foreach (var testData in SourceDataVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + yield return new object[] { testData[0], testData[1], testData[2], targetBlockSize }; + } + } + } + + private static IEnumerable? _sourceTargetDataVariants; + public static IEnumerable SourceTargetDataVariants => _sourceTargetDataVariants ??= CreateSourceTargetDataVariants().ToArray(); + } +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 5125fd3..56c518f 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -5,69 +5,12 @@ namespace Recyclable.CollectionsTests { public class RecyclableListTests { - private const int _totalObjectCount = 100_000; - private static RecyclableList NewRecyclableList => CreateReversedRecyclableList(_testData); - private static readonly IEnumerable _testData = Enumerable.Range(1, _totalObjectCount); - private static RecyclableList CreateReversedRecyclableList(IEnumerable source) => new(source, 1024); - - private static IEnumerable ItemsCountVariants => new[] - { - 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, 8_192, 16_384, - 32_768, 65_536, 131_072, 262_144, 524_288, 1_048_576, 10_000_000 - }; - - private static IEnumerable BlockSizeVariants = new[] - { - 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, - 8_192, 16_384, 32_768, 65_536, 131_072, 262_144, 524_288, 1_048_576 - }; - - public static IEnumerable TargetTestDataVariants() - { - foreach (var itemsCount in ItemsCountVariants) - { - foreach (var targetBlockSize in BlockSizeVariants) - { - yield return new object[] { itemsCount, targetBlockSize }; - } - } - } - - public static IEnumerable TestDataVariants() - { - foreach (var itemsCount in ItemsCountVariants) - { - foreach (var sourceBlockSize in BlockSizeVariants) - { - foreach (var targetBlockSize in BlockSizeVariants) - { - yield return new object[] { itemsCount, sourceBlockSize, targetBlockSize }; - } - } - } - } - - public static IEnumerable TestDataVariantsWithData() - { - foreach (var testData in AddRangeCollectionTypes) - { - foreach (var itemsCount in ItemsCountVariants) - { - foreach (var targetBlockSize in BlockSizeVariants) - { - yield return new object[] { testData[0], testData[1], itemsCount, targetBlockSize }; - } - } - } - } - [Theory] - [MemberData(nameof(TargetTestDataVariants))] - public void AddShouldAddItems(int itemsCount, int targetBlockSize) + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(minBlockSize: targetBlockSize); - var testData = Enumerable.Range(1, itemsCount); + using var list = new RecyclableList(minBlockSize: targetBlockSize); // Act foreach (var index in testData) @@ -81,70 +24,69 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(TargetTestDataVariants))] - public void AddShouldAcceptDuplicates(int itemsCount, int targetBlockSize) + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { - IEnumerable testNumbers = Enumerable.Range(1, itemsCount) - .Concat(Enumerable.Range(1, itemsCount)) - .ToArray(); + // Prepare + testData = testData.Concat(testData).ToArray(); + // Act - using var list = new RecyclableList(minBlockSize: targetBlockSize); - foreach (var index in testNumbers) + using var list = new RecyclableList(minBlockSize: targetBlockSize); + foreach (var index in testData) { list.Add(index); } // Validate _ = list.LongCount.Should().Be(itemsCount * 2); - _ = list.Should().ContainInConsecutiveOrder(testNumbers); + _ = list.Should().ContainInConsecutiveOrder(testData); } - [Fact] - public void AddShouldAddItemWhenAfterAddRange() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + //[InlineData("Debugging case", new long[1] { 1 }, 1, 2)] + public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { - // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; - // Act - using var list = new RecyclableList(); - list.AddRange(_testData); - foreach (var index in testNumbers) + using var list = new RecyclableList(minBlockSize: targetBlockSize); + list.AddRange(testData); + foreach (var index in testData) { list.Add(index); } // Validate - _ = list.LongCount.Should().Be(_totalObjectCount + testNumbers.Length); - _ = list.Should().BeEquivalentTo(_testData.Concat(testNumbers)); + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); } - [Fact] - public void ConstructorShouldAcceptDuplicates() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; + testData = testData.Concat(testData).ToArray(); // Act - using var list = new RecyclableList(testNumbers); + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Validate - _ = list.LongCount.Should().Be(testNumbers.Length); - _ = list.Should().BeEquivalentTo(testNumbers); + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(testData); } - [Fact] - public void InitializeSortClearShouldSucceed() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void InitializeSortClearShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { - var expectedList = _testData; - // Act - using RecyclableList list = NewRecyclableList; + using RecyclableList list = new(testData, minBlockSize: targetBlockSize); list.QuickSort(); // Validate - _ = list.LongCount.Should().Be(_totalObjectCount); - _ = list.Should().Equal(expectedList); - _ = expectedList.Should().AllSatisfy(expected => list.Contains(expected)); + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(testData); + _ = testData.Should().AllSatisfy(expected => list.Contains(expected)); // Act list.Clear(); @@ -153,12 +95,18 @@ namespace Recyclable.CollectionsTests _ = list.LongCount.Should().Be(0); } - [Fact] - public void ContainsShouldFindAllItems() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ContainsShouldFindAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - var testData = _testData; - using var list = NewRecyclableList; + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -169,12 +117,18 @@ namespace Recyclable.CollectionsTests } } - [Fact] - public void ContainsShouldNotFindNonExistingItems() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - var testData = _testData; - using var list = NewRecyclableList; + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -184,41 +138,45 @@ namespace Recyclable.CollectionsTests _ = list.Contains(-item).Should().BeFalse(); } - // Validate + // Validate - this case is special, because this is the default item value _ = list.Contains(0).Should().BeFalse(); } - [Fact] - public void ContainsShouldNotFindAnythingWhenListEmpty() + [Theory] + [MemberData(nameof(RecyclableListTestData.TargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ContainsShouldNotFindAnythingWhenListEmpty(long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(); + using var list = new RecyclableList(minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); // Validate _ = list.Contains(0).Should().BeFalse(); _ = list.Contains(1).Should().BeFalse(); } - [Fact] - public void CopyToShouldCopyAllItems() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + //[InlineData("Debuging case", new[] { 1L, 2, }, 2, 2)] + public void CopyToShouldCopyAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act - var actual = new int[list.LongCount]; + var actual = new long[itemsCount]; list.CopyTo(actual, 0); - _ = actual.Should().BeEquivalentTo(_testData); + _ = actual.Should().ContainInConsecutiveOrder(testData); } - [Fact] - public void GetEnumeratorShouldYieldAllItems() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void EnumeratorShouldYieldAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act - var actual = new List(); + var actual = new List(); var enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { @@ -226,29 +184,37 @@ namespace Recyclable.CollectionsTests } // Validate - _ = actual.Should().BeEquivalentTo(_testData); + _ = actual.Should().ContainInConsecutiveOrder(testData); } - [Fact] - public void IndexOfShouldReturnCorrectIndexes() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act & Validate - foreach (var item in _testData) + foreach (var item in testData) { var actual = list.IndexOf(item); - _ = actual.Should().Be(item - 1); + _ = actual.Should().Be((int)item - 1); } } - [Fact] - public void IndexOfShouldNotFindNonExistingItems() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - var testData = _testData; - using var list = NewRecyclableList; + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -273,30 +239,31 @@ namespace Recyclable.CollectionsTests _ = list.IndexOf(1).Should().Be(-1); } - [Fact] - public void InsertShouldRaiseNotSupportedException() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void InsertShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act _ = Assert.Throws(() => list.Insert(0, -1)); } - [Fact] - public void RemoveShouldRemoveLastItem() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveShouldRemoveLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; - + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + var expectedData = testData.ToArray(); int removedCount = 0; - var testData = _testData.ToArray(); - foreach (var item in _testData.Reverse()) + foreach (var item in testData.Reverse()) { // Act & Validate _ = list.Remove(item).Should().BeTrue($"we search for {item} which should exist"); removedCount++; - var expectedList = testData[0..(testData.Length - removedCount)]; + var expectedList = expectedData[0..((int)itemsCount - removedCount)]; _ = list.LongCount.Should().Be(expectedList.Length); _ = list.Should().ContainInConsecutiveOrder(expectedList); } @@ -304,182 +271,150 @@ namespace Recyclable.CollectionsTests _ = list.Should().BeEmpty(); } - [Fact] - public void RemoveShouldRaiseNotSupportedException() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act - _ = Assert.Throws(() => list.Remove(_testData.First())); + _ = Assert.Throws(() => list.Remove(testData.First())); } - [Fact] - public void RemoveAtShouldRaiseNotSupportedException() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveAtShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act _ = Assert.Throws(() => list.RemoveAt(0)); } - [Fact] - public void DisposeShouldSucceed() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void DisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act list.Dispose(); // Validate _ = list.LongCount.Should().Be(0L); + _ = list.Should().BeEmpty(); } - [Fact] - public void ConsecutiveDisposeShouldSucceed() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); list.Dispose(); // Act list.Dispose(); } - [Fact] - public void ClearShouldRemoveAllItems() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = NewRecyclableList; - _ = list.LongCount.Should().NotBe(0); + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); + _ = list.LongCount.Should().Be(itemsCount); // Act list.Clear(); + list.Clear(); // Validate _ = list.LongCount.Should().Be(0); - } - - public static IEnumerable AddRangeCollectionTypes - { - get - { - yield return new object[] { "int[]", _testData.ToArray() }; - yield return new object[] { "List", _testData.ToList() }; - yield return new object[] { "RecyclableArrayList", _testData.ToRecyclableArrayList() }; - foreach (var sourceBlockSize in BlockSizeVariants) - { - yield return new object[] { $"RecyclableList(minBlockSize: {sourceBlockSize})", _testData.ToRecyclableList(sourceBlockSize) }; - - } - - yield return new object[] { "IList", new CustomIList(_testData) }; - yield return new object[] { "IEnumerable with non-enumerated count", _testData }; - yield return new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(_testData) }; - } - } - - public static IEnumerable AddRangeEmptyCollectionTypes => new[] - { - new object[] { "int[]", Array.Empty() }, - new object[] { "List", Array.Empty().ToList() }, - new object[] { "RecyclableArrayList", Array.Empty().ToRecyclableArrayList() }, - new object[] { "RecyclableList", Array.Empty().ToRecyclableList() }, - new object[] { "IList", new CustomIList(Array.Empty()) }, - new object[] { "IEnumerable with non-enumerated count", Array.Empty().AsEnumerable() }, - new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(Array.Empty()) } - }; - - [Theory] - [MemberData(nameof(AddRangeCollectionTypes))] - public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData) - { - try - { - // Prepare - using var list = new RecyclableList(); - - // Act - if (testData is int[] testDataArray) - { - list.AddRange(testDataArray); - } - else if (testData is List testDataList) - { - list.AddRange(testDataList); - } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) - { - list.AddRange(testDataRecyclableArrayList); - } - else if (testData is RecyclableList testDataRecyclableList) - { - list.AddRange(testDataRecyclableList); - } - else if (testData is IList testDataIList) - { - list.AddRange(testDataIList); - } - else if (testData is IEnumerable testDataIEnumerable) - { - list.AddRange(testDataIEnumerable); - } - else - { - throw new InvalidCastException("Unknown type of test data"); - } - - // Validate - int expectedItemsCount = testData.Count(); - _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); - _ = list.Should().HaveCount(expectedItemsCount) - .And.ContainInConsecutiveOrder(testData); - } - finally - { - if (testData is IDisposable disposable) - { - disposable.Dispose(); - } - } + _ = list.Should().BeEmpty(); } [Theory] - [MemberData(nameof(AddRangeCollectionTypes))] - public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData) + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - var list = new RecyclableList(); + using var list = new RecyclableList(minBlockSize: targetBlockSize); // Act - if (testData is int[] testDataArray) + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableArrayList testDataRecyclableArrayList) + { + list.AddRange(testDataRecyclableArrayList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + int expectedItemsCount = testData.Count(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); + _ = list.LongCount.Should().Be(expectedItemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(minBlockSize: targetBlockSize); + + // Act + if (testData is long[] testDataArray) { list.AddRange(testDataArray); list.AddRange(testDataArray); } - else if (testData is List testDataList) + else if (testData is List testDataList) { list.AddRange(testDataList); list.AddRange(testDataList); } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) + else if (testData is RecyclableArrayList testDataRecyclableArrayList) { list.AddRange(testDataRecyclableArrayList); list.AddRange(testDataRecyclableArrayList); } - else if (testData is RecyclableList testDataRecyclableList) + else if (testData is RecyclableList testDataRecyclableList) { list.AddRange(testDataRecyclableList); list.AddRange(testDataRecyclableList); } - else if (testData is IList testDataIList) + else if (testData is IList testDataIList) { list.AddRange(testDataIList); list.AddRange(testDataIList); } - else if (testData is IEnumerable testDataIEnumerable) + else if (testData is IEnumerable testDataIEnumerable) { list.AddRange(testDataIEnumerable); list.AddRange(testDataIEnumerable); @@ -491,42 +426,45 @@ namespace Recyclable.CollectionsTests // Validate var expectedData = testData.Concat(testData).ToArray(); - _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedData.Length) - .And.BeLessThan((int)(expectedData.Length * 1.5)); - _ = list.Should().HaveCount(expectedData.Length) - .And.ContainInConsecutiveOrder(expectedData) - .And.BeEquivalentTo(expectedData); + + if (itemsCount > 0) + { + _ = list.Capacity.Should().BeGreaterThanOrEqualTo(targetBlockSize); + } + + _ = list.LongCount.Should().Be(expectedData.Length); + _ = list.Should().ContainInConsecutiveOrder(expectedData); } [Theory] - [MemberData(nameof(AddRangeEmptyCollectionTypes))] - public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) + [MemberData(nameof(RecyclableListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) { // Prepare - var list = new RecyclableList(); + using var list = new RecyclableList(); // Act - if (testData is int[] testDataArray) + if (testData is long[] testDataArray) { list.AddRange(testDataArray); } - else if (testData is List testDataList) + else if (testData is List testDataList) { list.AddRange(testDataList); } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) + else if (testData is RecyclableArrayList testDataRecyclableArrayList) { list.AddRange(testDataRecyclableArrayList); } - else if (testData is RecyclableList testDataRecyclableList) + else if (testData is RecyclableList testDataRecyclableList) { list.AddRange(testDataRecyclableList); } - else if (testData is IList testDataIList) + else if (testData is IList testDataIList) { list.AddRange(testDataIList); } - else if (testData is IEnumerable testDataIEnumerable) + else if (testData is IEnumerable testDataIEnumerable) { list.AddRange(testDataIEnumerable); } @@ -537,26 +475,27 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Capacity.Should().Be(0); - _ = list.Should().BeEmpty().And.HaveCount(0); + _ = list.LongCount.Should().Be(0); + _ = list.Should().BeEmpty(); } - [Fact] - public void InsertAtTheBeginningShouldMoveItems() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableList(); + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act _ = Assert.Throws(() => list.Insert(0, -1)); } - [Fact] - public void LongIndexOfShouldReturnCorrectIndexes() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void LongIndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableList(testData); + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Act & Validate foreach (var item in testData) @@ -569,12 +508,12 @@ namespace Recyclable.CollectionsTests } } - [Fact] - public void LongIndexOfShouldNotFindNonExistingItems() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void LongIndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - var testData = _testData; - using var list = NewRecyclableList; + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -592,26 +531,23 @@ namespace Recyclable.CollectionsTests public void LongIndexOfShouldNotFindAnythingWhenListEmpty() { // Prepare - using var list = new RecyclableList(); + using var list = new RecyclableList(); // Validate _ = list.LongIndexOf(0).Should().Be(-1); _ = list.LongIndexOf(1).Should().Be(-1); } - [Fact] - public void ConstructorSourceShouldInitializeList() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { - // Prepare - var testData = _testData.ToArray(); - // Act - var list = new RecyclableList(testData); + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); // Validate - _ = list.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); } } } \ No newline at end of file From e312accfe5d54b191f64dbde9291a2d11fea7365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 17 Mar 2023 02:09:16 +0100 Subject: [PATCH 144/305] Unused & confusing test data removed. --- .../RecyclableListTestData.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index 812ef35..acf61e0 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -70,7 +70,6 @@ namespace Recyclable.CollectionsTests _testCases = null; _sourceDataVariants = null; _targetDataVariants = null; - _testDataVariants = null; _sourceTargetDataVariants = null; _emptySourceDataVariants = null; GC.SuppressFinalize(this); @@ -102,23 +101,6 @@ namespace Recyclable.CollectionsTests private static IEnumerable? _targetDataVariants; public static IEnumerable TargetDataVariants => _targetDataVariants ??= GetTargetDataVariants().ToArray(); - private static IEnumerable GetTestDataVariants() - { - foreach (var itemsCount in ItemsCountVariants) - { - foreach (var sourceBlockSize in BlockSizeVariants) - { - foreach (var targetBlockSize in BlockSizeVariants) - { - yield return new object[] { itemsCount, sourceBlockSize, targetBlockSize }; - } - } - } - } - - private static IEnumerable? _testDataVariants; - public static IEnumerable TestDataVariants => _testDataVariants ??= GetTestDataVariants().ToArray(); - private static IEnumerable CreateSourceDataVariants() { foreach (var itemsCount in ItemsCountVariants) From e6224ebe44400a426a5e095ddd61b6c6a256fe96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 19 Mar 2023 20:37:18 +0100 Subject: [PATCH 145/305] Fixed handling of empty lists in RecyclableArrayList. Some RecyclableList properties made public to allow use in RecyclableArrayList. Added support for RecyclableList in RecyclableArrayList.AddRange. Additional tests added for RecyclableArrayList. --- Recyclable.Collections/RecyclableArrayList.cs | 78 +++++- Recyclable.Collections/RecyclableList.cs | 10 +- .../RecyclableArrayListTests.cs | 244 +++++++++--------- 3 files changed, 209 insertions(+), 123 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index a559252..7b83164 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -11,9 +11,9 @@ namespace Recyclable.Collections protected T[] _memoryBlock; - private static void ThrowArgumentOutOfRangeException() + private static void ThrowArgumentOutOfRangeException(in string message) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(message, (Exception?)null); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -162,6 +162,11 @@ namespace Recyclable.Collections public void AddRange(in T[] items) { + if (items.LongLength == 0) + { + return; + } + int sourceItemsCount = items.Length; int targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) @@ -177,6 +182,11 @@ namespace Recyclable.Collections public void AddRange(List items) { + if (items.Count == 0) + { + return; + } + var sourceItemsCount = items.Count; var targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) @@ -190,6 +200,11 @@ namespace Recyclable.Collections public void AddRange(IList items) { + if (items.Count == 0) + { + return; + } + var sourceItemsCount = items.Count; var targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) @@ -203,6 +218,11 @@ namespace Recyclable.Collections public void AddRange(RecyclableArrayList items) { + if (items.Count == 0) + { + return; + } + var sourceItemsCount = items._count; var targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) @@ -216,6 +236,58 @@ namespace Recyclable.Collections _count = targetCapacity; } + public void AddRange(RecyclableList items) + { + if (items.LongCount == 0) + { + return; + } + + var sourceItemsCount = items.LongCount; + if (sourceItemsCount > int.MaxValue) + { + ThrowArgumentOutOfRangeException($"The number of items exceeds the maximum capacity of {nameof(RecyclableArrayList)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableList)}, instead"); + } + + if (_count + sourceItemsCount > int.MaxValue) + { + ThrowArgumentOutOfRangeException($"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableArrayList)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableList)}, instead"); + } + + int targetCapacity = _count + (int)sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + Span targetSpan = new Span(_memoryBlock)[_count..], + itemsSpan; + + int blockIndex, + sourceBlockSize = items.BlockSize, + lastBlockIndex = (int)(sourceItemsCount / sourceBlockSize) - 1; + + for (blockIndex = 0; blockIndex <= lastBlockIndex; blockIndex++) + { + itemsSpan = new(items.MemoryBlocks[blockIndex], 0, sourceBlockSize); + itemsSpan.CopyTo(targetSpan); + targetSpan = targetSpan[sourceBlockSize..]; + } + + if (blockIndex == 0) + { + itemsSpan = new(items.MemoryBlocks[blockIndex], 0, (int)sourceItemsCount); + itemsSpan.CopyTo(targetSpan); + } + else if (blockIndex < items.BlockCount) + { + itemsSpan = new(items.MemoryBlocks[blockIndex], 0, items.NextItemIndex); + itemsSpan.CopyTo(targetSpan); + } + + _count = targetCapacity; + } + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { if (source is T[] sourceArray) @@ -375,7 +447,7 @@ namespace Recyclable.Collections if (index >= oldCount) { - ThrowArgumentOutOfRangeException(); + ThrowArgumentOutOfRangeException($"Argument \"{index}\" is out of range. The current version supports removing the last element, only "); return; } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index a67aeb7..97e6556 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -22,6 +22,9 @@ namespace Recyclable.Collections protected ArrayPool _blockArrayPool; protected T[][] _memoryBlocks; + public int BlockSize => _blockSize; + public int BlockCount => _memoryBlocks.Length; + private long _capacity; public long Capacity { @@ -29,7 +32,8 @@ namespace Recyclable.Collections protected set => _capacity = value; } - public int Count => (int)_longCount; + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } protected long _longCount; @@ -39,8 +43,8 @@ namespace Recyclable.Collections set => _longCount = value; } - public bool IsReadOnly { get; } - public int BlockCount => _memoryBlocks.Length; + public int LastBlockIndex => _lastBlockIndex; + public int NextItemIndex => _nextItemIndex; private static void RemoveAt(RecyclableList list, long index, ArrayPool blockArrayPool) { diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 7b6f475..1a9ce13 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -7,75 +7,163 @@ namespace Recyclable.CollectionsTests { private readonly static IEnumerable _testData = Enumerable.Range(1, 20); - [Fact] - public void AddCountShouldSucceed() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddCountShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(); + var list = new RecyclableArrayList(); // Act foreach (var item in testData) { list.Add(item); - _ = list.Count.Should().Be(item); + _ = list.Count.Should().Be((int)item); } // Validate - _ = list.Capacity.Should().Be(32); - _ = list.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); } - [Fact] - public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsArray() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); // Act - list.AddRange(testData); + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableArrayList testDataRecyclableArrayList) + { + list.AddRange(testDataRecyclableArrayList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } // Validate - _ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); } - [Fact] - public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsList() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) { - // Prepare - var testData = _testData.ToList(); - var list = new RecyclableArrayList(); - // Act - list.AddRange(testData); + using var list = new RecyclableArrayList((int)itemsCount * 2); + foreach (var index in testData) + { + list.Add(index); + } + + foreach (var index in testData) + { + list.Add(index); + } // Validate - _ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(testData.Count) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); + _ = list.Count.Should().Be((int)itemsCount * 2); + _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); } - [Fact] - public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var testData = _testData; + var list = new RecyclableArrayList(testData); + _ = list.Count.Should().Be((int)itemsCount); + // Act + list.Clear(); + + // Validate + _ = list.Count.Should().Be(0); + _ = list.Should().BeEmpty(); + + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) + { // Act - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData.Concat(testData), initialCapacity: (int)itemsCount * 2); // Validate - _ = list.Capacity.Should().Be(32, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); - _ = list.Should().HaveCount(testData.Count()) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); + _ = list.Count.Should().Be((int)itemsCount * 2); + _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable testData, long itemsCount) + { + // Act + var list = new RecyclableArrayList(testData); + + // Validate + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount) + { + // Act + var list = new RecyclableArrayList(testData); + + // Validate + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ContainsShouldFindAllItems(string testCase, IEnumerable testData, long itemsCount) + { + if (itemsCount == 0) + { + return; + } + + // Prepare + using var list = new RecyclableArrayList(testData); + _ = 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] @@ -203,21 +291,6 @@ namespace Recyclable.CollectionsTests _ = list.Capacity.Should().Be(20); } - [Fact] - public void ConstructorSourceShouldInitializeList() - { - // Prepare - var testData = _testData.ToArray(); - - // Act - var list = new RecyclableArrayList(testData); - - // Validate - _ = list.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); - } - [Fact] public void EnumerateShouldYieldAllItemsInCorrectOrder() { @@ -254,22 +327,6 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(testData) .And.BeEquivalentTo(testData); } - [Fact] - public void ClearShouldRemoveAllItems() - { - // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(testData); - _ = list.Count.Should().NotBe(0); - - // Act - list.Clear(); - - // Validate - _ = list.Count.Should().Be(0); - _ = list.Should().HaveCount(0); - - } [Fact] public void IndexOfShouldFindTheIndexes() @@ -286,22 +343,6 @@ namespace Recyclable.CollectionsTests } } - [Fact] - public void ContainsShouldFindAllItems() - { - // Prepare - var testData = _testData.ToArray(); - using var list = new RecyclableArrayList(testData); - _ = 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() { @@ -333,36 +374,5 @@ namespace Recyclable.CollectionsTests _ = list.Contains(1).Should().BeTrue(); } - [Fact] - public void AddShouldAcceptDuplicates() - { - // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; - - // Act - using var list = new RecyclableArrayList(); - foreach (var index in testNumbers) - { - list.Add(index); - } - - // Validate - _ = list.Count.Should().Be(testNumbers.Length); - _ = list.Should().BeEquivalentTo(testNumbers); - } - - [Fact] - public void ConstructorShouldAcceptDuplicates() - { - // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; - - // Act - using var list = new RecyclableArrayList(testNumbers); - - // Validate - _ = list.Count.Should().Be(testNumbers.Length); - _ = list.Should().BeEquivalentTo(testNumbers); - } - } - } \ No newline at end of file + } +} \ No newline at end of file From 1157e3e23d6894fc30732e77ee4957fb86a89af8 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 20 Mar 2023 19:32:08 +0100 Subject: [PATCH 146/305] Test cases adjusted to limit execution time. --- Recyclable.CollectionsTests/RecyclableListTestData.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index acf61e0..31e32b7 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -78,13 +78,13 @@ namespace Recyclable.CollectionsTests public static readonly IEnumerable ItemsCountVariants = new long[] { 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, 8_192, 16_384, - 32_768, 65_536, 131_072, 262_144, 524_288, 1_048_576, 10_000_000 + 32_768, 65_536, 131_072, 262_144, 524_288 }; public static readonly IEnumerable BlockSizeVariants = new int[] { 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, - 8_192, 16_384, 32_768, 65_536, 131_072, 262_144, 524_288, 1_048_576 + 8_192, 16_384, 32_768, 65_536, 131_072, 262_144, 524_288 }; private static IEnumerable GetTargetDataVariants() From b21b80e222d2a4eb1fedfb1562eedf2e70f08aa2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 20 Mar 2023 20:29:50 +0100 Subject: [PATCH 147/305] RecyclableList modified to respect the removal of blocks. RecyclableList.IndexOf fixed to work in various combinations. RecyclableList tests extended. --- Recyclable.Collections/RecyclableList.cs | 50 ++++++++++++------- .../RecyclableListTests.cs | 22 +++++++- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 97e6556..14af33d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -22,9 +22,6 @@ namespace Recyclable.Collections protected ArrayPool _blockArrayPool; protected T[][] _memoryBlocks; - public int BlockSize => _blockSize; - public int BlockCount => _memoryBlocks.Length; - private long _capacity; public long Capacity { @@ -43,6 +40,7 @@ namespace Recyclable.Collections set => _longCount = value; } + public int BlockCount => (int)(_capacity / _blockSize); public int LastBlockIndex => _lastBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -98,7 +96,7 @@ namespace Recyclable.Collections long capacity = _capacity; long available = capacity - copied; - Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span memoryBlocksSpan = new(_memoryBlocks, 0, BlockCount); Span blockArraySpan; var memoryBlocksCount = memoryBlocksSpan.Length; while (true) @@ -172,7 +170,7 @@ namespace Recyclable.Collections blockSize = _blockSize; } - var memoryBlocksCount = _memoryBlocks.Length; + var memoryBlocksCount = BlockCount; memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); foreach (var item in source) @@ -199,9 +197,9 @@ namespace Recyclable.Collections protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) { - Span memoryBlocksSpan = new(memoryBlocks); - int itemIndex; - for (var blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) { itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); if (itemIndex >= 0) @@ -210,18 +208,28 @@ namespace Recyclable.Collections } } - itemIndex = Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex); - return itemIndex >= 0 ? itemIndex + (lastBlockIndex * blockSize) : itemIndex; + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + (lastBlockIndex * blockSize) : ItemNotFoundIndex; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { const int sourceBlockCount = 0, requiredBlockCount = 1, uninitializedBlocksCount = 2, i = 3; Span localInts = stackalloc int[4] { - source?.Length ?? 0, // sourceBlockCount + (int)(oldCapacity / minBlockSize), // sourceBlockCount (int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0), // requiredBlockCount 0, // uninitializedBlocksCount 0, // i @@ -297,7 +305,7 @@ namespace Recyclable.Collections newCapacity = requestedCapacity; } - memory = SetNewLength(memory, _blockSize, newCapacity, _memoryBlocksPool, _blockArrayPool); + memory = SetNewLength(memory, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); if (newCapacity > 0 && _capacity == 0) { _blockSize = memory[0].Length; @@ -318,7 +326,7 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -342,7 +350,7 @@ namespace Recyclable.Collections _blockArrayPool = blockArrayPool; if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -425,8 +433,12 @@ namespace Recyclable.Collections targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); } + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + _nextItemIndex = itemsSpan.Length; - itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; _lastBlockIndex = targetBlockIndex; } @@ -685,7 +697,7 @@ namespace Recyclable.Collections { if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { - Span memoryBlocksSpan = new(_memoryBlocks); + Span memoryBlocksSpan = new(_memoryBlocks, 0, BlockCount); int memoryBlocksCount = memoryBlocksSpan.Length; ArrayPool blockArrayPool = _blockArrayPool; for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) @@ -735,14 +747,14 @@ namespace Recyclable.Collections ? ItemNotFoundIndex : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : (int)DoIndexOf(item, _memoryBlocks, _lastBlockIndex, _blockSize, _nextItemIndex); + : (int)DoIndexOf(item, _memoryBlocks, BlockCount - 1, _blockSize, _nextItemIndex); public void Insert(int index, T item) => throw new NotSupportedException(); public long LongIndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : DoIndexOf(item, _memoryBlocks, _lastBlockIndex, _blockSize, _nextItemIndex); + : DoIndexOf(item, _memoryBlocks, BlockCount - 1, _blockSize, _nextItemIndex); public bool Remove(T item) { diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 56c518f..9521781 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -77,8 +77,13 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void InitializeSortClearShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { + if (itemsCount == 0) + { + return; + } + // Act using RecyclableList list = new(testData, minBlockSize: targetBlockSize); list.QuickSort(); @@ -86,13 +91,26 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(itemsCount); _ = list.Should().Equal(testData); - _ = testData.Should().AllSatisfy(expected => list.Contains(expected)); // Act list.Clear(); // Validate _ = list.LongCount.Should().Be(0); + _ = list.Should().BeEmpty(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + + // Act + foreach (var item in testData.Reverse()) + { + _ = list.Remove(item).Should().BeTrue(); + } } [Theory] From b1e6c587c3f21c0eff783cb24f59bac529dfeafd Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 20 Mar 2023 20:29:50 +0100 Subject: [PATCH 148/305] RecyclableList modified to respect the removal of blocks. RecyclableList.IndexOf fixed to work in various combinations. RecyclableList tests extended. --- Recyclable.Collections/RecyclableList.cs | 51 ++++++++++++------- .../RecyclableListTests.cs | 22 +++++++- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 97e6556..a441572 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -22,9 +22,6 @@ namespace Recyclable.Collections protected ArrayPool _blockArrayPool; protected T[][] _memoryBlocks; - public int BlockSize => _blockSize; - public int BlockCount => _memoryBlocks.Length; - private long _capacity; public long Capacity { @@ -43,6 +40,8 @@ namespace Recyclable.Collections set => _longCount = value; } + public int BlockCount => (int)(_capacity / _blockSize); + public int BlockSize => _blockSize; public int LastBlockIndex => _lastBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -98,7 +97,7 @@ namespace Recyclable.Collections long capacity = _capacity; long available = capacity - copied; - Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span memoryBlocksSpan = new(_memoryBlocks, 0, BlockCount); Span blockArraySpan; var memoryBlocksCount = memoryBlocksSpan.Length; while (true) @@ -172,7 +171,7 @@ namespace Recyclable.Collections blockSize = _blockSize; } - var memoryBlocksCount = _memoryBlocks.Length; + var memoryBlocksCount = BlockCount; memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); foreach (var item in source) @@ -199,9 +198,9 @@ namespace Recyclable.Collections protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) { - Span memoryBlocksSpan = new(memoryBlocks); - int itemIndex; - for (var blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) { itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); if (itemIndex >= 0) @@ -210,18 +209,28 @@ namespace Recyclable.Collections } } - itemIndex = Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex); - return itemIndex >= 0 ? itemIndex + (lastBlockIndex * blockSize) : itemIndex; + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + (lastBlockIndex * blockSize) : ItemNotFoundIndex; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { const int sourceBlockCount = 0, requiredBlockCount = 1, uninitializedBlocksCount = 2, i = 3; Span localInts = stackalloc int[4] { - source?.Length ?? 0, // sourceBlockCount + (int)(oldCapacity / minBlockSize), // sourceBlockCount (int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0), // requiredBlockCount 0, // uninitializedBlocksCount 0, // i @@ -297,7 +306,7 @@ namespace Recyclable.Collections newCapacity = requestedCapacity; } - memory = SetNewLength(memory, _blockSize, newCapacity, _memoryBlocksPool, _blockArrayPool); + memory = SetNewLength(memory, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); if (newCapacity > 0 && _capacity == 0) { _blockSize = memory[0].Length; @@ -318,7 +327,7 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -342,7 +351,7 @@ namespace Recyclable.Collections _blockArrayPool = blockArrayPool; if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -425,8 +434,12 @@ namespace Recyclable.Collections targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); } + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + _nextItemIndex = itemsSpan.Length; - itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; _lastBlockIndex = targetBlockIndex; } @@ -685,7 +698,7 @@ namespace Recyclable.Collections { if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { - Span memoryBlocksSpan = new(_memoryBlocks); + Span memoryBlocksSpan = new(_memoryBlocks, 0, BlockCount); int memoryBlocksCount = memoryBlocksSpan.Length; ArrayPool blockArrayPool = _blockArrayPool; for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) @@ -735,14 +748,14 @@ namespace Recyclable.Collections ? ItemNotFoundIndex : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : (int)DoIndexOf(item, _memoryBlocks, _lastBlockIndex, _blockSize, _nextItemIndex); + : (int)DoIndexOf(item, _memoryBlocks, BlockCount - 1, _blockSize, _nextItemIndex); public void Insert(int index, T item) => throw new NotSupportedException(); public long LongIndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : DoIndexOf(item, _memoryBlocks, _lastBlockIndex, _blockSize, _nextItemIndex); + : DoIndexOf(item, _memoryBlocks, BlockCount - 1, _blockSize, _nextItemIndex); public bool Remove(T item) { diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 56c518f..9521781 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -77,8 +77,13 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void InitializeSortClearShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { + if (itemsCount == 0) + { + return; + } + // Act using RecyclableList list = new(testData, minBlockSize: targetBlockSize); list.QuickSort(); @@ -86,13 +91,26 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(itemsCount); _ = list.Should().Equal(testData); - _ = testData.Should().AllSatisfy(expected => list.Contains(expected)); // Act list.Clear(); // Validate _ = list.LongCount.Should().Be(0); + _ = list.Should().BeEmpty(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + + // Act + foreach (var item in testData.Reverse()) + { + _ = list.Remove(item).Should().BeTrue(); + } } [Theory] From b91c4476eb234225a029d902db9599b38a2eca04 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 20 Mar 2023 23:46:50 +0100 Subject: [PATCH 149/305] RecyclableList.BlockCount renamed => RecyclableList.ReservedBlockCount. Fixed to always include the last block. RecyclableList.LastTakenBlockIndex property added for some scenarios. RecyclableList.IndexOf & .LongIndexOf fixed to use the correct index. RecyclableArrayList.AddRange(RecyclableList items) fixed to use the correct index. Some test cases excluded, where needed. --- Recyclable.Collections/RecyclableArrayList.cs | 2 +- Recyclable.Collections/RecyclableList.cs | 16 ++++++++-------- Recyclable.Collections/RecyclableStack.cs | 2 +- .../RecyclableListTests.cs | 19 +++++++++++++++++-- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 7b83164..0d91bd0 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -279,7 +279,7 @@ namespace Recyclable.Collections itemsSpan = new(items.MemoryBlocks[blockIndex], 0, (int)sourceItemsCount); itemsSpan.CopyTo(targetSpan); } - else if (blockIndex < items.BlockCount) + else if (blockIndex <= items.LastTakenBlockIndex) { itemsSpan = new(items.MemoryBlocks[blockIndex], 0, items.NextItemIndex); itemsSpan.CopyTo(targetSpan); diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index a441572..aac2350 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -31,16 +31,16 @@ namespace Recyclable.Collections public int Count => checked((int)_longCount); public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _lastBlockIndex - (_nextItemIndex > 0 ? 0 : 1); protected long _longCount; - public long LongCount { get => _longCount; set => _longCount = value; } - public int BlockCount => (int)(_capacity / _blockSize); + public int ReservedBlockCount => (int)(_capacity / _blockSize) + (_capacity % _blockSize > 0 ? 1 : 0); public int BlockSize => _blockSize; public int LastBlockIndex => _lastBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -70,7 +70,7 @@ namespace Recyclable.Collections T[][] memoryBlocks = list._memoryBlocks; if (blockSize >= RecyclableDefaults.MinPooledArrayLength) { - blockArrayPool.Return(memoryBlocks[list.BlockCount - 1]); + blockArrayPool.Return(memoryBlocks[list.ReservedBlockCount - 1]); } list._capacity -= blockSize; @@ -97,7 +97,7 @@ namespace Recyclable.Collections long capacity = _capacity; long available = capacity - copied; - Span memoryBlocksSpan = new(_memoryBlocks, 0, BlockCount); + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); Span blockArraySpan; var memoryBlocksCount = memoryBlocksSpan.Length; while (true) @@ -171,7 +171,7 @@ namespace Recyclable.Collections blockSize = _blockSize; } - var memoryBlocksCount = BlockCount; + var memoryBlocksCount = ReservedBlockCount; memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); foreach (var item in source) @@ -698,7 +698,7 @@ namespace Recyclable.Collections { if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { - Span memoryBlocksSpan = new(_memoryBlocks, 0, BlockCount); + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); int memoryBlocksCount = memoryBlocksSpan.Length; ArrayPool blockArrayPool = _blockArrayPool; for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) @@ -748,14 +748,14 @@ namespace Recyclable.Collections ? ItemNotFoundIndex : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : (int)DoIndexOf(item, _memoryBlocks, BlockCount - 1, _blockSize, _nextItemIndex); + : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); public void Insert(int index, T item) => throw new NotSupportedException(); public long LongIndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : DoIndexOf(item, _memoryBlocks, BlockCount - 1, _blockSize, _nextItemIndex); + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); public bool Remove(T item) { diff --git a/Recyclable.Collections/RecyclableStack.cs b/Recyclable.Collections/RecyclableStack.cs index c915341..fb67715 100644 --- a/Recyclable.Collections/RecyclableStack.cs +++ b/Recyclable.Collections/RecyclableStack.cs @@ -49,7 +49,7 @@ namespace Recyclable.Collections LongCount--; if ((List.Capacity * _blockSize) - LongCount == _blockSize) { - List.RemoveBlock(List.BlockCount - 1); + List.RemoveBlock(List.ReservedBlockCount - 1); } return toRemove; diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 9521781..4bc2cca 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -291,8 +291,13 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void RemoveShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void RemoveShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { + if (itemsCount is 0 or 1) + { + return; + } + // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); @@ -302,8 +307,13 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void RemoveAtShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void RemoveAtShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { + if (itemsCount is 0 or 1) + { + return; + } + // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); @@ -530,6 +540,11 @@ namespace Recyclable.CollectionsTests [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] public void LongIndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { + if (itemsCount == 0) + { + return; + } + // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); From 277e52f7018d5ac138224385267f80c08ca7b085 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 20 Mar 2023 23:51:23 +0100 Subject: [PATCH 150/305] RecyclableArrayList.Contains fixed to not throw exception when the list is empty. RecyclableArrayList tests sorted alphabetically, simplified & extended to use the test data & validate various combinations of item counts & block sizes. --- Recyclable.Collections/RecyclableArrayList.cs | 2 +- .../RecyclableArrayListTests.cs | 299 +++++++++--------- 2 files changed, 149 insertions(+), 152 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 7b83164..5d32b43 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -372,7 +372,7 @@ namespace Recyclable.Collections _count = 0; } - public bool Contains(T item) => _memoryBlock.Contains(item); + public bool Contains(T item) => Array.IndexOf(_memoryBlock, 0, _count) >= 0; public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_memoryBlock, 0, array, arrayIndex, _count); diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 1a9ce13..709de94 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -167,32 +167,116 @@ namespace Recyclable.CollectionsTests } [Fact] - public void InsertAtTheBeginningShouldMoveItems() + public void ContainsShouldNotFindAnythingWhenListEmpty() { // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); + + // Validate + _ = list.Contains(0).Should().BeFalse(); + _ = list.Contains(1).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount) + { + if (itemsCount == 0) + { + return; + } + + // Prepare + using var list = new RecyclableArrayList(testData); + _ = 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(); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + var list = new RecyclableArrayList(testData); + long[] copiedItems = new long[itemsCount]; + + // Act + list.CopyTo(copiedItems, 0); + + // Validate + _ = copiedItems.Length.Should().Be((int)itemsCount); + _ = copiedItems.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + var list = new RecyclableArrayList(testData); + + // Act + var yieldedItems = new List((int)itemsCount); + foreach (var item in list) + { + yieldedItems.Add(item); + } + + // Validate + _ = yieldedItems.Count.Should().Be((int)itemsCount); + _ = yieldedItems.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void IndexOfShouldFindTheIndexes(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableArrayList(testData); + + // Act & Validate + foreach (var index in testData) + { + var actual = list.IndexOf(index); + _ = actual.Should().Be((int)(index - 1)); + } + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + var list = new RecyclableArrayList(); // Act foreach (var item in testData) { list.Insert(0, item); - _ = list.Count.Should().Be(item); + _ = list.Count.Should().Be((int)item); } // Validate - _ = list.Capacity.Should().Be(32); - _ = list.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData.Reverse()) - .And.BeEquivalentTo(testData); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData.Reverse()); } - [Fact] - public void LongIndexOfShouldReturnCorrectIndexes() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void LongIndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate foreach (var item in testData) @@ -201,27 +285,70 @@ namespace Recyclable.CollectionsTests var index = list.IndexOf(item); // Validate - _ = index.Should().Be(item - 1); + _ = index.Should().Be((int)(item - 1)); } } - [Fact] - public void RemoveFromTheBeginningShouldRemoveTheCorrectItem() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate - for (var deleted = 1; deleted <= testData.LongLength; deleted++) + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + list.RemoveAt(0); + + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + var list = new RecyclableArrayList(testData); + + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + list.RemoveAt(list.Count - 1); + + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + var list = new RecyclableArrayList(testData); + + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) { // Act _ = list.Remove(deleted).Should().BeTrue(); // Validate - _ = list.Should().HaveCount(testData.Length - deleted) - .And.ContainInConsecutiveOrder(testData.Skip(deleted)) - .And.BeEquivalentTo(testData.Skip(deleted)); + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); } } @@ -244,135 +371,5 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(testData.Take(testData.Length - deleted)); } } - - [Fact] - public void RemoveAtFromTheBeginningShouldSucceed() - { - // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(testData); - - // Act & Validate - for (var deleted = 1; deleted <= testData.LongLength; deleted++) - { - // Act - list.RemoveAt(0); - - // Validate - _ = list.Should().HaveCount(testData.Length - deleted) - .And.ContainInConsecutiveOrder(testData.Skip(deleted)) - .And.BeEquivalentTo(testData.Skip(deleted)); - } - - _ = list.Should().BeEmpty(); - _ = list.Capacity.Should().Be(20); - } - - [Fact] - public void RemoveAtFromTheEndShouldSucceed() - { - // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(testData); - - // Act & Validate - for (var deleted = 1; deleted <= testData.LongLength; deleted++) - { - // Act - list.RemoveAt(list.Count - 1); - - // Validate - _ = list.Should().HaveCount(testData.Length - deleted) - .And.ContainInConsecutiveOrder(testData.Take(testData.Length - deleted)) - .And.BeEquivalentTo(testData.Take(testData.Length - deleted)); - } - - _ = list.Should().BeEmpty(); - _ = list.Capacity.Should().Be(20); - } - - [Fact] - public void EnumerateShouldYieldAllItemsInCorrectOrder() - { - // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(testData); - - // Act - var yieldedItems = new List(); - foreach (var item in list) - { - yieldedItems.Add(item); - } - - // Validate - _ = yieldedItems.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); - } - - [Fact] - public void CopyToShouldCopyAllItemsInTheCorrectOrder() - { - // Prepare - var testData = _testData.ToArray(); - var list = new RecyclableArrayList(testData); - int[] yieldedItems = new int[testData.Length]; - - // Act - list.CopyTo(yieldedItems, 0); - - // Validate - _ = yieldedItems.Should().HaveCount(testData.Length) - .And.ContainInConsecutiveOrder(testData) - .And.BeEquivalentTo(testData); - } - - [Fact] - public void IndexOfShouldFindTheIndexes() - { - // Prepare - var testData = _testData.ToArray(); - using var list = new RecyclableArrayList(testData); - - // Act & Validate - foreach (var index in _testData.ToArray()) - { - var actual = list.IndexOf(index); - _ = actual.Should().Be(index - 1); - } - } - - [Fact] - public void ContainsShouldNotFindNonExistingItems() - { - // Prepare - var testData = _testData.ToArray(); - using var list = new RecyclableArrayList(testData); - _ = 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 - var testData = _testData.ToArray(); - using var list = new RecyclableArrayList(testData); - - // Validate - _ = list.Contains(0).Should().BeFalse(); - _ = list.Contains(1).Should().BeTrue(); - } - } } \ No newline at end of file From d4af246a3a3ebc723ad707be2c42d4cb497fb0ec Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 21 Mar 2023 23:14:34 +0100 Subject: [PATCH 151/305] Added missing Array.IndexOf parameter --- 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 425f671..d63bc23 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -372,7 +372,7 @@ namespace Recyclable.Collections _count = 0; } - public bool Contains(T item) => Array.IndexOf(_memoryBlock, 0, _count) >= 0; + public bool Contains(T item) => Array.IndexOf(_memoryBlock, item, 0, _count) >= 0; public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_memoryBlock, 0, array, arrayIndex, _count); From 4a1f2c1ffb541da5bdc4f0130c26829919efb72a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 22 Mar 2023 00:00:41 +0100 Subject: [PATCH 152/305] Added rented array clearing if T is a reference type, as discussed in https://github.com/dotnet/runtime/issues/25378. --- Recyclable.Collections/RecyclableArrayList.cs | 6 ++++-- Recyclable.Collections/RecyclableList.cs | 17 ++++++++++------- .../RecyclableListExtensions.cs | 4 ++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index d63bc23..561ac61 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -9,6 +9,8 @@ namespace Recyclable.Collections private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + protected T[] _memoryBlock; private static void ThrowArgumentOutOfRangeException(in string message) @@ -32,7 +34,7 @@ namespace Recyclable.Collections sourceSpan.CopyTo(newArraySpan); if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) { - arrayPool.Return(source!); + arrayPool.Return(source!, NeedsClearing); } } @@ -464,7 +466,7 @@ namespace Recyclable.Collections { Clear(); _capacity = 0; - _memoryBlock.ReturnToPool(_arrayPool); + _memoryBlock.ReturnToPool(_arrayPool, NeedsClearing); GC.SuppressFinalize(this); } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index aac2350..08cd2ef 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { public class RecyclableList : IDisposable, IList - { + { private const int ItemNotFoundIndex = -1; private const int BatchSize = 8; @@ -18,6 +18,9 @@ namespace Recyclable.Collections private int _blockSize; private int _lastBlockIndex; private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + protected ArrayPool _memoryBlocksPool; protected ArrayPool _blockArrayPool; protected T[][] _memoryBlocks; @@ -70,7 +73,7 @@ namespace Recyclable.Collections T[][] memoryBlocks = list._memoryBlocks; if (blockSize >= RecyclableDefaults.MinPooledArrayLength) { - blockArrayPool.Return(memoryBlocks[list.ReservedBlockCount - 1]); + blockArrayPool.Return(memoryBlocks[list.ReservedBlockCount - 1], NeedsClearing); } list._capacity -= blockSize; @@ -247,7 +250,7 @@ namespace Recyclable.Collections sourceSpan.CopyTo(newMemoryBlocksSpan); if (localInts[sourceBlockCount] >= RecyclableDefaults.MinPooledArrayLength) { - memoryBlocksPool.Return(source!); + memoryBlocksPool.Return(source!, NeedsClearing); } newMemoryBlocksSpan = newMemoryBlocksSpan[localInts[sourceBlockCount]..]; @@ -648,7 +651,7 @@ namespace Recyclable.Collections { if (items.Count >= RecyclableDefaults.MinPooledArrayLength) { - _blockArrayPool.Return(itemsBuffer); + _blockArrayPool.Return(itemsBuffer, NeedsClearing); } } } @@ -703,7 +706,7 @@ namespace Recyclable.Collections ArrayPool blockArrayPool = _blockArrayPool; for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) { - blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx]); + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); } } @@ -777,7 +780,7 @@ namespace Recyclable.Collections } else { - _blockArrayPool.Return(_memoryBlocks[index]); + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); } _capacity -= _blockSize; @@ -810,7 +813,7 @@ namespace Recyclable.Collections Clear(); if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) { - _memoryBlocksPool.Return(_memoryBlocks); + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); } GC.SuppressFinalize(this); diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 4b51d43..f976537 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -155,11 +155,11 @@ namespace Recyclable.Collections : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(this T[] array, ArrayPool arrayPool) + public static void ReturnToPool(this T[] array, ArrayPool arrayPool, bool needsClearing) { if (array.LongLength is > _minPooledArraySize and <= int.MaxValue) { - arrayPool.Return(array); + arrayPool.Return(array, needsClearing); } } From 0f7237e3f0fbe0f74fb271fc632c4bb1080763dd Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 22 Mar 2023 00:00:41 +0100 Subject: [PATCH 153/305] Added rented array clearing if T is a reference type, as discussed in https://github.com/dotnet/runtime/issues/25378. --- Recyclable.Collections/RecyclableArrayList.cs | 6 ++++-- Recyclable.Collections/RecyclableList.cs | 17 ++++++++++------- .../RecyclableListExtensions.cs | 4 ++-- Recyclable.Collections/RecyclableQueue.cs | 4 +++- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index d63bc23..561ac61 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -9,6 +9,8 @@ namespace Recyclable.Collections private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + protected T[] _memoryBlock; private static void ThrowArgumentOutOfRangeException(in string message) @@ -32,7 +34,7 @@ namespace Recyclable.Collections sourceSpan.CopyTo(newArraySpan); if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) { - arrayPool.Return(source!); + arrayPool.Return(source!, NeedsClearing); } } @@ -464,7 +466,7 @@ namespace Recyclable.Collections { Clear(); _capacity = 0; - _memoryBlock.ReturnToPool(_arrayPool); + _memoryBlock.ReturnToPool(_arrayPool, NeedsClearing); GC.SuppressFinalize(this); } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index aac2350..08cd2ef 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { public class RecyclableList : IDisposable, IList - { + { private const int ItemNotFoundIndex = -1; private const int BatchSize = 8; @@ -18,6 +18,9 @@ namespace Recyclable.Collections private int _blockSize; private int _lastBlockIndex; private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + protected ArrayPool _memoryBlocksPool; protected ArrayPool _blockArrayPool; protected T[][] _memoryBlocks; @@ -70,7 +73,7 @@ namespace Recyclable.Collections T[][] memoryBlocks = list._memoryBlocks; if (blockSize >= RecyclableDefaults.MinPooledArrayLength) { - blockArrayPool.Return(memoryBlocks[list.ReservedBlockCount - 1]); + blockArrayPool.Return(memoryBlocks[list.ReservedBlockCount - 1], NeedsClearing); } list._capacity -= blockSize; @@ -247,7 +250,7 @@ namespace Recyclable.Collections sourceSpan.CopyTo(newMemoryBlocksSpan); if (localInts[sourceBlockCount] >= RecyclableDefaults.MinPooledArrayLength) { - memoryBlocksPool.Return(source!); + memoryBlocksPool.Return(source!, NeedsClearing); } newMemoryBlocksSpan = newMemoryBlocksSpan[localInts[sourceBlockCount]..]; @@ -648,7 +651,7 @@ namespace Recyclable.Collections { if (items.Count >= RecyclableDefaults.MinPooledArrayLength) { - _blockArrayPool.Return(itemsBuffer); + _blockArrayPool.Return(itemsBuffer, NeedsClearing); } } } @@ -703,7 +706,7 @@ namespace Recyclable.Collections ArrayPool blockArrayPool = _blockArrayPool; for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) { - blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx]); + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); } } @@ -777,7 +780,7 @@ namespace Recyclable.Collections } else { - _blockArrayPool.Return(_memoryBlocks[index]); + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); } _capacity -= _blockSize; @@ -810,7 +813,7 @@ namespace Recyclable.Collections Clear(); if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) { - _memoryBlocksPool.Return(_memoryBlocks); + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); } GC.SuppressFinalize(this); diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 4b51d43..f976537 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -155,11 +155,11 @@ namespace Recyclable.Collections : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(this T[] array, ArrayPool arrayPool) + public static void ReturnToPool(this T[] array, ArrayPool arrayPool, bool needsClearing) { if (array.LongLength is > _minPooledArraySize and <= int.MaxValue) { - arrayPool.Return(array); + arrayPool.Return(array, needsClearing); } } diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 32ee242..0b48c3a 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -45,6 +45,8 @@ namespace Recyclable.Collections return false; } + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static long GetAbsoluteIndex(long index, long removedCount) => index + removedCount; @@ -181,7 +183,7 @@ namespace Recyclable.Collections { try { - Memory[index].ReturnToPool(_arrayPool); + Memory[index].ReturnToPool(_arrayPool, NeedsClearing); Memory.RemoveAt(index); } finally From c84cf263ca9e5fafbdec0886bbc8b2d151bd2393 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 22 Mar 2023 00:07:24 +0100 Subject: [PATCH 154/305] Missing using added to limit pressure on garbage collector. RecyclableArrayList.Remove tests extended to cover more cases. Tiny cleanup. --- .../RecyclableArrayListTests.cs | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 709de94..da390ce 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -5,14 +5,12 @@ namespace Recyclable.CollectionsTests { public class RecyclableArrayListTests { - private readonly static IEnumerable _testData = Enumerable.Range(1, 20); - [Theory] [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] public void AddCountShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); // Act foreach (var item in testData) @@ -96,7 +94,7 @@ namespace Recyclable.CollectionsTests public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); _ = list.Count.Should().Be((int)itemsCount); // Act @@ -125,7 +123,7 @@ namespace Recyclable.CollectionsTests public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable testData, long itemsCount) { // Act - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); // Validate _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); @@ -138,7 +136,7 @@ namespace Recyclable.CollectionsTests public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount) { // Act - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); // Validate _ = list.Count.Should().Be((int)itemsCount); @@ -206,7 +204,7 @@ namespace Recyclable.CollectionsTests public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); long[] copiedItems = new long[itemsCount]; // Act @@ -222,7 +220,7 @@ namespace Recyclable.CollectionsTests public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); // Act var yieldedItems = new List((int)itemsCount); @@ -256,7 +254,7 @@ namespace Recyclable.CollectionsTests public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); // Act foreach (var item in testData) @@ -276,7 +274,7 @@ namespace Recyclable.CollectionsTests public void LongIndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); // Act & Validate foreach (var item in testData) @@ -294,7 +292,7 @@ namespace Recyclable.CollectionsTests public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -316,7 +314,7 @@ namespace Recyclable.CollectionsTests public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -338,7 +336,7 @@ namespace Recyclable.CollectionsTests public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) { // Prepare - var list = new RecyclableArrayList(testData); + using var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -352,23 +350,23 @@ namespace Recyclable.CollectionsTests } } - [Fact] - public void RemoveFromTheEndShouldRemoveTheCorrectItem() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveFromTheEndShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) { - // Prepare - var testData = _testData.ToArray(); - // Act & Validate - var list = new RecyclableArrayList(testData); - for (var deleted = 1; deleted <= testData.LongLength; deleted++) + using var list = new RecyclableArrayList(testData); + long deleted = 0; + foreach (var item in testData.Reverse()) { // Act - _ = list.Remove(testData[^deleted]).Should().BeTrue(); + _ = list.Remove(item).Should().BeTrue(); + deleted++; // Validate - _ = list.Should().HaveCount(testData.Length - deleted) - .And.ContainInConsecutiveOrder(testData.Take(testData.Length - deleted)) - .And.BeEquivalentTo(testData.Take(testData.Length - deleted)); + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); + } } } From 5a39dc87f0bb9d54a15c65330fa107854463badc Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 22 Mar 2023 00:25:16 +0100 Subject: [PATCH 155/305] Tests ordered alphabetically. --- .../RecyclableListTests.cs | 820 +++++++++--------- 1 file changed, 410 insertions(+), 410 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 4bc2cca..430edcb 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -5,366 +5,6 @@ namespace Recyclable.CollectionsTests { public class RecyclableListTests { - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(minBlockSize: targetBlockSize); - - // Act - foreach (var index in testData) - { - list.Add(index); - } - - // Validate - _ = list.LongCount.Should().Be(itemsCount, "we added so many items"); - _ = list.Should().ContainInConsecutiveOrder(testData); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - testData = testData.Concat(testData).ToArray(); - - // Act - using var list = new RecyclableList(minBlockSize: targetBlockSize); - foreach (var index in testData) - { - list.Add(index); - } - - // Validate - _ = list.LongCount.Should().Be(itemsCount * 2); - _ = list.Should().ContainInConsecutiveOrder(testData); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - //[InlineData("Debugging case", new long[1] { 1 }, 1, 2)] - public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Act - using var list = new RecyclableList(minBlockSize: targetBlockSize); - list.AddRange(testData); - foreach (var index in testData) - { - list.Add(index); - } - - // Validate - _ = list.LongCount.Should().Be(itemsCount * 2); - _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - testData = testData.Concat(testData).ToArray(); - - // Act - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - - // Validate - _ = list.LongCount.Should().Be(itemsCount * 2); - _ = list.Should().Equal(testData); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - if (itemsCount == 0) - { - return; - } - - // Act - using RecyclableList list = new(testData, minBlockSize: targetBlockSize); - list.QuickSort(); - - // Validate - _ = list.LongCount.Should().Be(itemsCount); - _ = list.Should().Equal(testData); - - // Act - list.Clear(); - - // Validate - _ = list.LongCount.Should().Be(0); - _ = list.Should().BeEmpty(); - - // Act - list.AddRange(testData); - - // Validate - _ = list.LongCount.Should().Be(itemsCount); - _ = list.Should().ContainInConsecutiveOrder(testData); - - // Act - foreach (var item in testData.Reverse()) - { - _ = list.Remove(item).Should().BeTrue(); - } - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void ContainsShouldFindAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - if (itemsCount == 0) - { - // There is nothing to search for when our lists are empty - return; - } - - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - _ = 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($"we searched for {item}"); - } - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - if (itemsCount == 0) - { - // There is nothing to search for when our lists are empty - return; - } - - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - _ = 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 - this case is special, because this is the default item value - _ = list.Contains(0).Should().BeFalse(); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.TargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void ContainsShouldNotFindAnythingWhenListEmpty(long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); - - // Validate - _ = list.Contains(0).Should().BeFalse(); - _ = list.Contains(1).Should().BeFalse(); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - //[InlineData("Debuging case", new[] { 1L, 2, }, 2, 2)] - public void CopyToShouldCopyAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - - // Act - var actual = new long[itemsCount]; - list.CopyTo(actual, 0); - _ = actual.Should().ContainInConsecutiveOrder(testData); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void EnumeratorShouldYieldAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - - // Act - var actual = new List(); - var enumerator = list.GetEnumerator(); - while (enumerator.MoveNext()) - { - actual.Add(enumerator.Current); - } - - // Validate - _ = actual.Should().ContainInConsecutiveOrder(testData); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - - // Act & Validate - foreach (var item in testData) - { - var actual = list.IndexOf(item); - _ = actual.Should().Be((int)item - 1); - } - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - if (itemsCount == 0) - { - // There is nothing to search for when our lists are empty - return; - } - - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); - - // Act - foreach (var item in testData) - { - // Validate - _ = list.IndexOf(-item).Should().Be(-1); - } - - // Validate - _ = list.IndexOf(0).Should().Be(-1); - } - - [Fact] - public void IndexOfShouldNotFindAnythingWhenListEmpty() - { - // Prepare - using var list = new RecyclableList(); - - // Validate - _ = list.IndexOf(0).Should().Be(-1); - _ = list.IndexOf(1).Should().Be(-1); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void InsertShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - - // Act - _ = Assert.Throws(() => list.Insert(0, -1)); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void RemoveShouldRemoveLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - var expectedData = testData.ToArray(); - int removedCount = 0; - foreach (var item in testData.Reverse()) - { - // Act & Validate - _ = list.Remove(item).Should().BeTrue($"we search for {item} which should exist"); - removedCount++; - var expectedList = expectedData[0..((int)itemsCount - removedCount)]; - _ = list.LongCount.Should().Be(expectedList.Length); - _ = list.Should().ContainInConsecutiveOrder(expectedList); - } - - _ = list.Should().BeEmpty(); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void RemoveShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - if (itemsCount is 0 or 1) - { - return; - } - - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - - // Act - _ = Assert.Throws(() => list.Remove(testData.First())); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void RemoveAtShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - if (itemsCount is 0 or 1) - { - return; - } - - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - - // Act - _ = Assert.Throws(() => list.RemoveAt(0)); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void DisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - - // Act - list.Dispose(); - - // Validate - _ = list.LongCount.Should().Be(0L); - _ = list.Should().BeEmpty(); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - list.Dispose(); - - // Act - list.Dispose(); - } - - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) - { - // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); - _ = list.LongCount.Should().Be(itemsCount); - - // Act - list.Clear(); - list.Clear(); - - // Validate - _ = list.LongCount.Should().Be(0); - _ = list.Should().BeEmpty(); - } - [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) @@ -409,6 +49,49 @@ namespace Recyclable.CollectionsTests _ = list.Should().ContainInConsecutiveOrder(testData); } + [Theory] + [MemberData(nameof(RecyclableListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) + { + // Prepare + using var list = new RecyclableList(); + + // Act + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableArrayList testDataRecyclableArrayList) + { + list.AddRange(testDataRecyclableArrayList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + _ = list.Capacity.Should().Be(0); + _ = list.LongCount.Should().Be(0); + _ = list.Should().BeEmpty(); + } + [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) @@ -465,48 +148,313 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableListTestData))] - public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(); + testData = testData.Concat(testData).ToArray(); // Act - if (testData is long[] testDataArray) + using var list = new RecyclableList(minBlockSize: targetBlockSize); + foreach (var index in testData) { - list.AddRange(testDataArray); - } - else if (testData is List testDataList) - { - list.AddRange(testDataList); - } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) - { - list.AddRange(testDataRecyclableArrayList); - } - else if (testData is RecyclableList testDataRecyclableList) - { - list.AddRange(testDataRecyclableList); - } - else if (testData is IList testDataIList) - { - list.AddRange(testDataIList); - } - else if (testData is IEnumerable testDataIEnumerable) - { - list.AddRange(testDataIEnumerable); - } - else - { - throw new InvalidCastException("Unknown type of test data"); + list.Add(index); } // Validate - _ = list.Capacity.Should().Be(0); + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(minBlockSize: targetBlockSize); + + // Act + foreach (var index in testData) + { + list.Add(index); + } + + // Validate + _ = list.LongCount.Should().Be(itemsCount, "we added so many items"); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + //[InlineData("Debugging case", new long[1] { 1 }, 1, 2)] + public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Act + using var list = new RecyclableList(minBlockSize: targetBlockSize); + list.AddRange(testData); + foreach (var index in testData) + { + list.Add(index); + } + + // Validate + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); + _ = list.LongCount.Should().Be(itemsCount); + + // Act + list.Clear(); + list.Clear(); + + // Validate _ = list.LongCount.Should().Be(0); _ = list.Should().BeEmpty(); } + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + list.Dispose(); + + // Act + list.Dispose(); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + testData = testData.Concat(testData).ToArray(); + + // Act + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Validate + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Act + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Validate + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ContainsShouldFindAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + _ = 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($"we searched for {item}"); + } + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.TargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ContainsShouldNotFindAnythingWhenListEmpty(long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); + + // Validate + _ = list.Contains(0).Should().BeFalse(); + _ = list.Contains(1).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + _ = 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 - this case is special, because this is the default item value + _ = list.Contains(0).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + //[InlineData("Debuging case", new[] { 1L, 2, }, 2, 2)] + public void CopyToShouldCopyAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Act + var actual = new long[itemsCount]; + list.CopyTo(actual, 0); + _ = actual.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void DisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Act + list.Dispose(); + + // Validate + _ = list.LongCount.Should().Be(0L); + _ = list.Should().BeEmpty(); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void EnumeratorShouldYieldAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Act + var actual = new List(); + var enumerator = list.GetEnumerator(); + while (enumerator.MoveNext()) + { + actual.Add(enumerator.Current); + } + + // Validate + _ = actual.Should().ContainInConsecutiveOrder(testData); + } + + [Fact] + public void IndexOfShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableList(); + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + _ = list.IndexOf(1).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.IndexOf(-item).Should().Be(-1); + } + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + foreach (var item in testData) + { + var actual = list.IndexOf(item); + _ = actual.Should().Be((int)item - 1); + } + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + if (itemsCount == 0) + { + return; + } + + // Act + using RecyclableList list = new(testData, minBlockSize: targetBlockSize); + list.QuickSort(); + + // Validate + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(testData); + + // Act + list.Clear(); + + // Validate + _ = list.LongCount.Should().Be(0); + _ = list.Should().BeEmpty(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + + // Act + foreach (var item in testData.Reverse()) + { + _ = list.Remove(item).Should().BeTrue(); + } + } + [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) @@ -520,20 +468,24 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void LongIndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void InsertShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - // Act & Validate - foreach (var item in testData) - { - // Act - var index = list.LongIndexOf(item); + // Act + _ = Assert.Throws(() => list.Insert(0, -1)); + } - // Validate - _ = index.Should().Be(item - 1); - } + [Fact] + public void LongIndexOfShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableList(); + + // Validate + _ = list.LongIndexOf(0).Should().Be(-1); + _ = list.LongIndexOf(1).Should().Be(-1); } [Theory] @@ -560,27 +512,75 @@ namespace Recyclable.CollectionsTests _ = list.LongIndexOf(0).Should().Be(-1); } - [Fact] - public void LongIndexOfShouldNotFindAnythingWhenListEmpty() + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void LongIndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(); + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - // Validate - _ = list.LongIndexOf(0).Should().Be(-1); - _ = list.LongIndexOf(1).Should().Be(-1); + // Act & Validate + foreach (var item in testData) + { + // Act + var index = list.LongIndexOf(item); + + // Validate + _ = index.Should().Be(item - 1); + } } [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void RemoveAtShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { - // Act + if (itemsCount is 0 or 1) + { + return; + } + + // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - // Validate - _ = list.LongCount.Should().Be(itemsCount); - _ = list.Should().ContainInConsecutiveOrder(testData); + // Act + _ = Assert.Throws(() => list.RemoveAt(0)); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + if (itemsCount is 0 or 1) + { + return; + } + + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Act + _ = Assert.Throws(() => list.Remove(testData.First())); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveShouldRemoveLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + var expectedData = testData.ToArray(); + int removedCount = 0; + foreach (var item in testData.Reverse()) + { + // Act & Validate + _ = list.Remove(item).Should().BeTrue($"we search for {item} which should exist"); + removedCount++; + var expectedList = expectedData[0..((int)itemsCount - removedCount)]; + _ = list.LongCount.Should().Be(expectedList.Length); + _ = list.Should().ContainInConsecutiveOrder(expectedList); + } + + _ = list.Should().BeEmpty(); } } } \ No newline at end of file From 97d5d9c6a38a2db948d5a388c1dae26df7c398f5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 22 Mar 2023 01:22:01 +0100 Subject: [PATCH 156/305] RecyclableArrayList.Contains fixed to respect empty list. --- 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 561ac61..ce4caee 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -374,7 +374,7 @@ namespace Recyclable.Collections _count = 0; } - public bool Contains(T item) => Array.IndexOf(_memoryBlock, item, 0, _count) >= 0; + public bool Contains(T item) => _count > 0 && Array.IndexOf(_memoryBlock, item, 0, _count) >= 0; public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_memoryBlock, 0, array, arrayIndex, _count); From 6fc2a10e2835708bd92847a348613e6a7613f26e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 19:02:45 +0100 Subject: [PATCH 157/305] RecyclableArrayList fixes to always stay within the no. of items on the list, not the size of memory block. --- Recyclable.Collections/RecyclableArrayList.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index ce4caee..0d61193 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -176,7 +176,7 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - var targetSpan = new Span(_memoryBlock)[_count..]; + var targetSpan = new Span(_memoryBlock, _count, sourceItemsCount); Span itemsSpan = new(items); itemsSpan.CopyTo(targetSpan); _count = targetCapacity; @@ -232,8 +232,8 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - var targetSpan = new Span(_memoryBlock)[_count..]; - Span itemsSpan = new(items._memoryBlock); + var targetSpan = new Span(_memoryBlock, _count, sourceItemsCount); + Span itemsSpan = new(items._memoryBlock, 0, sourceItemsCount); itemsSpan.CopyTo(targetSpan); _count = targetCapacity; } @@ -390,8 +390,8 @@ namespace Recyclable.Collections public int IndexOf(T itemToFind) { - Span memorySpan = new(_memoryBlock); int itemCount = _count; + Span memorySpan = new(_memoryBlock, 0, itemCount); var equalityComparer = _equalityComparer; for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) { @@ -416,8 +416,8 @@ namespace Recyclable.Collections if (oldCount > 0) { - var sourceSpan = new Span(_memoryBlock)[index..oldCount]; - var targetSpan = new Span(_memoryBlock)[(index + 1)..]; + var sourceSpan = new Span(_memoryBlock, index, oldCount); + var targetSpan = new Span(_memoryBlock, index + 1, oldCount); sourceSpan.CopyTo(targetSpan); } From ea52b328592db39f31587a6eeaa81ee2dd36df3e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 19:03:46 +0100 Subject: [PATCH 158/305] Fixed memory leak in RecyclableList.AddRangeEnumerated --- 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 08cd2ef..787eb03 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -91,7 +91,7 @@ namespace Recyclable.Collections int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _lastBlockIndex; - IEnumerator enumerator = source.GetEnumerator(); + using IEnumerator enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) { return; From 31e3099bf4d2e89138121cebd313102cc9c81aca Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 19:04:34 +0100 Subject: [PATCH 159/305] Removed unnecessary arguments. We'll try to get enumerated count later in AddRange anyway. --- Recyclable.Collections/RecyclableListExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index f976537..d7c3f9d 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -130,7 +130,7 @@ namespace Recyclable.Collections public static RecyclableList ToRecyclableList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableArrayList ToRecyclableArrayList(this IEnumerable values) => new(values, values.TryGetNonEnumeratedCount(out int count) ? count : RecyclableDefaults.Capacity); + public static RecyclableArrayList ToRecyclableArrayList(this IEnumerable values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RecyclableArrayList ToRecyclableArrayList(this T[] values) => new(values); From e4e63715c26caedc3287f1a0193e20c15def42d8 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 19:06:01 +0100 Subject: [PATCH 160/305] Added missing "using" --- Recyclable.CollectionsTests/RecyclableListTests.cs | 2 +- Recyclable.CollectionsTests/RecyclableQueueTests.cs | 2 +- Recyclable.CollectionsTests/RecyclableStackTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 430edcb..231838d 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -355,7 +355,7 @@ namespace Recyclable.CollectionsTests // Act var actual = new List(); - var enumerator = list.GetEnumerator(); + using var enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { actual.Add(enumerator.Current); diff --git a/Recyclable.CollectionsTests/RecyclableQueueTests.cs b/Recyclable.CollectionsTests/RecyclableQueueTests.cs index b09ed28..4940eb9 100644 --- a/Recyclable.CollectionsTests/RecyclableQueueTests.cs +++ b/Recyclable.CollectionsTests/RecyclableQueueTests.cs @@ -107,7 +107,7 @@ namespace Recyclable.CollectionsTests // Act var actual = new RecyclableList(); - var enumerator = list.GetEnumerator(); + using var enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { actual.Add(enumerator.Current); diff --git a/Recyclable.CollectionsTests/RecyclableStackTests.cs b/Recyclable.CollectionsTests/RecyclableStackTests.cs index 9507043..e6330a0 100644 --- a/Recyclable.CollectionsTests/RecyclableStackTests.cs +++ b/Recyclable.CollectionsTests/RecyclableStackTests.cs @@ -15,7 +15,7 @@ namespace Recyclable.CollectionsTests // Act var actual = new RecyclableList(); - var enumerator = list.GetEnumerator(); + using var enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { actual.Add(enumerator.Current); From a5a4129f1c743d7f7f84da21b561c0d193a50cad Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 20:04:16 +0100 Subject: [PATCH 161/305] Resolved problem with running tests all together. --- .../RecyclableListTestData.cs | 46 +++---------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index 31e32b7..0bef65c 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -35,13 +35,6 @@ namespace Recyclable.CollectionsTests } } - private static Dictionary>? _testCases = new(); - - public RecyclableListTestData() - { - _testCases ??= new(); - } - private static IEnumerable DoCreateTestData(long itemsCount) { for (long index = 1; index <= itemsCount; index++) @@ -50,30 +43,7 @@ namespace Recyclable.CollectionsTests } } - public static IEnumerable CreateTestData(long itemsCount) - { - if (_testCases!.TryGetValue(itemsCount, out var testCases)) - { - return testCases; - } - - testCases = DoCreateTestData(itemsCount).ToArray(); - _testCases.Add(itemsCount, testCases); - return testCases; - } - - public void Dispose() - { - DisposeTestData(_testCases?.Values); - DisposeTestData(_sourceDataVariants); - DisposeTestData(_emptySourceDataVariants); - _testCases = null; - _sourceDataVariants = null; - _targetDataVariants = null; - _sourceTargetDataVariants = null; - _emptySourceDataVariants = null; - GC.SuppressFinalize(this); - } + public static IEnumerable CreateTestData(long itemsCount) => DoCreateTestData(itemsCount).ToArray(); public static readonly IEnumerable ItemsCountVariants = new long[] { @@ -98,8 +68,7 @@ namespace Recyclable.CollectionsTests } } - private static IEnumerable? _targetDataVariants; - public static IEnumerable TargetDataVariants => _targetDataVariants ??= GetTargetDataVariants().ToArray(); + public static IEnumerable TargetDataVariants => GetTargetDataVariants().ToArray(); private static IEnumerable CreateSourceDataVariants() { @@ -125,9 +94,8 @@ namespace Recyclable.CollectionsTests } } - private static IEnumerable? _sourceDataVariants; - public static IEnumerable SourceDataVariants => _sourceDataVariants ??= CreateSourceDataVariants().ToArray(); - + public static IEnumerable SourceDataVariants => CreateSourceDataVariants().ToArray(); + private static IEnumerable CreateSourceEmptyDataVariants() => new[] { new object[] { "int[]", Array.Empty() }, @@ -139,8 +107,7 @@ namespace Recyclable.CollectionsTests new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(Array.Empty()) } }; - private static IEnumerable? _emptySourceDataVariants; - public static IEnumerable EmptySourceDataVariants => _emptySourceDataVariants ??= CreateSourceEmptyDataVariants().ToArray(); + public static IEnumerable EmptySourceDataVariants => CreateSourceEmptyDataVariants().ToArray(); private static IEnumerable CreateSourceTargetDataVariants() { @@ -153,7 +120,6 @@ namespace Recyclable.CollectionsTests } } - private static IEnumerable? _sourceTargetDataVariants; - public static IEnumerable SourceTargetDataVariants => _sourceTargetDataVariants ??= CreateSourceTargetDataVariants().ToArray(); + public static IEnumerable SourceTargetDataVariants => CreateSourceTargetDataVariants().ToArray(); } } \ No newline at end of file From 98026f010346831c4c3a706f39bdbeb25210b028 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 20:10:49 +0100 Subject: [PATCH 162/305] No. of test cases reduced due to high execution times. --- Recyclable.CollectionsTests/RecyclableListTestData.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index 0bef65c..7431a63 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -47,14 +47,13 @@ namespace Recyclable.CollectionsTests public static readonly IEnumerable ItemsCountVariants = new long[] { - 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, 8_192, 16_384, - 32_768, 65_536, 131_072, 262_144, 524_288 + 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 333, 512, 1_024, 8_192, 16_384 }; public static readonly IEnumerable BlockSizeVariants = new int[] { 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, - 8_192, 16_384, 32_768, 65_536, 131_072, 262_144, 524_288 + 8_192, 16_384 }; private static IEnumerable GetTargetDataVariants() From 00b059006d92ffa182e03bf066ab63b38c594aef Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 20:13:40 +0100 Subject: [PATCH 163/305] Compilation error fix. --- Recyclable.CollectionsTests/RecyclableListTestData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index 7431a63..d962876 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -2,7 +2,7 @@ namespace Recyclable.CollectionsTests { - public class RecyclableListTestData : IDisposable + public class RecyclableListTestData { private static void DisposeTestData(IEnumerable? testCases) { From 76598dcc7d563721de5c1171d4cc1d1fd22f1eab Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 20:14:29 +0100 Subject: [PATCH 164/305] Cleanup --- .../RecyclableListTestData.cs | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index d962876..d1119d0 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -4,37 +4,6 @@ namespace Recyclable.CollectionsTests { public class RecyclableListTestData { - private static void DisposeTestData(IEnumerable? testCases) - { - if (testCases != null) - { - foreach (var testData in testCases) - { - foreach (var item in testData) - { - if (item is IDisposable disposable) - { - disposable.Dispose(); - } - } - } - } - } - - private static void DisposeTestData(IEnumerable>? testCases) - { - if (testCases != null) - { - foreach (var testData in testCases) - { - if (testData is IDisposable disposable) - { - disposable.Dispose(); - } - } - } - } - private static IEnumerable DoCreateTestData(long itemsCount) { for (long index = 1; index <= itemsCount; index++) From b815767d8ff50751a931605f5666e8aa58e8d140 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 20:53:54 +0100 Subject: [PATCH 165/305] RecyclableArrayList & RecyclableList tests synced. --- .../RecyclableArrayListTests.cs | 275 +++++++++++++++--- .../RecyclableListTests.cs | 19 +- 2 files changed, 245 insertions(+), 49 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index da390ce..7d236d8 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -5,26 +5,6 @@ namespace Recyclable.CollectionsTests { public class RecyclableArrayListTests { - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] - public void AddCountShouldSucceed(string testCase, IEnumerable testData, long itemsCount) - { - // Prepare - using var list = new RecyclableArrayList(); - - // Act - foreach (var item in testData) - { - list.Add(item); - _ = list.Count.Should().Be((int)item); - } - - // Validate - _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); - _ = list.Count.Should().Be((int)itemsCount); - _ = list.Should().ContainInConsecutiveOrder(testData); - } - [Theory] [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) @@ -68,24 +48,157 @@ namespace Recyclable.CollectionsTests _ = list.Should().ContainInConsecutiveOrder(testData); } + [Theory] + [MemberData(nameof(RecyclableListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) + { + // Prepare + using var list = new RecyclableArrayList(); + + // Act + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableArrayList testDataRecyclableArrayList) + { + list.AddRange(testDataRecyclableArrayList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + _ = list.Capacity.Should().Be(0); + _ = list.Count.Should().Be(0); + _ = list.Should().BeEmpty(); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableArrayList(); + + // Act + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + list.AddRange(testDataList); + } + else if (testData is RecyclableArrayList testDataRecyclableArrayList) + { + list.AddRange(testDataRecyclableArrayList); + list.AddRange(testDataRecyclableArrayList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + var expectedData = testData.Concat(testData).ToArray(); + + if (itemsCount > 0) + { + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount * 2); + } + + _ = list.Count.Should().Be(expectedData.Length); + _ = list.Should().ContainInConsecutiveOrder(expectedData); + } + [Theory] [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) { - // Act - using var list = new RecyclableArrayList((int)itemsCount * 2); - foreach (var index in testData) - { - list.Add(index); - } + // Prepare + testData = testData.Concat(testData).ToArray(); + // Act + using var list = new RecyclableArrayList(); foreach (var index in testData) { list.Add(index); } // Validate - _ = list.Count.Should().Be((int)itemsCount * 2); + _ = list.Count.Should().Be((int)(itemsCount * 2)); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableArrayList(); + + // Act + foreach (var item in testData) + { + list.Add(item); + _ = list.Count.Should().Be((int)item); + } + + // Validate + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount) + { + // Act + using var list = new RecyclableArrayList(); + list.AddRange(testData); + foreach (var index in testData) + { + list.Add(index); + } + + // Validate + _ = list.Count.Should().Be((int)(itemsCount * 2)); _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); } @@ -99,6 +212,7 @@ namespace Recyclable.CollectionsTests // Act list.Clear(); + list.Clear(); // Validate _ = list.Count.Should().Be(0); @@ -106,6 +220,18 @@ namespace Recyclable.CollectionsTests } + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableArrayList(testData); + list.Dispose(); + + // Act + list.Dispose(); + } + [Theory] [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) @@ -234,9 +360,46 @@ namespace Recyclable.CollectionsTests _ = yieldedItems.Should().ContainInConsecutiveOrder(testData); } + [Fact] + public void IndexOfShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableArrayList(); + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + _ = list.IndexOf(1).Should().Be(-1); + } + [Theory] [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] - public void IndexOfShouldFindTheIndexes(string testCase, IEnumerable testData, long itemsCount) + public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + // Prepare + using var list = new RecyclableArrayList(testData); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.IndexOf(-item).Should().Be(-1); + } + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount) { // Prepare using var list = new RecyclableArrayList(testData); @@ -249,6 +412,44 @@ namespace Recyclable.CollectionsTests } } + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount) + { + if (itemsCount == 0) + { + return; + } + + // Act + using RecyclableArrayList list = new(testData); + list.QuickSort(); + + // Validate + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().Equal(testData); + + // Act + list.Clear(); + + // Validate + _ = list.Count.Should().Be(0); + _ = list.Should().BeEmpty(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + + // Act + foreach (var item in testData.Reverse()) + { + _ = list.Remove(item).Should().BeTrue(); + } + } + [Theory] [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount) @@ -269,24 +470,6 @@ namespace Recyclable.CollectionsTests _ = list.Should().ContainInConsecutiveOrder(testData.Reverse()); } - [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] - public void LongIndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount) - { - // Prepare - using var list = new RecyclableArrayList(testData); - - // Act & Validate - foreach (var item in testData) - { - // Act - var index = list.IndexOf(item); - - // Validate - _ = index.Should().Be((int)(item - 1)); - } - } - [Theory] [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 231838d..e229d6d 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -246,6 +246,19 @@ namespace Recyclable.CollectionsTests _ = list.Should().Equal(testData); } + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Act + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Validate + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) @@ -320,7 +333,7 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] //[InlineData("Debuging case", new[] { 1L, 2, }, 2, 2)] - public void CopyToShouldCopyAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); @@ -348,7 +361,7 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void EnumeratorShouldYieldAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); @@ -564,7 +577,7 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void RemoveShouldRemoveLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void RemoveFromTheEndShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); From 17787b2cb1f5bb061c7e42ef5e41d281c95c1e16 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 24 Mar 2023 21:02:45 +0100 Subject: [PATCH 166/305] Benchmark results moved to Recyclable.Collections.Benchmarks to keep the package clean. --- .../RecyclableArrayList_AllInOneBenchmarks.png | Bin .../RecyclableArrayList_CreateBenchmarks.png | Bin ...ecyclableArrayList_CreateEmptyListBenchmarks.png | Bin 3 files changed, 0 insertions(+), 0 deletions(-) rename {Recyclable.Collections => Recyclable.Collections.Benchmarks/Results}/RecyclableArrayList_AllInOneBenchmarks.png (100%) rename {Recyclable.Collections => Recyclable.Collections.Benchmarks/Results}/RecyclableArrayList_CreateBenchmarks.png (100%) rename {Recyclable.Collections => Recyclable.Collections.Benchmarks/Results}/RecyclableArrayList_CreateEmptyListBenchmarks.png (100%) diff --git a/Recyclable.Collections/RecyclableArrayList_AllInOneBenchmarks.png b/Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_AllInOneBenchmarks.png similarity index 100% rename from Recyclable.Collections/RecyclableArrayList_AllInOneBenchmarks.png rename to Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_AllInOneBenchmarks.png diff --git a/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png b/Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_CreateBenchmarks.png similarity index 100% rename from Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png rename to Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_CreateBenchmarks.png diff --git a/Recyclable.Collections/RecyclableArrayList_CreateEmptyListBenchmarks.png b/Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_CreateEmptyListBenchmarks.png similarity index 100% rename from Recyclable.Collections/RecyclableArrayList_CreateEmptyListBenchmarks.png rename to Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_CreateEmptyListBenchmarks.png From f231f7e0aa791f20d002052b34a40d006e575ea3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 26 Mar 2023 23:16:34 +0200 Subject: [PATCH 167/305] RecyclableList.LastBlockIndex renamed to RecyclableList.NextItemBlockIndex. --- Recyclable.Collections/RecyclableList.cs | 60 ++++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 787eb03..e9ad370 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -16,7 +16,7 @@ namespace Recyclable.Collections private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private int _blockSize; - private int _lastBlockIndex; + private int _nextItemBlockIndex; private int _nextItemIndex; protected static readonly bool NeedsClearing = !typeof(T).IsValueType; @@ -34,7 +34,7 @@ namespace Recyclable.Collections public int Count => checked((int)_longCount); public bool IsReadOnly { get; } - public int LastTakenBlockIndex => _lastBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); protected long _longCount; public long LongCount @@ -45,7 +45,7 @@ namespace Recyclable.Collections public int ReservedBlockCount => (int)(_capacity / _blockSize) + (_capacity % _blockSize > 0 ? 1 : 0); public int BlockSize => _blockSize; - public int LastBlockIndex => _lastBlockIndex; + public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; private static void RemoveAt(RecyclableList list, long index, ArrayPool blockArrayPool) @@ -64,7 +64,7 @@ namespace Recyclable.Collections } else { - list._lastBlockIndex--; + list._nextItemBlockIndex--; list._nextItemIndex = blockSize; } @@ -89,7 +89,7 @@ namespace Recyclable.Collections { long i, copied = _longCount; - int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _lastBlockIndex; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; using IEnumerator enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) @@ -127,12 +127,12 @@ namespace Recyclable.Collections { if (targetItemIdx < blockSize) { - _lastBlockIndex = targetBlockIdx; + _nextItemBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; } else { - _lastBlockIndex = targetBlockIdx + 1; + _nextItemBlockIndex = targetBlockIdx + 1; _nextItemIndex = 0; } @@ -163,7 +163,7 @@ namespace Recyclable.Collections long copied = _longCount; int blockSize = _blockSize; int targetItemIdx = _nextItemIndex; - int targetBlockIdx = _lastBlockIndex; + int targetBlockIdx = _nextItemBlockIndex; Span memoryBlocksSpan; Span blockArraySpan; @@ -194,7 +194,7 @@ namespace Recyclable.Collections } _longCount = requiredCapacity; - _lastBlockIndex = targetBlockIdx; + _nextItemBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; return; } @@ -394,10 +394,10 @@ namespace Recyclable.Collections _ = EnsureCapacity(_longCount + 1); } - _memoryBlocks[_lastBlockIndex][_nextItemIndex++] = item; + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; if (_nextItemIndex == _blockSize) { - _lastBlockIndex++; + _nextItemBlockIndex++; _nextItemIndex = 0; } @@ -418,7 +418,7 @@ namespace Recyclable.Collections } int blockSize = _blockSize, - targetBlockIndex = _lastBlockIndex, + targetBlockIndex = _nextItemBlockIndex, memoryBlockCount = _memoryBlocks.Length; Span itemsSpan = new(items); @@ -444,7 +444,7 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; _longCount = targetCapacity; - _lastBlockIndex = targetBlockIndex; + _nextItemBlockIndex = targetBlockIndex; } public void AddRange(RecyclableArrayList items) @@ -461,7 +461,7 @@ namespace Recyclable.Collections } int blockSize = _blockSize, - targetBlockIndex = _lastBlockIndex, + targetBlockIndex = _nextItemBlockIndex, memoryBlockCount = _memoryBlocks.Length; Span itemsSpan = new(items.AsArray(), 0, items.Count); @@ -483,7 +483,7 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; - _lastBlockIndex = targetBlockIndex; + _nextItemBlockIndex = targetBlockIndex; } public void AddRange(RecyclableList items) @@ -503,7 +503,7 @@ namespace Recyclable.Collections int blockSize = _blockSize, sourceBlockIndex = 0, - targetBlockIndex = _lastBlockIndex, + targetBlockIndex = _nextItemBlockIndex, toCopy; Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); @@ -546,12 +546,12 @@ namespace Recyclable.Collections if (targetCapacity % blockSize == 0) { _nextItemIndex = 0; - _lastBlockIndex = targetBlockIndex + 1; + _nextItemBlockIndex = targetBlockIndex + 1; } else { _nextItemIndex = (int)(targetCapacity % blockSize); - _lastBlockIndex = targetBlockIndex; + _nextItemBlockIndex = targetBlockIndex; } @@ -572,7 +572,7 @@ namespace Recyclable.Collections } int blockSize = _blockSize, - targetBlockIndex = _lastBlockIndex, + targetBlockIndex = _nextItemBlockIndex, itemsCount = items.Count, copied = Math.Min(blockSize - _nextItemIndex, itemsCount); @@ -600,7 +600,7 @@ namespace Recyclable.Collections items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); } - _lastBlockIndex = targetBlockIndex; + _nextItemBlockIndex = targetBlockIndex; _longCount = targetCapacity; } @@ -618,7 +618,7 @@ namespace Recyclable.Collections } int blockSize = _blockSize, - targetBlockIndex = _lastBlockIndex, + targetBlockIndex = _nextItemBlockIndex, memoryBlockCount = _memoryBlocks.Length; T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; @@ -644,7 +644,7 @@ namespace Recyclable.Collections itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; - _lastBlockIndex = targetBlockIndex; + _nextItemBlockIndex = targetBlockIndex; _nextItemIndex = itemsSpan.Length; } finally @@ -711,7 +711,7 @@ namespace Recyclable.Collections } _capacity = 0; - _lastBlockIndex = 0; + _nextItemBlockIndex = 0; _nextItemIndex = 0; _longCount = 0; } @@ -724,13 +724,13 @@ namespace Recyclable.Collections return false; } - if (_lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0)) + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) { return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; } Span memoryBlocksSpan = new(_memoryBlocks); - int lastBlockIndex = _lastBlockIndex; + int lastBlockIndex = _nextItemBlockIndex; for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) { if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) @@ -749,14 +749,14 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex - : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); public void Insert(int index, T item) => throw new NotSupportedException(); public long LongIndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex - : _lastBlockIndex == 0 || (_lastBlockIndex == 1 && _nextItemIndex == 0) + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); @@ -784,13 +784,13 @@ namespace Recyclable.Collections } _capacity -= _blockSize; - if (_lastBlockIndex == index) + if (_nextItemBlockIndex == index) { _nextItemIndex = 0; } - else if (_lastBlockIndex > index) + else if (_nextItemBlockIndex > index) { - _lastBlockIndex--; + _nextItemBlockIndex--; } } From c7052e778f06cbd520e08c8cca29e84ed0807c58 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 26 Mar 2023 23:22:32 +0200 Subject: [PATCH 168/305] RecyclableList.SetNewLength fixed to properly initialize the list if the no. of memory blocks from array pool exceeds requested no. --- Recyclable.Collections/RecyclableList.cs | 2 +- Recyclable.CollectionsTests/RecyclableListTestData.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index e9ad370..36588ca 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -257,7 +257,7 @@ namespace Recyclable.Collections } localInts[uninitializedBlocksCount] = newMemoryBlocksSpan.Length; - if (localInts[uninitializedBlocksCount] == localInts[requiredBlockCount]) + if (localInts[uninitializedBlocksCount] >= localInts[requiredBlockCount]) { if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index d1119d0..5f8b179 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -16,12 +16,13 @@ namespace Recyclable.CollectionsTests public static readonly IEnumerable ItemsCountVariants = new long[] { - 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 333, 512, 1_024, 8_192, 16_384 + 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 333, 512, 1_024, 8_192, 16_384, + 1_000_000 }; public static readonly IEnumerable BlockSizeVariants = new int[] { - 1, 2, 3, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, + 1, 2, 4, 8, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, 8_192, 16_384 }; From 9aac3e0224ffa8b8a5d0189a482d26b8cd710fee Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 26 Mar 2023 23:31:31 +0200 Subject: [PATCH 169/305] RecyclableList[int] & RecyclableList[long] performance improvements. We'll now require the block size to be the power of 2. Includes historical versions. --- Recyclable.Collections/RecyclableList.cs | 130 ++++++++++++++++++++++- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 36588ca..eb9cd8a 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -16,6 +16,7 @@ namespace Recyclable.Collections private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private int _blockSize; + private int _blockSizePow2Shift; private int _nextItemBlockIndex; private int _nextItemIndex; @@ -37,6 +38,8 @@ namespace Recyclable.Collections public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); protected long _longCount; + //private int _divModMethodCutoffIndex; + public long LongCount { get => _longCount; @@ -313,6 +316,8 @@ namespace Recyclable.Collections if (newCapacity > 0 && _capacity == 0) { _blockSize = memory[0].Length; + //_divModMethodCutoffIndex = _blockSize << 4; + _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); } newCapacity = memory.Length * _blockSize; @@ -337,11 +342,15 @@ namespace Recyclable.Collections } _blockSize = minBlockSize; + //_divModMethodCutoffIndex = minBlockSize << 4; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _capacity = _memoryBlocks.Length * minBlockSize; } else { _blockSize = minBlockSize; + //_divModMethodCutoffIndex = minBlockSize << 4; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } } @@ -361,11 +370,15 @@ namespace Recyclable.Collections } _blockSize = minBlockSize; + //_divModMethodCutoffIndex = minBlockSize << 4; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _capacity = _memoryBlocks.Length * minBlockSize; } else { _blockSize = minBlockSize; + //_divModMethodCutoffIndex = minBlockSize << 4; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -374,16 +387,125 @@ 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; + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; } public T this[int index] { - get => _memoryBlocks[index / _blockSize][index % _blockSize]; - set => new Span(_memoryBlocks[index / _blockSize])[index % _blockSize] = value; + //[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get + { + //************ VERSION 1 ******************* + //int blockSize = _blockSize; + //int remainder; + ////if (index >= _divModMethodCutoffIndex) + ////{ + //// return _memoryBlocks[Math.DivRem(index, blockSize, out remainder)][remainder]; + ////} + ////else + ////{ + // int result = 0; + // remainder = index; + // while (remainder >= blockSize) + // { + // remainder -= blockSize; + // result++; + // } + + // return _memoryBlocks[result][remainder]; + ////} + + //************ VERSION 2 ******************* + //int result = 0; + //int totalItems = 0; + //int blockSize = _blockSize; + //while (totalItems + blockSize <= index) + //{ + // totalItems += blockSize; + // result++; + //} + + //return _memoryBlocks[result][index - totalItems]; + + //************ VERSION 3 ******************* 5.65 + //int result = 0; + //int remainder = index; + //int blockSize = _blockSize; + + //while (remainder >= blockSize) + //{ + // remainder -= blockSize; + // result++; + //} + + //return _memoryBlocks[result][remainder]; + + //************ VERSION 4 ******************* 11.97 + //int blockSize = _blockSize; + //Vector128 state = Vector128.Create(0, index, 0, 0); + //Vector128 increment = Vector128.Create(1, -blockSize, 0, 0); + + //while (state.GetElement(1) >= blockSize) + //{ + // state = Sse2.Add(state, increment); + //} + + //return _memoryBlocks[state.GetElement(0)][state.GetElement(1)]; + + //************ VERSION 5 ******************* 85.61 + //int blockSize = _blockSize; + //Vector state = new(new[] { 0, index, 0, 0, 0, 0, 0, 0 }); + //Vector increment = new(new[] { 1, -blockSize, 0, 0, 0, 0, 0, 0 }); + + //while (state[1] >= blockSize) + //{ + // state += increment; + //} + + //return _memoryBlocks[state[0]][state[1]]; + + //************ VERSION 5 ******************* 85.61 + //int blockSize = _blockSize; + //var state = Vector128.Create(index, 0); + //var increment = Vector128.Create(-blockSize, 1); + //while (state.ToScalar() >= blockSize) + //{ + // state = Sse2.Add(state, increment); + //} + + //return _memoryBlocks[state.GetElement(1)][state.ToScalar()]; + + //************ VERSION 6 ******************* 3.07 + + //return _blockSizePow2Shift >= 0 + // ? _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)] + // : GetItemAtIndex(index); + + //************ VERSION 7 ******************* 2.34 + return _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + } + + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; } + //************ VERSION 6 ******************* 3.07 + //private T GetItemAtIndex(int index) + //{ + // int remainder; + // int result = 0; + // remainder = index; + // int blockSize = _blockSize; + + // while (remainder >= blockSize) + // { + // remainder -= blockSize; + // result++; + // } + + // return _memoryBlocks[result][remainder]; + //} + public T[][] MemoryBlocks { get => _memoryBlocks; } [MethodImpl(MethodImplOptions.AggressiveInlining)] From ef285bdc670f754b3dad7711491e2d13788175d5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 26 Mar 2023 23:37:26 +0200 Subject: [PATCH 170/305] Historical versions removed. --- Recyclable.Collections/RecyclableList.cs | 117 +---------------------- 1 file changed, 1 insertion(+), 116 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index eb9cd8a..c230043 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -38,7 +38,6 @@ namespace Recyclable.Collections public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); protected long _longCount; - //private int _divModMethodCutoffIndex; public long LongCount { @@ -316,7 +315,6 @@ namespace Recyclable.Collections if (newCapacity > 0 && _capacity == 0) { _blockSize = memory[0].Length; - //_divModMethodCutoffIndex = _blockSize << 4; _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); } @@ -342,14 +340,12 @@ namespace Recyclable.Collections } _blockSize = minBlockSize; - //_divModMethodCutoffIndex = minBlockSize << 4; _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _capacity = _memoryBlocks.Length * minBlockSize; } else { _blockSize = minBlockSize; - //_divModMethodCutoffIndex = minBlockSize << 4; _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -370,14 +366,12 @@ namespace Recyclable.Collections } _blockSize = minBlockSize; - //_divModMethodCutoffIndex = minBlockSize << 4; _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _capacity = _memoryBlocks.Length * minBlockSize; } else { _blockSize = minBlockSize; - //_divModMethodCutoffIndex = minBlockSize << 4; _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -393,119 +387,10 @@ namespace Recyclable.Collections public T this[int index] { - //[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - get - { - //************ VERSION 1 ******************* - //int blockSize = _blockSize; - //int remainder; - ////if (index >= _divModMethodCutoffIndex) - ////{ - //// return _memoryBlocks[Math.DivRem(index, blockSize, out remainder)][remainder]; - ////} - ////else - ////{ - // int result = 0; - // remainder = index; - // while (remainder >= blockSize) - // { - // remainder -= blockSize; - // result++; - // } - - // return _memoryBlocks[result][remainder]; - ////} - - //************ VERSION 2 ******************* - //int result = 0; - //int totalItems = 0; - //int blockSize = _blockSize; - //while (totalItems + blockSize <= index) - //{ - // totalItems += blockSize; - // result++; - //} - - //return _memoryBlocks[result][index - totalItems]; - - //************ VERSION 3 ******************* 5.65 - //int result = 0; - //int remainder = index; - //int blockSize = _blockSize; - - //while (remainder >= blockSize) - //{ - // remainder -= blockSize; - // result++; - //} - - //return _memoryBlocks[result][remainder]; - - //************ VERSION 4 ******************* 11.97 - //int blockSize = _blockSize; - //Vector128 state = Vector128.Create(0, index, 0, 0); - //Vector128 increment = Vector128.Create(1, -blockSize, 0, 0); - - //while (state.GetElement(1) >= blockSize) - //{ - // state = Sse2.Add(state, increment); - //} - - //return _memoryBlocks[state.GetElement(0)][state.GetElement(1)]; - - //************ VERSION 5 ******************* 85.61 - //int blockSize = _blockSize; - //Vector state = new(new[] { 0, index, 0, 0, 0, 0, 0, 0 }); - //Vector increment = new(new[] { 1, -blockSize, 0, 0, 0, 0, 0, 0 }); - - //while (state[1] >= blockSize) - //{ - // state += increment; - //} - - //return _memoryBlocks[state[0]][state[1]]; - - //************ VERSION 5 ******************* 85.61 - //int blockSize = _blockSize; - //var state = Vector128.Create(index, 0); - //var increment = Vector128.Create(-blockSize, 1); - //while (state.ToScalar() >= blockSize) - //{ - // state = Sse2.Add(state, increment); - //} - - //return _memoryBlocks[state.GetElement(1)][state.ToScalar()]; - - //************ VERSION 6 ******************* 3.07 - - //return _blockSizePow2Shift >= 0 - // ? _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)] - // : GetItemAtIndex(index); - - //************ VERSION 7 ******************* 2.34 - return _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; - } - + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; } - //************ VERSION 6 ******************* 3.07 - //private T GetItemAtIndex(int index) - //{ - // int remainder; - // int result = 0; - // remainder = index; - // int blockSize = _blockSize; - - // while (remainder >= blockSize) - // { - // remainder -= blockSize; - // result++; - // } - - // return _memoryBlocks[result][remainder]; - //} - public T[][] MemoryBlocks { get => _memoryBlocks; } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 858964edbc642178cab454cef4baa5d05338b34f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 26 Mar 2023 23:38:21 +0200 Subject: [PATCH 171/305] MathUtils added --- Recyclable.Collections/MathUtils.cs | 42 +++++++++++++++++++ Recyclable.CollectionsTests/MathUtilsTests.cs | 32 ++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 Recyclable.Collections/MathUtils.cs create mode 100644 Recyclable.CollectionsTests/MathUtilsTests.cs diff --git a/Recyclable.Collections/MathUtils.cs b/Recyclable.Collections/MathUtils.cs new file mode 100644 index 0000000..30e5434 --- /dev/null +++ b/Recyclable.Collections/MathUtils.cs @@ -0,0 +1,42 @@ +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections +{ + public static class MathUtils + { + private static long[] Pow2 = new long[] + { + 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, + 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, + 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, + 536870912, 1073741824, 2147483648 + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPow2(int value) => Array.BinarySearch(Pow2, value) >= 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetPow2Shift(int value) => Array.BinarySearch(Pow2, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long DivMod(long number, int divider, out long remainder) + { + if (number >= divider << 4) + { + return Math.DivRem(number, divider, out remainder); + } + else + { + int result = 0; + remainder = number; + while (remainder >= divider) + { + remainder -= divider; + result++; + } + + return result; + } + } + } +} diff --git a/Recyclable.CollectionsTests/MathUtilsTests.cs b/Recyclable.CollectionsTests/MathUtilsTests.cs new file mode 100644 index 0000000..40c9362 --- /dev/null +++ b/Recyclable.CollectionsTests/MathUtilsTests.cs @@ -0,0 +1,32 @@ +using FluentAssertions; +using Recyclable.Collections; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Recyclable.CollectionsTests +{ + public class MathUtilsTests + { + [Theory] + [InlineData(int.MaxValue / 1024, int.MaxValue / 10 / 1024)] + public void DivMod(long testNumber, int blockSize) + { + for (var testCase = testNumber; testCase >= 0; testCase--) + { + // Prepare + var expectedResult = testCase / blockSize; + var expectedRemainder = testCase % blockSize; + + // Act + var actualResult = MathUtils.DivMod(testCase, blockSize, out long actualRemainder); + + // Validate + _ = actualResult.Should().Be(expectedResult); + _ = actualRemainder.Should().Be(expectedRemainder); + } + } + } +} From bd04f1093159622c6ab2cfa58d37268163c1cfb9 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 26 Mar 2023 23:40:13 +0200 Subject: [PATCH 172/305] Tiny cleanup --- Recyclable.Collections/MathUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/MathUtils.cs b/Recyclable.Collections/MathUtils.cs index 30e5434..98b63c0 100644 --- a/Recyclable.Collections/MathUtils.cs +++ b/Recyclable.Collections/MathUtils.cs @@ -4,7 +4,7 @@ namespace Recyclable.Collections { public static class MathUtils { - private static long[] Pow2 = new long[] + private static readonly long[] Pow2 = new long[] { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, From 0cc9f6b84adc875171cf89946c8435a56a5f754a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 26 Mar 2023 23:57:23 +0200 Subject: [PATCH 173/305] Modulo benchmarks added. --- .../ModuloBenchmarks.cs | 355 ++++++++++++++++++ Recyclable.Collections.Benchmarks/Program.cs | 22 ++ 2 files changed, 377 insertions(+) create mode 100644 Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs diff --git a/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs b/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs new file mode 100644 index 0000000..dfa9d3f --- /dev/null +++ b/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs @@ -0,0 +1,355 @@ +using BenchmarkDotNet.Attributes; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Recyclable.Collections.Benchmarks +{ + [MemoryDiagnoser] + public class ModuloBenchmarks + { + private long _cutoff; + private Vector128 _increment; + private long _lastCaseIndex; + + [GlobalSetup] + public void Setup() + { + _cutoff = BlockSize << 4; + _increment = Vector128.Create(-BlockSize, 1L); + _lastCaseIndex = 0; + } + + private static void DoNothing(T result, T remainder, [CallerMemberName] string? callerName = null) + { + //Console.WriteLine($"{callerName}: result = {result}, remainder = {remainder}"); + } + + //[Params(123, 10, 2, int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 10, int.MaxValue / 15, int.MaxValue / 20)] + [Params(int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 8, int.MaxValue / 10, int.MaxValue / 100)] + //[Params(int.MaxValue / 10, int.MaxValue / 20, int.MaxValue / 100)] + public int BlockSize { get; set; } = int.MaxValue; + + [Params(int.MaxValue, int.MaxValue / 1000, int.MaxValue / 10000)] + //[Params(int.MaxValue)] + //[Params(int.MaxValue * 100L)] + public long TestNumber { get; set; } = int.MaxValue; + + private void CalculateDividerAndModulusWithOperator(long i) + { + DoNothing(i / BlockSize, i % BlockSize); + } + + private void CalculateWithMathDivRem(long i) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + + private void CalculateWithAddWithFor(long i) + { + int result = 0; + int remainder = 0; + int blockSize = BlockSize; + for (long iteration = 0; iteration < i; iteration++) + { + if (remainder > blockSize - 1) + { + remainder = 0; + result++; + } + + remainder++; + } + + DoNothing(result, remainder); + } + + private void CalculateWithAddWithForOptimized(long i) + { + long total = 0; + int result = 0; + int blockSize = BlockSize; + while (total + blockSize <= i) + { + total += blockSize; + result++; + } + + DoNothing(result, i - total); + } + + private void CalculateWithXDivYMulX(long i) + { + DoNothing(i / BlockSize, i - (i / BlockSize * BlockSize)); + } + + private void CalculateWithXDivYMulXWithLocalVar(long i) + { + int blockSize = BlockSize; + int result = (int)(i / blockSize); + DoNothing(result, i - (result * blockSize)); + } + + private void CalculateWithSubtractWithWhile(long i) + { + int result = 0; + long remainder = i; + int blockSize = BlockSize; + while (remainder >= blockSize) + { + remainder -= blockSize; + result++; + } + + DoNothing(result, remainder); + } + + private void CalculateWithSubractWithWhileWithSse2(long i) + { + var state = Vector128.Create(i, 0); + int blockSize = BlockSize; + var inc = _increment; + while (state.GetElement(0) >= blockSize) + { + state = Sse2.Add(state, inc); + } + + DoNothing(state.GetElement(1), state.GetElement(0)); + } + + private void CalculateWithMixedApproachWithSubractWithWhile(long i) + { + long remainder; + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out remainder), remainder); + } + else + { + int result = 0; + int blockSize = BlockSize; + remainder = i; + while (remainder >= blockSize) + { + remainder -= blockSize; + result++; + } + + DoNothing(result, remainder); + } + } + + private void CalculateWithMixedApproachWithSubtractWithWhileAsVector(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + var state = new Vector(new[] { 0L, i, 0, 0 }); + var increment = new Vector(new[] { 1L, -BlockSize, 0, 0 }); + int blockSize = BlockSize; + while (state[1] >= blockSize) + { + state += increment; + } + + DoNothing(state[0], state[1]); + } + } + + private void CalculateWithMixedApproachWithSubtractWithWhileWithSse2(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + var increment = _increment; + var state = Vector128.Create(i, 0); + int blockSize = BlockSize; + while (state.GetElement(0) >= blockSize) + { + state = Sse2.Add(state, increment); + } + + DoNothing(state.GetElement(1), state.GetElement(0)); + } + } + + private void CalculateWithMixedApproachWithSubtractWithStackalloc(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + Span state = stackalloc long[2] { i, 0 };// Vector128.Create(BlockSize, 0); + int blockSize = BlockSize; + while (state[0] >= blockSize) + { + state[0] -= blockSize; + state[1]++; + } + + DoNothing(state[1], state[0]); + } + } + + private void CalculateWithMixedApproachWithForLoopOptimized(long i) + { + long total; + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out total), total); + } + else + { + total = 0; + int result = 0; + int blockSize = BlockSize; + while (total + blockSize <= i) + { + total += blockSize; + result++; + } + + DoNothing(result, i - total); + } + } + + [Benchmark(Baseline = true)] + public void DividerAndModulusWithOperator() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateDividerAndModulusWithOperator(i); + } + } + + [Benchmark] + public void MathDivRem() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMathDivRem(i); + } + } + + [Benchmark] + public void AddWithFor() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithAddWithFor(i); + } + } + + [Benchmark(Baseline = true)] + public void AddWithForOptimized() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithAddWithForOptimized(i); + } + } + + [Benchmark] + public void XDivYMulX() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithXDivYMulX(i); + } + } + + [Benchmark] + public void XDivYMulXWithLocalVar() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithXDivYMulXWithLocalVar(i); + } + } + + [Benchmark] + public void SubtractWithWhile() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithSubtractWithWhile(i); + } + } + + [Benchmark] + public void SubtractWithWhileWithSse2() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithSubractWithWhileWithSse2(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhile() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubractWithWhile(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhileAsVector() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithWhileAsVector(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhileWithSse2() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithWhileWithSse2(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithStackalloc() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithStackalloc(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithForLoopOptimized() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithForLoopOptimized(i); + } + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 93579d5..82b9a7a 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -10,6 +10,21 @@ using Recyclable.Collections.Benchmarks; internal class Program { + static void RunModuloBenchmarks() + { + var benchmark = new ModuloBenchmarks(); + //benchmark.AddWithFor(); + //benchmark.DividerAndModulusOperator(); + //benchmark.MathDivRem(); + //benchmark.SubtracWithWhile(); + //benchmark.XDivYMulX(); + //benchmark.XDivYMulXWithLocalVar(); + //benchmark.AddWithForOptimized(); + //benchmark.MixedApproachWithForLoopOptimized(); + //benchmark.MixedApproachWithSubtractWithWhile(); + benchmark.MixedApproachWithSubtractWithWhileAsVector(); + } + static void RunBenchmark() { var benchmark = new RecyclableCollectionsBenchmarks(); @@ -39,6 +54,13 @@ internal class Program ); //RunBenchmark(); + + + //BenchmarkRunner.Run( + // ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary) + //); + + //RunModuloBenchmarks(); } } // benchmark.Setup(); From 3f986c75a208ed00ce9160ec310b203b482c46e1 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 27 Mar 2023 00:00:42 +0200 Subject: [PATCH 174/305] Additional benchmarks added to compare iterations over RecyclableList. --- ...bleCollectionsBenchmarks.RecyclableList.cs | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 8758cc2..f9451d6 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -102,6 +102,118 @@ namespace Recyclable.Collections.Benchmarks } } + [Benchmark] + public void RecyclableList_GetItem_AsSpan() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + + var memoryBlocks = new Span(data.MemoryBlocks, 0, data.LastTakenBlockIndex + 1); + for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) + { + var memoryBlock = new Span(memoryBlocks[blockIndex], 0, blockSize); + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + + if (nextItemBlockIndex == data.LastTakenBlockIndex) + { + + var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); + for (var itemIndex = 0; itemIndex < memoryBlock.Length; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + } + + [Benchmark] + public void RecyclableList_GetItem_AsArray() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + + var memoryBlocks = data.MemoryBlocks; + for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) + { + var memoryBlock = memoryBlocks[blockIndex]; + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + + if (nextItemBlockIndex == data.LastTakenBlockIndex) + { + var memoryBlock = memoryBlocks[nextItemBlockIndex]; + blockSize = data.NextItemIndex; + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + } + + [Benchmark] + public void RecyclableList_GetItem_AsForEach() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + + foreach (var item in data) + { + DoNothing(item); + } + } + + [Benchmark] + public void RecyclableList_GetItem_AsWhile() + { + var data = _testRecyclableList; + int dataCount = TestObjectCount; + if (dataCount == 0) + { + return; + } + + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + int itemIndex = 0; + int blockIndex = 0; + var memoryBlocks = data.MemoryBlocks; + var memoryBlock = memoryBlocks[0]; + while (blockIndex < nextItemBlockIndex) + { + DoNothing(memoryBlock[itemIndex]); + if (itemIndex + 1 < blockSize) + { + itemIndex++; + } + else + { + blockIndex++; + memoryBlock = memoryBlocks[blockIndex]; + itemIndex = 0; + } + } + + if (nextItemBlockIndex == data.LastTakenBlockIndex) + { + memoryBlock = memoryBlocks[nextItemBlockIndex]; + blockSize = data.NextItemIndex; + while (itemIndex < blockSize) + { + DoNothing(memoryBlock[itemIndex++]); + } + } + } + //[Benchmark] public void RecyclableList_SetItem() { From b33904a17f76e0838cfe8c828847b9d68aca7118 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 27 Mar 2023 01:10:54 +0200 Subject: [PATCH 175/305] RecyclableList.BlockSize enforced to be the power of 2 --- Recyclable.Collections/RecyclableList.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index c230043..16c1afc 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.Collections; +using System.Numerics; using System.Runtime.CompilerServices; namespace Recyclable.Collections @@ -330,6 +331,7 @@ namespace Recyclable.Collections blockArrayPool ??= _defaultBlockArrayPool; _memoryBlocksPool = memoryBlocksPool; _blockArrayPool = blockArrayPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); if (expectedItemsCount > 0) { @@ -357,6 +359,8 @@ namespace Recyclable.Collections blockArrayPool ??= _defaultBlockArrayPool; _memoryBlocksPool = memoryBlocksPool; _blockArrayPool = blockArrayPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + if (expectedItemsCount > 0) { _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); From 1f86ef2c598a19d2baabd2cdcd0364d9fe6512a3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 27 Mar 2023 01:12:39 +0200 Subject: [PATCH 176/305] MathUtils.IsPow2 & MathUtils.GetPow2Shift performance improvements. --- Recyclable.Collections/MathUtils.cs | 7 ++- Recyclable.CollectionsTests/MathUtilsTests.cs | 63 +++++++++++++++++-- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/Recyclable.Collections/MathUtils.cs b/Recyclable.Collections/MathUtils.cs index 98b63c0..40f2141 100644 --- a/Recyclable.Collections/MathUtils.cs +++ b/Recyclable.Collections/MathUtils.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Numerics; +using System.Runtime.CompilerServices; namespace Recyclable.Collections { @@ -13,10 +14,10 @@ namespace Recyclable.Collections }; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPow2(int value) => Array.BinarySearch(Pow2, value) >= 0; + public static bool IsPow2(long value) => (value != 0) && ((value & (value - 1)) == 0); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetPow2Shift(int value) => Array.BinarySearch(Pow2, value); + public static int GetPow2Shift(long value) => BitOperations.IsPow2(value) ? 63 - BitOperations.LeadingZeroCount((ulong)value) : -1; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long DivMod(long number, int divider, out long remainder) diff --git a/Recyclable.CollectionsTests/MathUtilsTests.cs b/Recyclable.CollectionsTests/MathUtilsTests.cs index 40c9362..dd8e625 100644 --- a/Recyclable.CollectionsTests/MathUtilsTests.cs +++ b/Recyclable.CollectionsTests/MathUtilsTests.cs @@ -1,10 +1,5 @@ using FluentAssertions; using Recyclable.Collections; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Recyclable.CollectionsTests { @@ -28,5 +23,63 @@ namespace Recyclable.CollectionsTests _ = actualRemainder.Should().Be(expectedRemainder); } } + + [Theory] + [InlineData(0, false)] + [InlineData(1, true)] + [InlineData(2, true)] + [InlineData(3, false)] + [InlineData(4, true)] + [InlineData(5, false)] + [InlineData(8, true)] + [InlineData(16, true)] + [InlineData(20, false)] + [InlineData(32, true)] + [InlineData(64, true)] + [InlineData(128, true)] + [InlineData(256, true)] + [InlineData(512, true)] + [InlineData(1024, true)] + [InlineData(2048, true)] + [InlineData(4096, true)] + [InlineData(8192, true)] + [InlineData(16384, true)] + [InlineData(32768, true)] + [InlineData(65536, true)] + [InlineData(131072, true)] + [InlineData(262144, true)] + [InlineData(524288, true)] + [InlineData(1048576, true)] + [InlineData(2097152, true)] + [InlineData(4194304, true)] + [InlineData(8388608, true)] + [InlineData(16777216, true)] + [InlineData(33554432, true)] + [InlineData(67108864, true)] + [InlineData(134217728, true)] + [InlineData(268435456, true)] + [InlineData(536870912, true)] + [InlineData(1073741824, true)] + [InlineData(2147483648, true)] + [InlineData(2147483649, false)] + [InlineData(4294967296, true)] + public void IsPow2(long testNumber, bool expected) + { + _ = MathUtils.IsPow2(testNumber).Should().Be(expected); + } + + [Theory] + [InlineData(-2, -1)] + [InlineData(-1, -1)] + [InlineData(0, -1)] + [InlineData(1, 0)] + [InlineData(2, 1)] + [InlineData(3, -1)] + [InlineData(4, 2)] + [InlineData(8, 3)] + public void GetPow2Shift(long testNumber, int expected) + { + _ = MathUtils.GetPow2Shift(testNumber).Should().Be(expected); + } } } From 8c0d3c5fbc4fcaa8fabcec4e0ec047eb41003481 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 27 Mar 2023 01:53:09 +0200 Subject: [PATCH 177/305] Responsibility for rounding RecyclableList.minBlockSize up to the closest & highest power of 2 moved to .SetNewLength. --- Recyclable.Collections/RecyclableList.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 16c1afc..954125f 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -46,7 +46,7 @@ namespace Recyclable.Collections set => _longCount = value; } - public int ReservedBlockCount => (int)(_capacity / _blockSize) + (_capacity % _blockSize > 0 ? 1 : 0); + public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); public int BlockSize => _blockSize; public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -233,11 +233,12 @@ namespace Recyclable.Collections protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { const int sourceBlockCount = 0, requiredBlockCount = 1, uninitializedBlocksCount = 2, i = 3; - + minBlockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + int minBlockSizePowerToShift = MathUtils.GetPow2Shift(minBlockSize); Span localInts = stackalloc int[4] { - (int)(oldCapacity / minBlockSize), // sourceBlockCount - (int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0), // requiredBlockCount + (int)(oldCapacity >> minBlockSizePowerToShift), // sourceBlockCount + (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0), // requiredBlockCount 0, // uninitializedBlocksCount 0, // i }; @@ -331,7 +332,6 @@ namespace Recyclable.Collections blockArrayPool ??= _defaultBlockArrayPool; _memoryBlocksPool = memoryBlocksPool; _blockArrayPool = blockArrayPool; - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); if (expectedItemsCount > 0) { @@ -359,7 +359,6 @@ namespace Recyclable.Collections blockArrayPool ??= _defaultBlockArrayPool; _memoryBlocksPool = memoryBlocksPool; _blockArrayPool = blockArrayPool; - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); if (expectedItemsCount > 0) { @@ -554,14 +553,14 @@ namespace Recyclable.Collections } } - if (targetCapacity % blockSize == 0) + if ((targetCapacity & (blockSize - 1)) == 0) { _nextItemIndex = 0; _nextItemBlockIndex = targetBlockIndex + 1; } else { - _nextItemIndex = (int)(targetCapacity % blockSize); + _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); _nextItemBlockIndex = targetBlockIndex; } From d1e0482693591fbf072ad35c0813434840995030 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 30 Mar 2023 19:33:55 +0200 Subject: [PATCH 178/305] RecyclableList.SetNewLength renamed to RecyclableList.Resize & performance improved. --- Recyclable.Collections/RecyclableList.cs | 136 +++++++++++------------ 1 file changed, 64 insertions(+), 72 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 954125f..5149d10 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -230,99 +230,93 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { - const int sourceBlockCount = 0, requiredBlockCount = 1, uninitializedBlocksCount = 2, i = 3; - minBlockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); - int minBlockSizePowerToShift = MathUtils.GetPow2Shift(minBlockSize); - Span localInts = stackalloc int[4] - { - (int)(oldCapacity >> minBlockSizePowerToShift), // sourceBlockCount - (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0), // requiredBlockCount - 0, // uninitializedBlocksCount - 0, // i - }; + int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); + int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); + int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); + int blockIndex; + T[][]? newMemoryBlocks; + Span memoryBlocksSpan; - T[][] newMemoryBlocks = localInts[requiredBlockCount] < RecyclableDefaults.MinPooledArrayLength - ? new T[localInts[requiredBlockCount]][] - : memoryBlocksPool.Rent(localInts[requiredBlockCount]); - - Span newMemoryBlocksSpan = new(newMemoryBlocks); - if (localInts[sourceBlockCount] > 0) + // Release excesive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) { - Span sourceSpan = new(source); - sourceSpan.CopyTo(newMemoryBlocksSpan); - if (localInts[sourceBlockCount] >= RecyclableDefaults.MinPooledArrayLength) + memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) { - memoryBlocksPool.Return(source!, NeedsClearing); + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); } - - newMemoryBlocksSpan = newMemoryBlocksSpan[localInts[sourceBlockCount]..]; } - localInts[uninitializedBlocksCount] = newMemoryBlocksSpan.Length; - if (localInts[uninitializedBlocksCount] >= localInts[requiredBlockCount]) + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(requiredBlockCount); + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) { + Array.Copy(source!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(source!, true); + } + } + + source = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { - newMemoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); - minBlockSize = newMemoryBlocksSpan[0].Length; + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } } else { - newMemoryBlocksSpan[0] = new T[minBlockSize]; - } - - newMemoryBlocksSpan = newMemoryBlocksSpan[1..]; - localInts[uninitializedBlocksCount]--; - } - - if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) - { - for (localInts[i] = 0; localInts[i] < localInts[uninitializedBlocksCount]; localInts[i]++) - { - newMemoryBlocksSpan[localInts[i]] = blockArrayPool.Rent(minBlockSize); - } - } - else - { - for (localInts[i] = 0; localInts[i] < localInts[uninitializedBlocksCount]; localInts[i]++) - { - newMemoryBlocksSpan[localInts[i]] = new T[minBlockSize]; + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } } } - return newMemoryBlocks; + return newMemoryBlocks.Length << minBlockSizePowerToShift; } protected long EnsureCapacity(long requestedCapacity) { - T[][] memory = _memoryBlocks; - long newCapacity; + long newCapacity = _capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; - if (_capacity > 0) - { - newCapacity = _capacity; - while (newCapacity < requestedCapacity) - { - newCapacity *= 2; - } - } - else - { - newCapacity = requestedCapacity; - } - - memory = SetNewLength(memory, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); + newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); if (newCapacity > 0 && _capacity == 0) { - _blockSize = memory[0].Length; + _blockSize = _memoryBlocks[0].Length; _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); } - newCapacity = memory.Length * _blockSize; _capacity = newCapacity; - _memoryBlocks = memory; return newCapacity; } @@ -335,7 +329,7 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -343,11 +337,10 @@ namespace Recyclable.Collections _blockSize = minBlockSize; _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - _capacity = _memoryBlocks.Length * minBlockSize; } else { - _blockSize = minBlockSize; + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -362,7 +355,7 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -370,11 +363,10 @@ namespace Recyclable.Collections _blockSize = minBlockSize; _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - _capacity = _memoryBlocks.Length * minBlockSize; } else { - _blockSize = minBlockSize; + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } From 07f1ee688037b60c26f89f14729a51e2c35f6e2d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 30 Mar 2023 19:36:03 +0200 Subject: [PATCH 179/305] Benchmarks for Collections.Pooled added --- .../Recyclable.Collections.Benchmarks.csproj | 1 + ...yclableCollectionsBenchmarks.PooledList.cs | 172 ++++++++++++++++++ .../RecyclableCollectionsBenchmarks.cs | 6 + 3 files changed, 179 insertions(+) create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs diff --git a/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj b/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj index 190fbfc..ee77c13 100644 --- a/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj +++ b/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj @@ -10,6 +10,7 @@ + diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs new file mode 100644 index 0000000..f3e1c67 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs @@ -0,0 +1,172 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + //[Benchmark] + public void PooledList_Create() + { + using var list = new PooledList(); + DoNothing(list); + } + + //[Benchmark] + public void PooledList_Create_WithCapacity() + { + using var list = new PooledList(capacity: TestObjectCount); + DoNothing(list); + } + + //[Benchmark] + public void PooledList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void PooledList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(capacity: dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void PooledList_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + using var list = new PooledList(capacity: TestObjectCount); + list.AddRange(data); + } + + //[Benchmark] + public void PooledList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new PooledList(capacity: TestObjectCount); + list.AddRange(data); + } + + //[Benchmark] + public void PooledList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsPooledList; + using var list = new PooledList(capacity: TestObjectCount); + list.AddRange(data); + } + + //[Benchmark] + public void PooledList_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + using var list = new PooledList(capacity: TestObjectCount); + list.AddRange(data); + } + + //[Benchmark] + public void PooledList_AddRangeWhenSourceIsPooledList() + { + var data = TestObjectsAsPooledList; + using var list = new PooledList(capacity: TestObjectCount); + list.AddRange(data); + } + + //[Benchmark] + public void PooledList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(capacity: TestObjectCount); + list.AddRange(data); + } + + //[Benchmark] + public void PooledList_GetItem() + { + var data = _testPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void PooledList_SetItem() + { + var data = _testPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void PooledList_Count() + { + var data = _testPooledList; + DoNothing(data.Count); + } + + //[Benchmark] + public void PooledList_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + + //[Benchmark] + public void PooledList_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + //[Benchmark] + public void PooledList_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + //[Benchmark] + public void PooledList_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs index 7c4e9de..4bce805 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Attributes; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { @@ -7,6 +8,9 @@ namespace Recyclable.Collections.Benchmarks private object[] _testArray; private RecyclableArrayList _testRecyclableArrayList; private RecyclableList _testRecyclableList; + private PooledList _testPooledList; + + public PooledList TestObjectsAsPooledList => _testPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); [GlobalSetup] public void Setup() @@ -19,6 +23,7 @@ namespace Recyclable.Collections.Benchmarks _testObjectsAsList = TestObjects.ToList(); _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); _testObjectsAsRecyclableList = TestObjects.ToRecyclableList(); + _testPooledList = new PooledList(TestObjects); } [GlobalCleanup] @@ -27,6 +32,7 @@ namespace Recyclable.Collections.Benchmarks Console.WriteLine("******* GLOBAL CLEANUP *******"); _testRecyclableArrayList?.Dispose(); _testRecyclableList?.Dispose(); + _testPooledList?.Dispose(); TestObjectsAsRecyclableArrayList?.Dispose(); _testObjectsAsRecyclableArrayList = default; _testObjectsAsRecyclableList = default; From a2694a0b87fc4256e152d277c485d6f568906686 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 30 Mar 2023 19:39:34 +0200 Subject: [PATCH 180/305] Benchmark results added. --- .../Results/AllInOneBenchmarks.txt | 72 ++++++++++++++++++ ...RecyclableArrayList_AllInOneBenchmarks.png | Bin 85664 -> 0 bytes .../RecyclableArrayList_CreateBenchmarks.png | Bin 1117876 -> 0 bytes ...bleArrayList_CreateEmptyListBenchmarks.png | Bin 15888 -> 0 bytes 4 files changed, 72 insertions(+) create mode 100644 Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt delete mode 100644 Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_AllInOneBenchmarks.png delete mode 100644 Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_CreateBenchmarks.png delete mode 100644 Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_CreateEmptyListBenchmarks.png diff --git a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt new file mode 100644 index 0000000..096d5f5 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt @@ -0,0 +1,72 @@ +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|------------------------------------------------------------ |----------------------:|-------------------:|-------------------:|----------------------:|-----------:|--------:|----------:|----------:|---------:|-----------:|------------:| +| Array_Create_WithCapacity | 2,026,196.4844 ns | 25,507.5479 ns | 23,859.7761 ns | 2,028,675.0000 ns | 8.466 | 0.10 | 54.6875 | 54.6875 | 54.6875 | 8000029 B | NA | + +| Array_SetItem | 5,108,186.2723 ns | 5,237.9274 ns | 4,643.2862 ns | 5,108,716.0156 ns | 21.343 | 0.02 | - | - | - | 5 B | NA | +| List_SetItem | 5,209,882.6823 ns | 63,181.7652 ns | 59,100.2623 ns | 5,175,639.4531 ns | 21.768 | 0.25 | - | - | - | 5 B | NA | +| PooledList_SetItem | 5,341,256.3151 ns | 8,251.3008 ns | 6,442.0730 ns | 5,340,014.4531 ns | 22.315 | 0.04 | - | - | - | 5 B | NA | +| RecyclableArrayList_SetItem | 4,012,900.8333 ns | 8,775.9740 ns | 8,209.0515 ns | 4,014,782.8125 ns | 16.766 | 0.04 | - | - | - | 5 B | NA | +| RecyclableList_SetItem | 4,749,345.8854 ns | 27,832.8301 ns | 26,034.8466 ns | 4,741,316.4062 ns | 19.843 | 0.10 | - | - | - | 5 B | NA | + +| List_Add | 10,418,761.3911 ns | 204,147.0502 ns | 311,754.1071 ns | 10,363,307.8125 ns | 44.147 | 1.27 | 140.6250 | 78.1250 | 78.1250 | 16777798 B | NA | +| PooledList_Add | 7,747,779.9279 ns | 19,784.5733 ns | 16,521.0148 ns | 7,750,001.5625 ns | 32.370 | 0.06 | - | - | - | 63 B | NA | +| RecyclableArrayList_Add | 7,237,424.9399 ns | 20,275.5325 ns | 16,930.9879 ns | 7,232,728.1250 ns | 30.238 | 0.08 | - | - | - | 1229 B | NA | +| RecyclableList_Add | 5,611,979.0365 ns | 8,138.0172 ns | 6,353.6285 ns | 5,610,862.5000 ns | 23.446 | 0.03 | - | - | - | 733 B | NA | + +| Array_GetItem | 239,342.1403 ns | 174.5315 ns | 163.2569 ns | 239,256.2256 ns | 1.000 | 0.00 | - | - | - | - | NA | +| List_GetItem | 362,950.6755 ns | 183.6751 ns | 143.4014 ns | 362,937.7930 ns | 1.516 | 0.00 | - | - | - | - | NA | +| PooledList_GetItem | 362,154.3983 ns | 139.8387 ns | 116.7716 ns | 362,175.5859 ns | 1.513 | 0.00 | - | - | - | - | NA | +| RecyclableArrayList_GetItem | 239,285.5675 ns | 103.7975 ns | 86.6756 ns | 239,275.0000 ns | 1.000 | 0.00 | - | - | - | - | NA | +| RecyclableList_GetItem | 580,268.8770 ns | 7,251.9194 ns | 6,783.4499 ns | 577,058.2520 ns | 2.424 | 0.03 | - | - | - | 1 B | NA | +| RecyclableList_GetItem_AsArray | 239,421.2158 ns | 63.1265 ns | 52.7135 ns | 239,409.4727 ns | 1.000 | 0.00 | - | - | - | - | NA | + +| Array_Count | 0.0073 ns | 0.0014 ns | 0.0013 ns | 0.0074 ns | 0.000 | 0.00 | - | - | - | - | NA | +| Array_LongCount | 0.0022 ns | 0.0012 ns | 0.0010 ns | 0.0022 ns | 0.000 | 0.00 | - | - | - | - | NA | +| List_Count | 1.2221 ns | 0.0014 ns | 0.0011 ns | 1.2221 ns | 0.000 | 0.00 | - | - | - | - | NA | +| PooledList_Count | 0.0046 ns | 0.0043 ns | 0.0040 ns | 0.0023 ns | 0.000 | 0.00 | - | - | - | - | NA | +| RecyclableArrayList_Count | 1.2188 ns | 0.0022 ns | 0.0019 ns | 1.2183 ns | 0.000 | 0.00 | - | - | - | - | NA | +| RecyclableList_Count | 0.0123 ns | 0.0007 ns | 0.0005 ns | 0.0121 ns | 0.000 | 0.00 | - | - | - | - | NA | +| RecyclableList_LongCount | 0.0022 ns | 0.0014 ns | 0.0012 ns | 0.0019 ns | 0.000 | 0.00 | - | - | - | - | NA | + +| List_Add_WithCapacity | 6,895,420.4314 ns | 135,354.6651 ns | 260,782.4874 ns | 6,854,155.0781 ns | 28.519 | 1.13 | 62.5000 | 62.5000 | 62.5000 | 8000360 B | NA | +| PooledList_Add_WithCapacity | 5,321,730.2455 ns | 12,387.1665 ns | 10,980.9005 ns | 5,321,642.5781 ns | 22.235 | 0.05 | - | - | - | 63 B | NA | +| RecyclableArrayList_Add_WithCapacity | 5,284,050.7212 ns | 18,133.4955 ns | 15,142.2900 ns | 5,280,705.4688 ns | 22.077 | 0.07 | - | - | - | 45 B | NA | +| RecyclableList_Add_WithCapacity | 5,792,708.2031 ns | 3,343.4720 ns | 3,127.4858 ns | 5,793,183.2031 ns | 24.203 | 0.02 | - | - | - | 173 B | NA | + +| List_AddRangeWhenSourceIsArray | 3,749,286.9792 ns | 54,523.3877 ns | 51,001.2107 ns | 3,758,780.4688 ns | 15.665 | 0.21 | 42.9688 | 42.9688 | 42.9688 | 8000363 B | NA | +| PooledList_AddRangeWhenSourceIsArray | 2,171,012.5601 ns | 3,257.1297 ns | 2,719.8508 ns | 2,170,896.0938 ns | 9.070 | 0.01 | - | - | - | 60 B | NA | +| RecyclableArrayList_AddRangeWhenSourceIsArray | 2,221,883.2552 ns | 6,140.5301 ns | 5,743.8557 ns | 2,223,654.2969 ns | 9.283 | 0.03 | - | - | - | 42 B | NA | +| RecyclableList_AddRangeWhenSourceIsArray | 2,375,847.7958 ns | 2,930.1660 ns | 2,597.5158 ns | 2,374,959.9609 ns | 9.927 | 0.01 | - | - | - | 170 B | NA | + +| List_AddRangeWhenSourceIsList | 3,672,995.1904 ns | 71,114.1123 ns | 69,843.5671 ns | 3,681,876.3672 ns | 15.345 | 0.30 | 42.9688 | 42.9688 | 42.9688 | 8000308 B | NA | +| PooledList_AddRangeWhenSourceIsList | 2,174,547.5811 ns | 6,715.5228 ns | 5,607.7656 ns | 2,172,790.8203 ns | 9.085 | 0.03 | - | - | - | 60 B | NA | +| RecyclableArrayList_AddRangeWhenSourceIsList | 2,233,371.7849 ns | 23,364.1263 ns | 19,510.1037 ns | 2,239,082.8125 ns | 9.331 | 0.08 | - | - | - | 42 B | NA | +| RecyclableList_AddRangeWhenSourceIsList | 2,366,026.9252 ns | 2,060.7322 ns | 1,826.7854 ns | 2,366,219.5312 ns | 9.886 | 0.01 | - | - | - | 170 B | NA | + +| List_AddRangeWhenSourceIsIList | 3,745,132.7415 ns | 71,613.4245 ns | 87,947.7165 ns | 3,766,088.2812 ns | 15.676 | 0.39 | 46.8750 | 46.8750 | 46.8750 | 8000355 B | NA | +| PooledList_AddRangeWhenSourceIsIList | 2,181,849.8372 ns | 5,190.8155 ns | 4,052.6473 ns | 2,181,487.5000 ns | 9.115 | 0.02 | - | - | - | 60 B | NA | +| RecyclableArrayList_AddRangeWhenSourceIsIList | 2,196,517.2154 ns | 5,996.6282 ns | 5,315.8547 ns | 2,196,481.2500 ns | 9.177 | 0.02 | - | - | - | 42 B | NA | +| RecyclableList_AddRangeWhenSourceIsIList | 5,078,413.7835 ns | 4,215.5884 ns | 3,737.0093 ns | 5,078,082.4219 ns | 21.219 | 0.03 | - | - | - | 173 B | NA | + +| List_AddRangeWhenSourceIsIEnumerable | 90,967,420.2381 ns | 1,673,003.6192 ns | 1,483,074.1318 ns | 90,862,658.3333 ns | 380.079 | 6.30 | 4333.3333 | 2333.3333 | 500.0000 | 40778028 B | NA | +| PooledList_AddRangeWhenSourceIsIEnumerable | 61,756,721.3068 ns | 1,225,233.1407 ns | 2,301,284.8288 ns | 61,755,050.0000 ns | 258.529 | 11.85 | 4375.0000 | 2375.0000 | 500.0000 | 24000600 B | NA | +| RecyclableArrayList_AddRangeWhenSourceIsIEnumerable | 60,353,570.2532 ns | 1,156,946.0276 ns | 3,007,056.5522 ns | 60,136,425.0000 ns | 253.210 | 12.57 | 4250.0000 | 2500.0000 | 375.0000 | 24000331 B | NA | +| RecyclableList_AddRangeWhenSourceIsIEnumerable | 60,597,556.2500 ns | 1,206,305.7735 ns | 1,481,451.8228 ns | 60,891,993.7500 ns | 253.050 | 6.19 | 4375.0000 | 2375.0000 | 500.0000 | 24000503 B | NA | + +| PooledList_AddRangeWhenSourceIsPooledList | 2,206,735.6220 ns | 2,860.3858 ns | 2,388.5517 ns | 2,206,296.6797 ns | 9.220 | 0.01 | - | - | - | 60 B | NA | +| RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList | 2,213,033.1771 ns | 26,423.0604 ns | 24,716.1471 ns | 2,227,495.3125 ns | 9.246 | 0.10 | - | - | - | 42 B | NA | +| RecyclableList_AddRangeWhenSourceIsRecyclableArrayList | 2,456,687.9557 ns | 45,977.2836 ns | 43,007.1796 ns | 2,458,159.5703 ns | 10.264 | 0.18 | - | - | - | 170 B | NA | + +| List_AddRange_WithCapacity | 3,688,541.3969 ns | 72,945.5071 ns | 86,836.4373 ns | 3,678,040.4297 ns | 15.440 | 0.38 | 42.9688 | 42.9688 | 42.9688 | 8000363 B | NA | +| PooledList_AddRange_WithCapacity | 2,215,278.5993 ns | 4,630.4941 ns | 4,104.8124 ns | 2,216,485.9375 ns | 9.256 | 0.02 | - | - | - | 60 B | NA | +| RecyclableArrayList_AddRange_WithCapacity | 2,194,831.5569 ns | 7,092.2469 ns | 6,287.0921 ns | 2,193,706.8359 ns | 9.170 | 0.03 | - | - | - | 42 B | NA | +| RecyclableList_AddRange_WithCapacity | 2,863,515.4536 ns | 72,047.1346 ns | 210,164.8733 ns | 2,862,379.4922 ns | 11.665 | 0.69 | - | - | - | 170 B | NA | + +| List_Contains_FirstItems | 7,538,040,293.3333 ns | 64,412,902.0196 ns | 60,251,868.5137 ns | 7,556,011,200.0000 ns | 31,494.854 | 253.82 | - | - | - | 600 B | NA | +| PooledList_Contains_FirstItems | 7,473,775,857.1429 ns | 51,447,673.7955 ns | 45,607,022.7642 ns | 7,475,783,150.0000 ns | 31,226.686 | 181.41 | - | - | - | 600 B | NA | +| RecyclableList_Contains_FirstItems | 7,499,097,740.0000 ns | 41,307,956.8328 ns | 38,639,488.4507 ns | 7,477,484,200.0000 ns | 31,332.183 | 171.85 | - | - | - | 600 B | NA | + +| List_IndexOf_FirstItems | 7,556,502,520.0000 ns | 61,900,425.0807 ns | 57,901,696.0262 ns | 7,528,699,800.0000 ns | 31,571.988 | 243.79 | - | - | - | 600 B | NA | +| PooledList_IndexOf_FirstItems | 7,480,317,953.8462 ns | 61,668,998.5528 ns | 51,496,406.7947 ns | 7,454,337,900.0000 ns | 31,252.779 | 224.30 | - | - | - | 600 B | NA | +| RecyclableList_IndexOf_FirstItems | 7,528,408,666.6667 ns | 53,978,194.9132 ns | 50,491,237.0123 ns | 7,524,296,300.0000 ns | 31,454.598 | 211.23 | - | - | - | 600 B | NA | +| RecyclableList_LongIndexOf_FirstItems | 7,478,265,225.0000 ns | 4,283,522.6817 ns | 3,344,292.7517 ns | 7,477,691,600.0000 ns | 31,243.094 | 27.20 | - | - | - | 600 B | NA | diff --git a/Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_AllInOneBenchmarks.png b/Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_AllInOneBenchmarks.png deleted file mode 100644 index 2056af015d33619df2e01a84260da2e649d2f32e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85664 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%0Da} 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%0D=4&gJa&BhTuEFw` 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 diff --git a/Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_CreateBenchmarks.png b/Recyclable.Collections.Benchmarks/Results/RecyclableArrayList_CreateBenchmarks.png deleted file mode 100644 index fd59fd8d177e93e1401d503e410eb3112051a14d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1117876 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

Gdul>y2Hn<3-bk-JQlG}*J|M=zk9q0Yl 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

pNaY<({ z{%CIMS9gx!p7R=X$^Ih;0ua<2blrBH4SkCBgEMNWQ}2v6iutD>-GJ}+U{Xb>i3 zFb{~y{48$8{JK9a|Hvsi%ojcLqQ368gwcOvi?;)N{O6-=QL&xm^O_3U$CCn2r(j9( zXYZzf_xg>?3~bd((T_Bpi4mlCw!S?t%%<@q)OPP6Ed9Z8gJ}W?a|qNrC(aMBAvAd? z7V|GasY}3tjzFRoO1TW77zt}b^d*KG*i+-GvMc)56 zzD+D@9TXHZYHi|-0&=AJ*~CM=IeU(L)l&>znqAnAzo|dROF}4Nbll}m8;knX=Df(A zqtuPs_s5_lJ^biPx6WwSDC{PrZr@Nj0X|6mw%Xg{!;^6H_&t4;QMHdKGPNCS{3zPTNX)?14P^hnf!-Jpiekg#2>N#9a2^D$i3}HxiKRJ+3Lc z_u?$lX!+;5w&5#*^@cZ=2Y9LT-&oFH{MuQU6mv(fQ*U<3nw&r4-+AWX8#nZ6&rs0m zaC{WjIp<+0etcRu5S#7JSr4jW=iJW*beY@6h4;NYRV!oWYq=@+#PHKcb%*x*s)mDm2FkSo=G`XS++RzO32^Fa5cLwBHia&9-5@%feQ zRKx!tvYj>>|25lb)eXq_H7dG4>`Q8Qjp6=l!3;*(5&Lrqizb)c(dsu+QdM!9uvL5V zPs9cmCb97r|I|J4t~(dAcRC*f9!`0bSt6oww<^N{V9K8bHkT3$Dl6i@5Cp|?K=PG{ z&9y#6Ry-ZX@U5rqB2P{#Q9;K1O_3G zGoQ8es!t|+y?YJyU!uJj#?ym8Rr~0+sU;FM<%qx6$K%IeVbOT#{HEDo8|Xm!rrd_5 zQ%T;HHJ((~H1Ux=UsGMwIQWQqOu5>=^Tf1#UOxT@|GFn^cbW!wl95;wU-hkV94&t; zoQR&(I2E08$7Z7`aci%L%+_k$XMyb|3AYFig_Q6n^fbL8vs_v?Hur7)6hx&Q7<0#p zwc<-hchcJ?qev`t0V7nQOk}LbWLcAg;r@wzo(>eE2m5X_?9jzcpA?ytHZlIhBzyQc zM-e9*;_X?Xd%M`#OB&JcnVMyCyJPnhe_N_#u2ybocMJPLF=F(M`MI`>^SdQ2cVp{7 zAZXLw5OxPi@hp0I9k-$|mL@t3I{ksOGD}-+O40MShiY5I;$(XrZaD%KFnBk_!CI=pqNu2lW?CW<(lDV3bVxyL6VKJ#t^NlJ zrtJ30RQ4U)@&vexy6L27jlkq07?vzH;pf27gZpKyPi?c5=vT7O6O&Opn>HEo;Rs_H zO`gf7{FO@jSf43i(UBgknrQh0znK>69M;I{=e~3gSI=CemG;++&f3OXQ zfP9KdbcVXx5|zald52(_(=TV(!LaK<#k8o)A%N#NTwLlWigeRxyl9s+lxSW#Am=3S zEd3EkI}dKubamAy5lzv(q~mlEdWMYVUJ}QdtG9bP8AWpdas5h@mA133pz_7T$hFbT zM%a7~qos3MiBQo#`yJQQQ?}b?`?lqkVi^IoWZU-u=d3(Kb-SYx~LjDueswU6?f zsA2!UuHa5)sHhq@x$rh9!BNNl*>4+*I9ea-g9@48utMH>7qA=koc>c_NMSTKVT=q0 z4IfZ@`-8VfC~fOe@B^uCU*rTS1?I05r<&tW)M?n#_RM4vC~0DoCcl^}#O4L&?ia`o zHS%=-(pcyYewD&ZNIq59q|$Ca{1*Fj5YzOob|u5AF!N-)c_y0n6P|U`#98Dr^so!f z2ed12gixl)z~j>_qsYn!8a#Y-KZ8hPd_?;gs>UdSUT9yMd)AJ4`JK3(eCp+HQqrC7 z0l@Ric!(X)vWu$b9E%+JKjgh-Sd`lv_Pvo1P(fOdkPbn*1q7sF$f3JS>6QjjTDnVy zVTKM#0YT}Kp-Va?B_-c`bl1P$?fc<9o)6D)_(AuvMP{+?b)VOH{myHR05=1inO9^z z>wFsj@XkgjnOYlq-_O~j0~qq5dL2ULr|cC5V!RLj6vq+5={U=e1^Oa#dt^oE0OoCY23TZ>kKu+tztL)N3fG7!H_RZ)&p{0bpWKg`_OFbv|9f`_b zx=%7XRzRM=`SgkA`}mPd`yD}rS)M#m{--u-YS7BGnU@x@RyHA>scjyG_Y-f6%W3$f zdH*OcbZotx?pLPg->Prmi13hI;J+ni+i(+IC4DjD<#XvrJf*%?zdyT=O_`5Lw>N__7suz1tKHV(-^|^^y{`Pnm>=JR1Njw zds4j3p%e=#oK(Su5yDSg6frkF=9JNF$rst3aD$v)m(jnsm|e%shLKzbCl@uue5fK}&$u?7rQWYID*g6o z#teetw9WZ?K1RB@i|=U$KdpoaSygctH8*2MT99_Z(*FF;`RgZ9AD@XDJCRT%UZ1Ur ziJh*d1BpQ6@Lo}4EGrMs>#t>`%U>wpZH2^KU7c958?r|6W#Vyt@jW}__rH92Mu^ce z+5(;De-Iq{pi5z?JKo6m`%35aX(ytv-Uy_Tc?Np!zL(OF^i{@FaiX03Dd0R;LTX>A zyeUT&ipOYi&E5`(WsrD&)KYDWYnc{FYeI&A@A+{IsXSctXo2e}bs70x)$_2O)@8s| z*O^v?TM0X(UF?KU85@ZDc$cVMVhCQ2|CW_&^W>h(XgNPMuQ8E@qOQiQQMwPwbUk(p z7_8*4_^BQAeVcXowvhYP;pK_olh|qHm;D)JNlRX6K7#L9PRdmnF`CX=Oh{Eice2b} z*iXvx8#47Y5{sZtVREQ$vd_PxJS_2vKC%;N^pWayw(@u|D=1#~X#3ppGHEX+fPK)Y zdD%RFz^{2!-eBv-tyX8&J9G6JNsvABPauEH)}<=Vk2ZB^+TG*~+8z%od4RSbS9H zRH9>mMue!im!p(`dMm(G9@#-dn{EFS^hmscc3fsfgmmILTQ?2 zzK(>^C6hcY7Tb9dCv&DPsmiut8%#+YMTymfM_SsjVJ__zHmzejOMBHOLqbPz1FeQs z8=S$|Dm5OYu2(b{7;RyS=9d5r&sQvFj|1@)kUuzuod-qIL)Zq3VvmJT)iP0cQLYI%A?@&^n?#nWK1+#xQKM=aLT8L*Qj`y9*Z zRxv!slQRKH|D^p^Pkn7NMnr7Q1A$2_v+5g*q)E-RbpE9Ak%&uhJ!5I@0qeMfNH9Yi zWT=2!D(t>AdsY0$q)1&dbPqRM^XN6at(^qM6M>$?r8K?A<8;NQI~l%Kch$=_SeCd~ zzVYmEyXD|?Y>OFh=(Hu?#G1?*f8}~hbx0;KJL~E|)&_pdV^~HN=0!FytlLP^zs7Eq zSIi&@D>Zo*QtZ>KL8oS>=tC7(C8Xn-87Bc|Qj6EkQnQgt$>k~jFi>sdD$TJjEBB0B z{#=g}PbY(#J7-QrieZSvci>ImTrm~|Dr=5Kz=@fdl%A)EpQb?=!qYF$FD&RaHZr#9 z(It&J21F0Kv@+3YL|l1I!cft~+

I=dWd)HyNoWOI=6D9<062+SD(Sh*D`Y*ruHZ zZV;H}@;y_xkDR+qDo|6^VvCOsJ$%*G@idCpfQ)w?-%0eo)thHwnkOtXO`-#zvn^gc zcWb*h8XyoRWfihKXsV{xQ1-%QHLLE+K-Fo9P>~lO!y*@SV89T8;1ni}3L>ljl-M?p z;N009u`Cx}&Pl8~N6&mC5=5H$Z8?kdwXlUmK_P7@>gT28sK>NLk%I}sxNlk1Z*CgT zThusY26bwdjH z!3yh;UK=7Gp)R}KC)XwrkAeDY1k!k%onNBOy4qO+hX6ylBrlu%@>O@=- z-lvs$4D8|}tnSU6Ckq_i9V6dA3Me$bbQ^k#FP+gVa4X@NaCL)LMI$1?@mWT$SnvGI zUWm@lJ5*10$|Oq#j?8nRm2MF%<;_xDZL+8x@&KDn%~WqCEz)e36DnzN0nSKk-M)Wd`&2y2 zd!`f)HrWY?#)K-=?^>eA)k=BuTuMRRyfJyFPvHt;ME)cm{`{r*Bt?I9A&G%nE|*>p z@IU4e(!kQ_`bX5CU&?8D*Vi592~kQi4C9dYZbw5s+32mzy{jgf%NHJr$~q4V~QdV3NVi zu*Q4VOK`vwmvyR4anW~4h_5YIS3MUJJ!3#t7M#YAOph`W>DN7kPTXrb z2P?6_{8D3ibXtLLiU4+vO4)b2bc8i|FKDuH2;H3 zlh!j!b^D|XSNrx)UG~GSMb66lW>A6_FohC}m6@sj>WETa@}yjRP+D3nB}J-pc5lXy zr4|-Sgb*mnr}~haVh#tH4>8S`)(1~bim@vnLCq5PVa~)>g91OdwLi6a(S+U&pKttS z*VxnsfsK&6o=u;xs&UcSevDIM*?MMIISMRxh-M(xna1=X7 zn5MH4>9T+RiY~UB?5;;p&4|ZEVyoD5%7gbUTv=)uMk^Ee)*G`?9q6~*F&7?Dd;q?SwM zQj^ieVndnq+&wPjT~fi9K%wUaAXU2R0{Ko2ESrL&IqL0ur>cD=OfPH5dv4memq!nq zL>G14QfNO6W>w**FbQ0=?T$>tD)9kpw-*3FQwYn(QwsZ7acdw7!Tk|?HD={O8KV20<$vxA-?=le5;w(I$$C2Xntx^6^4&1{B%2kUemRRCBzl7wJyuc zeBxrOjeI?>Bvf|Ei=EsaxHDMBLgZJq_tvt0=(l&I<2-KMF0J(^fE2s=_1@M1pj2nE z`ONE8al4yDEU0WFboaOVe7Fn1DHZ!(%SM;m&u&FxURi$%x0n3n#Z5Evma{jUoXe#R z#{9}ch!S_FIU93Cpib0y-$(e8JId$7Db@R{xijJ+aW8UZE0f*>>+Ma4@}nT)e^lR& zz8@O+FY>Piz88h!^euS`;idb22p`4$vl)nlP4oLS1L=6Vlu}~J^G4DVB*2Saq1d_9I(tG8NEUV)K!mo0uje2`bNUwVVdWjXwfH#OwTcAz5RnS@O%(rFMo7N+fc=ZL%PU-M zagIk7NA7e%Vq_IG(|4V|L}DIU_f^t_^<_BPVw9tX;931MSW*RDl*VsKoPl_xgK0)upEm+Vn4MABCA_wcJY=_>b37Fd6fzQjX|KLR_4hpY*a{SDzC4FCv59>jin%@<^Edl# zWqS8=u+0CIsyFIuou}6I%DF$o17v9l{3}PbEuU&0VU+qlU}Mr&GX5R z-S}FpI=z8)IFRqjea}PA4qJIT3fo;?WhoWgSS)`3+mG7P|2SCL{w?*wq;im8{DLun zHu2pc`eIqApdHlfo-C`TRVPq6p`wAH=%F>DdRDKNyyq8LL*sW+l$I1;d@GgNNm#_Te&`$=UeMB5H^F@U zbkxGO#6Yl^OVbF=?|guJ)4O}iu|oZ$^Wzv}5~wbFk&0-IP!IiTG`?ONPJAisdL(p{ zS@q9P{H$Nu@6e1dpw#fb9zC~1e@ zyXS&RM`a1(odOTb#Oq6hP-@Y?IhN1I_f3rpNEx)n@tB&kimMy2V{5#k#!{B@(bNmD z6Vsm=EikrK@Yyoa4vABk$Q5hvIGx?KqgjZph-X~PaDoa%D2B~@9%Fvq2Wm#k4kEQW*||&{)o?IQqg;3NYaD-o=#$oMD@PR!^mxpqcrcxxsyoj9q|_*rpUe&9pWF4>KRQ46Q2hfv^J8s zMYAw#gf5bZT?oz!ju+aESKz{+jTIrex@7??`nOFT0#~q7$R4a`?QHs;5T-sgI`kUP zf5iQCEX1DX35`OEtV1Cizm(;VyZq*TF*SQ>w}+gi5n;tvlY zcRBuIX_lnj3CUC$pzlTqJk#Y9OoXEZ+^9{GZv0uz?VHxGT?Nqpmr`!s7jiV(jjZMd z61jTtZip2pi@dMLSG>G=BcH~3RG6Ikl#Z=T7@2iwk1?R?LbZ8KyfNH*U^X7D97?Gev>t^;b~w$`SPjblQYOR zdP*anImONDq> zJ9)p&8K%qHH{_$gt_gN0)!NMD+wVwB8_5ssr-Q}@i1Jc@LSmx|! z@V6h!RIsq>Lco=i^wn}YBn0l|;uyIrexwL^K~g~9vL8b^zN@Fe?jz4);e5;6@VuOh0uWO-a0q+KcJie=Ux7{W)w_F z@wS1x12qLpB@2;mt891g>*BV=Gr6Kg%U(|wZ;8U7{3XjxLMaJ5xT|7fY=ITz!Cxhj zK1`u1*zGX}>^v#&iF|Sv;%SQL`JJDG??MiF_xR&vGGv163X6_3V0k=kdVI>rz%Kcj&0b=H?TP zK_qTgH9ZT&`={~%sULBTn$9L=sMw;6ANyUNm3a!BCyFpletWFjsOJCuDqF;#;qFZE zKdNV-enO``=&bd>u3cug#2%}hyjiRb`>XX)7x^o~l=QlO%BTkrmwpS|Gto%(MfuvHg3#p}(vkO-|$*^eRT?@QO`Jmo@tAJw=J zUCnFpjz2Gq$ow{~!wWlV`^`e$BcVut>n~p;EL4bmeN@a0+2&ZvVC1(N#Zm_!hiNF1 z6-we$mA{-nV_TpKymJ9OYmQuMR$@MsA1EKW2hs)yo7tQWtk~ymj(9k`5a~U#GcI4x zkJY@WqIx00AR8S*f{x#pdiwx_D!Kc6pI^(lYrZg1*4Gi(T{RZ(zJsq34erFd(r4Da zi9(5uabDHJNZC0##CXx#LKSlt*x(ILJ3ZF9?f{26+_R>+cUk05@W>F)>Ra_pZ5=13 z$pz?LZ@YVG8`TATb?S~=g}R@xLi{WLqfnHdgdL{Z2}?-L@*h2oQ{d;-ze~o_wZUXU zQXU=y*-_+Vu zfcxECo_}Kr$SBIp-EyZ}R~ZsuLxs;O2>iVMA+n>ZOW{P1K-C1TR6DVm#PYd>&ErC0 z2{2tTaKBrcNrrdB3Qyu&ZeLM6Vx8&0XeUj>U}rtI8_#bon;g$u9iPuKZoA$1oY}xa zEd9A_s%q+n{`taUDIYqj?i9Czd)m>4as{W~iC$X9eR6^DWx0Lfj8-Eu^qD9Db+P+v za23xPnYA;4V5?G-?nsX;4%gfz1QUi_I%|~wQD9>u*E$3U z_iFxx_p;!OS6!3-we`k>vF#zj#)Awau z`Nt8vhMrT!Nh&o?pi3eB;saUGK$5Wx1bWZGc=RURN`0u}ozzAzCRu7SqpK%wdF_X9 zXcz6c{#U?0+ADZheGBz$R^1%mcWBLk`+(n0VuKl9r|qe?Vk z8w}B(KM6lql}j8lQXIpGN(neqfYe_RW)hCWFT!zM9ATq9nV9L%bc^9N0db? zMU=*?2f7GZBKxf29&PQj_VG6o?RyXZSU8^70i``^ju_YZdV!62R>~Pg5(`dLnO~P~ zXVt%?=gle~KFr{_d;v<=V3K`YggY}U@_o>Kw=QO%XB)TUOy352v%@OMt|n_QeREuK z1MtHk@y#46Dqr)fnNRj7kn&T|dS%N`+{!%0SQ=P6E9pg0YAo}n?80w{rxvt>T|jro z!Y=C$EJaA=CatLP9dRp#^0$ciWCk&NfeqmN@H$Icn$(CKp2|D4Kx@*#l1u8ac@eF_ z()BFEwXiQh^929g+Tx?LE&B=f33WQAqG`Ns#&AFXlU7U4P}e8vr6Y6#-m~^i`npDM zc@p_Gd_In8Rf(=(22zdnQhw3LV(6C6)OcFz9X1)9J+@t~-85dbDRe7)^@ATS;$C?yjg>d1$+o?6Azn)Y_8mWk5*<=309#Kk zN{jq4<89Y(g7?)wdTUZ`mGaQf2z(cr72a85_PF_-HIKuv>L_GRqs3C*|Cj}wxfLod z`A$s;yyKFpPU`|}IwN4=7#HMbk5Cle4rQX2yO2e|#$5q(`x`tq9ER$~0qBLht~F@F z;lA&9K#mCN1)?x@8@%s)L^1#j^o{q?Y==Z#^@Ci;xNnaNOR{#{nOUQxPgcI9PoyCI zh4&2$8gQKkBOD8S;C^C_+3MF^OT#F<2H^N|b(K}G2P?Oa41mtwn^0C@pwaNID;uEU z8FiofRcpr^!*Q}s2PrQ`fFmu{k`zq{Uz=qLXUalfp&t;J|JD^~)}*@y5-dP(33cm! z87^?Ey`Dv%O!P!4?4cW$2{@ybd%Qfq!^&xoyj*3~MTnvGuuODfwl4)7fyiEDEo#uJ zdNG~Y<;jHTMQOL(=LpW;Z++-A^XPB8{)JL$gucfjKLF9MnxIbz_X-4jQbTudk zrkf!JsDTKzX9s-P(D5H0Kikqq-baHn@&9*q`n%J=)vCO`Tc=Dp1wz@r;l|3F*l-N> zvJV>s`5WAFzVYaP`23YbIz$&NIv!ZG13JKSyV5T10=F;lnZeG2?y>xHnYeI@o^(Ztq5dm?|j>!D0-+%jC#wSBE-ad-B z?AgHPWwrvtVfg+)lS9T|Pb!KY4KR-OtZhqnF<&xI?{c=?Z|wDJ1}!13XdyMkoxa!K zk&4tA1ZLq${fY~{7MVU&09K1FUs6gFBJpt5UhoaLPEE`PJY!F)EL9c>f3Q)PDkSyh za~cbUI(`24lBH|BfNHaO8P3|gdsEJikVpMEV@^VSPKnLu`PCb*G=}3+XP?H~ozY0m z@~C^gR%!+A^#CJY3r#!-{`^yp4lfp?vJg0L-)sr*zwgA?@0X!Y-Cyk*Wb9G)ntMXP z#Uu;f70TnrmappTeJmLsgVEG~%7bexd`0Am)hTLzlsMrh^N(+7AjS>HZy+ zbSEWser}QspE-g1YO3zHk7-1WDF|vz8&sJGPBt33emeA@m~}*B;r0%C#{Ve%T8~h^ ze$*=SJ06k&yv*qKq_v?kqiYLttLw*4F91srbS=O+5%)`4wKZ@OU}dQ0t}f@|S|lF+ zMeN*QM7qm=S@sX^kpUw4MD+4-7OI>qeHTq{f9r7Y1ACOQ-alLF{;z!iqx`}*@E6?A zZc*dQ{q4Ew7W0$jh^y9K@T|-{pr#ftNWS8{?!0tP^jM&L$otAJM(!Uy*yjf*5i91Z z;H%xa=1V|#8a8VFwsUD=mDiiL)tKbjVgEsiu=fO8ce7P13NZCd}K(ns8p|++w6CjQ>Kh?{T&b&}< z6<@ZSeQFil2P@S`gfo=6zbE;w)HK1|k_nM}|FF`1Gwa!f?;p=IIO~3jxOg0KCjNjV zMlo$#mKA9lcj*SaUz??NaRw!m^bdgNq;~Acyi9;i7q+^ElP`?b?`AU>_O&*|AMM5W z%@X$0sHd5iFNzWyCeVq4Revs21W!3+om1qIoEi-+1p}l&FJQ^ z+0)x`iLRh6%$PNOzrU`|s)uT-Rum53;ZD%0+%D7 zQ1Me|ckgkq8l|Ht1J4t8mW7m;{b8}f*Dx$q%GdJ0m8rM+cLmhH)lV${_-Myy5V;0g zOU0HEKOm-Slv@e(8nWm(zR#WBRp+!ZJe6JWU11bEI~-C8nsGZZd`8Az zf*Q>hREpkDm={d7V8;2HCzLpMFGhTYldvS*&mh)}EEV;-ch}o?a1wQOcB<| zixZ2Ilk+C=pWKC?&k~MLdnJ9X{i*c!r)@Oc)_c#h3~!LS7(u01f#PBZ z#3{^IXzC`bj71ABG~Ll?Oy1hL;w|P91%p1GVgj$(+Uo#_WcEYG<|gU?^BBjKO=`TLPX=n+kdIdBm&qevtw+T6@d7if!c^({;iA$Spq5?^wuT_+k(l@hi_ zCEIXk!TmtZ9C_U|3)w+Sn}!_lI+4==Pl>4xe2HHq9P%V{53!svK1+=BZ_8nfD=(3o z*X>5pSHf<8_jt0x+~S5gC8wrLtD#N(zbQWmPHJ%MiAYnTQ>_?q`SZf~47&SG;)1{A zv+kO-xBNXTTvC^egHKE2J3B@nS(fyAMg@`;DGGy~&Rb9;+NOiCAis@x;jdd%T+9x9 z{P^YZA#W~(Ss>E0kTr?`V0WAw+cA@DF}E>$8?=-lLG+uZnt=&bwbM!EJg`GTHDPYG zsz8R_G5adun9IU|atE1oux^JS{N$3P@#Ep@v810j4Na*^?6p^7K9>O28H)^0@8T5gx(A*T#^V*+1PPj{0aW(yAPPsBU1r-Q_U{sm*piC!;!s94z}4b2Y>Dqg)b^ zqWk%Uk@3b*Z$uE*a8Q0Cy;^~A47P8}`kMN2xTMGG3Wbz&BSa3&|1oDP+ERBU;t@=x zj6_ZJ=$r&{S8kNfrXcGEe!q4Q87=T*d5VFhtOJ5bEl+EZn-KOlzcN&RMgQEHX zbB`j^^vhWNn=Jgiw;6E_A2&RX2YaE0!{83B#9o0~WJ$Pme0_^rul)=Dgr&dtr55G} z_6%F-ljC*z4$-^SJmO`vm|)Kdy?84zw=9uD=Fz)cI2|f5cNw*`vuTek>I=|0TF3f* z_swEeY&~EmQJ?xG%p1d3)sPIjR>YEFv=Nz-26y{_*D)fXzhXZM<6@uyLXd!>FAs!~ zMhR(c@-dk2ND97@Vn51rt%|1;nz6Axjt0(-!xZeum$9$S^{8Cfv_p@5$b^U^j5lFr ze{ws@)R$eO5T_}M8g(?efu#)Wv`ER&#>;gJpo_vWY9Nu$BiyeG2vrpfd3|z(X3{YU zPrF3^4!)>!hI%C{gcd(zG#DaRqVLt-pzBC(Wo#stYYHp z($(c;o+UvG{ivj58M46@-w{10xYt9;|lr<8B65OW5nrIL4*N;F3On9L9!EZjL;UnGPy%)uZbf!ZSR&8|!}if@xKgP*M-4bObWlDg+7> zERFLL_bWJmlQ7^KtDRG*U_CVaJnnN>2Wtg$of>~GHtlrjhB1If@v1l~$trb?0$&J$ z6*LArJqCB{%P!p>XC=_6l_N@DNt3D&A2TI@ zO+0{{Ke#oVtk#mAM}9zAtqFI9!{xZytnM7IYwS-mD-^FMov9(jNTP#?)Ic;nsjumz41S)t!3l*N0B{$7I3ssq$PH~{ubgy1z6bZ%mU|`p6-;L z!#Wd48~Y4tV{Z>$21WfNt`ZEu5={$?$eKWR{ljBm8c_w($G-f#kBvl1{svf}iQoFq z`oO{Z(XZ489Aid{lxX7ezg8gpM0u=M{+ST^oqG92lN+FJoHTcSA&2mT`uRnY14$kI z-}|isP}kYsXi>KB;Sd4{eLTEE_6W|xt}A$gp8d)u{8vV7OA1L*{j~=%Uv>b6aFeo_ z@5#n61P`rM^Va|;fs$Cw_={4GaT6R;|3f&VA{g^(v;~W#46}^$J*sDe zmWb}xRb)%S0Sq3VGXOFY{&|~R@5Z7F)ZTE1@Pn}~y&gSs|D8cUcWZz;;Qla zv18JivO%HrAYg{ujgJOj3(OBO1Yct6ck~i(|#m6=9 zslM~6@!Gv}fb)(9D0kC&fpS-qnYTzs_s$hM%!fWK#^qtY#^XQ1p|Z4P@PeFg`{o>4 zL9;#ZgPJ*UE%UA(>;EtK5C!|@ducVfIR#ZcH~Bw$R_)B7`fvMmKi1ww<~19#l{4bM zDZ%S~6&sE$X#r<=?EU#6^Zp39%=%N3P-y1@a{CY(&ch`V1V`>7^4lKBPBd-6I&nty zjIn6fM^z}I#`H7bkuNifF3nl)%zJ`89wLg}*%wA~koQ?|kkQhvb&Xjc->Uz;RQ=ZU zFS!$d9vYUa%|K!?IcD}vb}G-IM8?#Z&6^ZB#4qA>&Vk$66tv?ouvsxjcg#igFyyR) zZ*>e6$!&?|YQBLsKUJ|^%%BuvBPGjCl%-_ALpv~X=>(FLcYN%F zw(jSffzqEOJiHKTNcKVn4K6`E(sK4;b~gUn{sb;lG($+4`EXzFU731qS|MSaU2@4KJ55J3a!KV152#q8~VQr-xK$+2MvY6IU>P2Yo zMODERl3yK63PAIzOBlR}TuWoPU1w5ptPT?<)KoGtH9}cNME>aFlzD3qJ)$7{!FPbc zRV?~KJbUX2Gq!nZ*7$0#TIuP;GNB=RKo6(xWvGo{qtRzl;9j;N*w()JvG&f;)Ac@= z9k?R^5pBls2~aK(RWuF!1U^cWuWIZyig+>CB`FtFzI2DSQ z5Fnlh0)MtK6PQCZh zI}z7`&Gg}Y^=QHuaY-JuYB({T$~3H*G0=RbX7w&B^xV-*V|W`;?v;K+IC?zLWSHc- zz%c%RjHPr^e#9U}5nS{-+OV_K(Zw)w-peojnFm?evUy^D;qwV&{3(8E6t}tbKreh9 z9RHpWu5wZ=I$>ZVU+45Kd;8HeR}B1rLRd_^-EtVvN|wRU&a8TYEE&Lw+E{x15Yi`t zmFe}|V`%NSt@r7#eP>(nWA?)*3<2f?7x|<*%Hk{rkcYbcm(L4b*2Y@IVeG7h${R3N@8iyfyHMx7(KxaxkFx)lZ%)&*f|M#Ns zaDSwy3G9{i1+shK;&1qc>Y3x?j$I#V^pgjo#?N}##jYRw8Z5cV&$VN*izj_%3VlDZ zkKe)fcF*OF(6n-G;n%KzBZ~YF{ve9(e(Tu-M2aE%^ z$pzeSG2@oxv|ZO-&IWy4aN{ktInCr)r~WO?Rx-Q`+jVX6F%KJ8;B}igy}?(Jz<~P< zdXFa&B9sH49m>tCEw%59D{c(oV-8L!4+bEaWkl8RwJ#7Y<;TjsZ3C`Kgy`(uQQnl! zxO95SlbdF%{7@WfB@nV(Zx#pzaN|09w?wH=B590a_A36HU$qX>aaeZrbM zc$yV1aF&r1LOOdv!^UAfZ^84f{p_UjY&TP2s!{Z4eZb8F;N__9HTPSHnr2PHte$LG zc(Ht_CWqQLXPP2Mh#vx82P_r#R~jV>^M9$+L>{8sbbg32eco3g+RbG0-Sfd38_CG2P+7pszU z_h~Q7rljC#+AD89hW_exO>m0D>V!hhb;8|I6e!ZJGz4)v=$_TD&8cjpTv&Er)it60 zo~6j0IUimBF3C~}grr~NYIg>55BNzsFIU*w>_4{N?f+4ZZDf{eWdC}SV*0>@YvPvX zTJW&ZzT@#jAC<}#uWEP5FQp3r5I*S5_xW@OQP7oXfiW90M3MM6{o4zGIe#y}T0aBu zQIp3R$?%W%4Bjt=9e#FZelfy8mI;KM1+Lp0Bp03hV=vwV0MRcd3aHngp1y6lzMPC8 zLGnXU|CW(H+L)N54t|LR0ZOEZ|Tpz&pECFfHpA%1uP*tadQr9z+v-V zu`21Dtp-chO%e>VEj4KTfe@vQOrD+#{qg+Goc`#sP(ZKL+l2BqypnPSm(oh7B8!gw zel!=5J$ijAlWR=-c_0$?Z3mQjIU|O4slRnYj8Z^6sbeFcX3qIf>1khI9N3x{FQK~+ zEOjiF1}1ImSDE zrs)9yc0E=)i%f$Tr;SYg@%h}H;0^nn!9gO7iT)JOA`q5~Og)Q9n88d-7C7B$^ozsq zQlBiyHbf8}tBy!^_~JPc5@%QCq^0x_!J1oUnDWKOQ&dFRzZKZY4Cp`cb zCO?Eu{>2TwVn&YIj4TkdWtw$RdK!ucT$*-fm{N}g+0WU*1ivYVV@=q+6!HJA21rb2 z(SjKAHs@m>F$%M<2Eoq4CBNMg-vjS*`0S~lvpZ51C;!MbOB?bsr2Wwi$+4u|2Jtd9 zBn~0yOx;aiU8Q6)-*%-L7*SUmu)=ZQWXd3Hd3(EA+$h@Dhgid84(UHA-$`?kGGZ%CCrfNV0_7hSrr zzY9$8?vu^9=KF3xA$2I#9rQREPnCCD9u2s}BG2wzqU3B&c3E}iE7ZsZ%GX$&-F`;{ z1THmrZPcjA^qGaq;Av!Nm6k-n8l^HQ(c~0;t0#LQei%DJ1*(1m&{e2Q)ib2oN&6%j z9L(TcMiqXYgoj$o_8uZ!tm3j+RS}A_b+rj73J>s!@)8EMMoKF;=054$4 z@MCg}P*E?lwY}Sw23C}rrGK$3K+7n$!aL40ptIL<3G>1$l5t;db$uwJ?v0#KE2JQ)MPPBFfA_ zs|S3Zxnnh?zG^4w)GvJ;X=Ctv!yy7L<1-W^ClBV12*wGyO3|qb-!NYiv(JF#pM|>+ zXME#XjjhdvBYO}Qt#$j&+0qy-(y>EKzbj4{(6(sj3p$?1{(Ce|HIx|JaRZjzW(K0cuvZc1Z zVy%%hY1{-=18Lq}$4Hn{ooyqRk5HO%-`xAF`#G*Fz7Zc2ghIs&47C(&YaYzPFS&!ZLGzpMCPctCh1~c7@GuX|gGZwL|r=-MrcE7Ka71kqR z6QM9Z&l?s(`fOJ~{DE6FtkP#g^fR55YRxrQ-Y)W~6apSe(m#6zz#jS&7#N*9opjn1 z1NKu~wz#Zux1M5C0eGow=9~?gT}OhzPeO+CstVZqqUj6=V%IhOD}JEp%knZEsxsL3 z{f2~}Z2@n&0%tx!qBJ3}`XwTu(&yHO${R53bGV=$$Q&5|qpVBBuh*eI)D6Loi%y5B z6JnLX5S(C%G%Aq}f3#|n@Oq+%T(0~V`CvKWfl$FxghzG)$nAp=e4W@<-V*2mV2yGr z*pqLhtAuh64;Leu^pcbW>JryDqMAM0Y)Rp8r=l_FQ*V?%x9^;(I zJPmMD^i&q=U~J6{Ln zWrx%8pybnE5psaJuX&d|rC-$8cdwtHiqSvx?=~yw-;%i;Jisu6-OMYt&HrBQcoPFz!x!#X~sJgUd6bc<>&TP$dy0MMN?4^{O2)=qnS)UJKjFiWCm>-s) zm&0wdr*MK|bQpNFwV}0L8udC>?>M&x?j4H zh<|A2feSLlH2zbsxl-a`j#Cb?A?l-l1}AK{@zjbGx&5==z71Lptg4S$h$XICif0)qCO~>!denWnkw?L;PpJ8uhL7Q#h6Rkc?ERX@E-o2jB;3jP@)1 z%^1yZEo;EdFx$dpxnvvL1o2GBXn2yU(dng~m{k)kjH=857nUWLW4BU!r(Uv#N6*344uak_BH5>ug~v2GX@!SuG3z+f#Un|`fKoebfU=dcGCI7;DNt;% z|Hl*`cc*J__;kG#OtC#EKC6R*;m!zk{pC$ol_V8vq6xE^MOz#3P>P7ywyy(!4)XD2 z;)VRSavz^vxo|%+*ECI55k!KOyJg6m=oGsShbBCR7*m_6dA(`whwDS1w@2C>OiFD4@N3lyCg_QDO6Oh@2K3Jq0%q*r^ki z_;#|bxH}cUt_fT`e4&{+s@SyiIW_O2Nc5`&jVwXueSLK#p6mgw99b;y`BJC3Qa|e( zGCm9Lfc9uu!>pHtb-hh zM17xH`&M^fSu)aZ^(NhR37CiOqzhlpzKL^ZG&>TGWs5CnD(nypjhgrU^L!?3(a zz@YagVa$Z9m)d0vgvy%$W5Oy^{Fc5{yIEYo&5$hU=6)Qf22G4jeeeA=n=swe)jec3 z@b6x$eN3zYPX(~D95DslgY`viDC_%0VpH<~h61sNI%WUBP_8|URXu$;atQ;3?kh9; zeeY?LZ2R)qQ$xkZ1{;U=cn=}a>WWEUs~2gUL|aYE`W4u**hz&EI_PR+fu_|V5IG1m zb}IEv+8rLjfeB0GamOQ&iFcdZ(uOhNSfP?9E=40V=ugs~#XsaZTNJ_MQ)B#ejirp| z%_!J>5zP6hQ?3w(){V9NBVKv+qN2Inf7j2OqFOU=^mR(S!>R!*dpI)^(!CJ;couzB zz)G7#U4i4RciKCkSGA1!I)ntC{jLQ55ib%o9GS90XQ4O~Z}*cF&G=fmG9a95&}* zg7W2+6!TLxUb!?FG~g+BQcee^!V=mW@wSZ)SoNzH>EX1Id6OhVcJO0bxm)=1qs*~w z2y0iPou=1Oay*{gA7C0)t}Wy}bimE%um&3fiw-ENZ3CRaWOpt9>`@fKMwbM!<&U*? z%#*5Sb0{xfxo?{Y3(l@rwp;}1R_gb6H4NuRY(AN#iMOdKe>ABwI$SIzw7av;^O^eN zsF|KZ7;XDYE&(b`B;0fP_G3Ypw^T;!qCN$8!~_O%P>c}Ac^ZMVd1`i)P4zPAZHTcm zwX^j9uy>YGRd4OPwiQH>78H;W zq@)#Dgdp9u=n$nrq*Fi%5mdTUnzfLSB_K!$N_U5p(nv~4pZQyWy6fG(=lsVwq<*K4C|P?>*@a0fF zw?`~g;8%JjmkvD8=A6$IX&Z2V#(Mzp zO^!nLADBGx$wxV|K3EXHNB5 zDc@BRj*SqdZXS>P1mL@|MQ=fKEnD2IQ5`!fJT7&M$^|b!xTNZO)abA?TxPG|7kY4_ z&-2v~vyy6l1isjKl74NI@(Fb-G?Hg`hWUZVr{a*z6(QYkL`pd(U5=AO#9mX2P+Dn~?D_+d=SQ+fm+4`TuWy

UjI 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

a`7u#9x$s_VW0YR$l%^dP_ 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

$#~a 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

EKz`pki^y!BcH^b-2X&5Z0p_X@o{!)C% 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Ëdm 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 z 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

_u z;(JMeg5z@uAAL*-C}hi%v!K}y0(Axp0l zA)$T-Y!|e)seb@<>}oL1mYM*tD@n+KWy->Dxb(YtE*HnFWBqb+?8u?>_gwBPSaxT7 zN!a|ze8X zs>zFok6_B^s!?#$Z68J0BDcW3ggnv$%kij=M|H&`7-!~mT)YvrB4h>C`nb-OBV7Rm zFW_*Vab55oHYs}eXq)0BimZy;B;>TD<|IHkanVSSW54EkE7iya{5m37j00%j``Mp& zgZ8*T54bZrnD2owP8cp9@D#)kox3sjhlKm|PGZ;nIlwiYG>POQB^}_rzs-tpR?$n| zIs_N{fe)#OsoCBLHPYQ|-KLEOx)(5$Z4tfsC_AF1d9lkp@0?{tVB}*=&65n1A-@-P=E#fl#wO+N% zpD3nFs&>Lv$)Gw)TmqfUz{fF-gcXuh@u>d-F@d*A#JLpqeM~I8Gj}k?$6ZtGH%_ZV zma1=4Czh%!g!GCwkAbC1T?Embr-W$E%Zox4y61CEs zYJ^Xu$bDRs9da~5=ba>So&x{Tt1gy6uB&5298Nb?kVxv(^}aWVU~NeDpqxi}8NFj~ z_ryOlBfU!ovB1IgvQA)NgQQ6(W@JOL>ybK*806Rm;+%@rQN4Y}6W1}zl(=c#rC>DT zqY^E4Sr2b?kzFZBj}vP7Fmo{uspia#9R%;sCO`1OoYNsZ4%QS^Ptc74i!T@3-N6cZ zT~izsE%+N9nT_xESbSxl|oZ-PN+VW8t@ls#8)Txk$POD&Tm50rxxQyzm%`np}} z)+FS-rX-LS$hD~N%qI}c40d67cP8%%*v?-KQqDnZg7g#BpHF2Kz_KMQs)=L>=&p7X z&goplvUxm*5leJ^F{yC*xKL#4-cSZ3yQnkju;?SvXqK>XMQuc0^Y!?CjpJjka{BFPnSA`V3%~dMvMNQT{4Lh$CvgNGPfNjik9&Q73@O(r90La{-8akwe(}W zTjeU&4iD>8gjB&p^Xn6hzMC{D6@%Xk2^HklGHQ*WB0UKk8zI0gVZ6KI<8?D+rjD_h z!7mu}pYy~}v^QDwmb6-^qMyR4EY~TkiFqTDM}Ws1eLvgfoL763pEViE-*t*Kopg%) z)Kp3E0etD^{5^N_R5=#f8#pv#UY0IEW=uQuY3$rY?yIi6kkhbRy-Q-xJjuGrA|UHj z5G|?7x23HK8;^~50bR`Fw~f;VL&;52q_yHHB=sDEpX8%8`R=Et`%yS1oCo}Shi`Da zB$ObIL)teiCqafgn20i?(@(WQESPos-#^bLQ5uOTc)+aNTtqSF>K7(b@l`~`3lu)2 zDL-c%D$3Dv>NRbR1sj~gK17(EYdF1wAM9?*4JHhb-lU#SRSU*>qU`InUhAKEO^sqm zQL?W{fY922nAJksi3uDTGfYnXJTdys)hZVcX2r`k=zz!f*X_+scCBuL)R*AjIU18* z=r*PMxw`4_BHx^F+IXHo^v>gSoDkBCRuw7=Zq>Rylc(S)!95Vj!piBH0;k5?ylUtI z6dGRwaynZ@^XzUY3c(`8;YHwi#aw9lx&)+wwd)r8?Bu-m#~Bt$^!NC&bt4%4arBf= zr@;@P%rFf1FZh0R$ITeYSN0O(m9(1X#Ga8V zcAFj5J{aO5`K2lk#20TqWJmBwnwRp?P(77fBf|NEO`01#BW|=Syf9y39o?(NVxfJQ z*{mt^!tzws;r!slsZ58KQ<1EKca)rq(uPpP(>vpw15qX04A@b1%ThBy2Bjh}JK#Sx z<%Nba5VWr$s2(}iA;oeC3&hnIt9KvpF)PnVtmRdtc(3+T$5 zrSu{JWi}D=DP2z^Ybv2COG)j(%4@vKOsO+BvHD(`iaSPTd4a19!-R*==P^_g^V%XP z=9&W|$Ig%0G-WgeIb+zj_gSIxApn2^I$TEykE$H>c0~f7N*P}#wsNZDrzV}}j@p@D zZOEQsNSJUs`dAta;wFQ-Y2{93C#^{ZXe(am=2hHx!~u~M?G^0mtmb{w(Sp3!G-kXH z7-y=FRLXAiTVabH8>ub<6Xh4~s9khH8WYBkCHF5Z^@PH5Sug@4YZGXQ5-gI-=%A;T z`qvinFj=AF1c^?TC)n`u0ftK>>FOskY1V`57s^B=uKNjkQ|0-|bXn-56G%|&*u_9l z((PGEL=!pu@#&GcTv#WK9eVQo#>2j9Tf9elkd{yVDjFOR$uZN3Pvy7pbi0lD~iI99{M{>CI8wbJYxVV#y z`tLDGzo7e@-?qAMF#ooqb-JF}_FqM@;3#Lh08QH`%_7OhB2^Muzt`>!8ByL`d-*A( z9>4*h9Bd|F83c>xZN|Tx-{K+omV3zWou|GHxw-Ufzn0ETue}g(U1>^w`AMN&tn=Ww zCAsvqZ*GkJK1zrm+;{Qd^TapO1NfJNB99pOBp&RA?+6(6R9_xeJ?ePqr9(EW=E3u( zv6qxIwMQ`!yVrw6m+LiSEtFPz z;$x+fcWU#~o?}wN1@$4hJ2{^*PS!QR$$B%x@OZlYqr52uY-SRD4#afUPHQ7yX|ZRm zU^c^sWxtGghX34DOAgJBe2K%Atv5Q9XfiSC^c9AuKoIeO0;N*c)B9CP_y+h}q0F0+ zsY4zP$Ej6+o?@OsAGv$#VHw!YH|~S-qvkQ{Nl+G2uN69TtlE0#kbAF}=pIDsUsv)U zS_3>5Q>m;mH%jl)VT?+Te9fq59=mcu9o=A^V{Hv$lGk^Gbq*$gtT}^j&RtB!Hr7kz zgI5_^Pd=k=*uhog!KV?(gsqz+NxwJ1-nCgsBgv|k3G`9@QH@tkp&`@C(?`7s9JpP- zmg50B$)@h49M3A5l}AyeOTLXvqKjt?7M3C+GE;Uj2Q)&{PYEK)F&J=&i|bWS#QP$X z+cwiZlrF0Nqj~lD$`T;+S4(H*(F8TR4jWMZGTQ#Z(}cbprwJ*aSsdq(cNmLQoJTgb|bmK^jS=Ly(pnB&9`>l1^zPq#0=> zr3DFT>CPEu-u*yP&+9&~`#hiZJ>Ng?de?HfmN?AJvB$P=+qVDWDo!_%fn0FGNVXi@ z|4mu5Yv`OUj zs<6$*1l^qWa%#=)BDkLiZXZ9pszBN0`NN0hIaiaydJJ8PJ4sVYJ2IkvsDFVQuVv!c z90rDAM=w@TA`~Bv;1P-e2sAy&&H}oFt+#$%|5c$W)qO#(BXs^8z}1kIaRNFr*k^_b-*}Av0yxpzqvJDu>&)1?1^yjvCnq+AP zWac`x6~Z|ngi=rUlTQtU9wY;N#gub94;?`9XKZ?>(PTbR9ii?#?#Pg-~Zu^_Xnf< zt*(Ok6?y+NeajFQ|8hfkmAzt}k&f}_2cb492_%9eeMt%QkM>05nU>S;cKyaCeRd6< zeVnc=K5^AaJu1s3s=DFf z#RloFW;}Txfi+n;2244)?EW0j)6+$^8XEVE7RXs7{s1Le*W%)H*s(cJws-;9J+S1E zkuGWZusjBa(qH?7^gDX*Li!88a7mEJ(1IzDg}CqQCC*vVeTh;-3FNfK;dxebT%@l6 zCmTO*qVk++MtYXkFYtXEi1ggrEKgbc*he^han2eI-N>|Pyce}L=N_G#DftDN$Npb| zNl% z?TBW+ZlGstwrUUj9=YZ8g|~;PZ>3)SsiS?+9os38%9dV%3}xEDFzOR2igg%9HCee> z7!y?$u=KM7SNHd)VmI#;3|HuHTeW8a&%B@aP>oA6e%;d&Tgor5?pa?~c{%Z$qCH*j z1Mr#q8sbME%ZSQ-x9e0ZaH*;nYm~B+<95y8 zX*4;y6@KcT(Ip%*$6+4vKMtYqgMOm#|G4W2U$gC+$;At{X_EDM!pHNN^%|wfi^vvv2Cndcr7@4@OwNl-VK2*5wVdvOOt;GbCa=V~Z(2wlDN-Bx zPU@7Ro)r0O!z1vG5j~y)5_y_0Hwst;{#0fPuBUitmcqzyDg*IAz%lBrVIQd?xO40L za#}n==Dll2Hy&*VVYZwX4%gu!(YpG=j{{|y%Tnd#nJnv}$0-*9dP%kw2vI+Y1-H>< z(sz_2iI?RYaf7RW3N!HvRg2dQzVyFKin$L-T4Utc|Ixzv?|o_{R3AbMW5 z!&aZ#Zc2jen%u%+n!C}P{*NFfWidQPnp=wLyo`a;G&G7#Q|kmF`$i56grT#)Gu;Pv zb0w;h6K6o}#z}u7)w@taNBDNnG$DPEUuHc?jES#2_6UP4L{=~^)A3QV0G=3q9x{Er z{L#uu=b$83cn4@QJnYP%1d`<Uq#N@<3qhf)rVsDz)+*r=~D5pdgvrOqX~QDY{SJ zTa)i_75&2lnl8oJWT+%Ho-4^lASWg4zA_}>iUcf%bO7(~1}L%5uedMP7M^DLT-otM zPBKyPz*@?|FM(C&(MR6h_S%r>X|a>N+mSuJ?QP!HOC|Pat!}3LLM(+R_1jQOggbw| zo~|7Ee%-B}BdQwLPHpVw`G3+ps{4%nhxLFBJ5XDNc+!)+$XWUcAS?Q05Bszs&A-Wd zuVWh)6R1IbS-2D$=jt!-XIzk+|86z^y)(f|$~8WPjc$$`K`Pk3h`RRaU-3;zIre)ITkv z4Z?4Y(?&(t7dQPQ2>@IVkT1?4c=d28m5kL0Hv9E2G*By=d}@@oc+m^y zGEMixiQox8gZ{6!^iL4~HH$k~eixe|06)~|fVob`{}%wio`4jB)eX{QXDzjBUB1qz zB!pC+LxaRiJ4o`Zwlw_+bJj>xlM69W`20hhNebEOksT~~>BVaEJ-OPNiQ7*E>&z2r z$G2%N)3)=!R02|_)ESqtUFGzK&!v@eFy!#Wb*&rcJ73FnJO`ne%Az6PXsHr*i3eBb z!mZEQsxo>089p#UGX_a4_kJ!lNo=namw?tF#fN3vb%s& z$arcD@YTCuf4d()NG-5$nJj3J@mhlt#dwhTSTRqSnHBC1Xg=jLLN=xCNG&1wbB zVi6rdpB@R3R71ocRy2=DTjxHZ&ND(FXpL9e6~Hp zSZ3MuH=+q(g#!<1uj1ydwk;mF1r80PDTnQ1xMNKgDE13w@aIY`eCf|^iaejOL5!|{ri2=3R6QPw|5 zugQ%CBMCZ&CQYp49fMC4kK~uud9piyx-ErgdT}!m=Ml{jCnbiQ7=s-cId?0OEvJd9 zn8so4zIt}P{muqxB#Ws>;HG}?2_h<3zN#215p;dhRiYzvVo)bV{(^sRN_hH>hteL2 z-F@D%a|WL&=o1*kfja5Xn7?211ZXt=U4_9g^O#>~}En{NR&u_556`>+$T#r8OLG`8J&y|2Sz`WY~Z5$~Mm zx}NR)ZRV}6{<8OJR*LkrkkQg0sS5U=#DEriPq(O`%Cp}SkS}!JR>03OG$y-0{2={Q zlEE2oAvw6!doU~=BaqMg&WBLyxUnLe6Kkl(f@n+rmp*k%z@ck z#6zcEKfe21;oduY;0sB2i{-)Wc@?=2hau*V#3vdEG5$jSj-IDivfpVcjaZQSgy(b-M7bpXew<5 z0O^Sf6Ww<>%R@qKAvj|_$|;A^(KSh3JA^eULs^1-?tNf$ZE@*YQ?{Yfl%7JBs*b$Vq)zzNex zj=4}}yJBa!j4t(QZs~h=x6Ft<+uZF>HiU1yc#?6S6#Ig{kqqTMUfM@jlcaB=>lz(r zoP5ZQ(m}$W5L2#c(a?9@k-BNthxDhKRn`2DrToa{sDdne9N>P1J6H*^Z$Zc;m=yCz z$pa$9>t;@;^T+HF0LxSj@qu(kn0SpPj?>!ktwf|_bk3-=u7!W(LbhiiL?n#9S+JZ^ z;dG^779uRVA)Lo>#!lYGO7d)Xn%V=!40?U)z{Cej!6`Sk(k%rC&P(eC3bh+v;FNmQ z%9s%A$STm+k-*ske0LJFu#l?!3~6bmG*7**^WYotY1m~3N;YM5yB9cLFxNHx*~*SW z%HJpHlCHWkvPj~0(}9D>xe0h}2LGr2>T@ssE(t`A_CNx)OgPIVUT1Qd;!XeTnh6cF&Tv89 z=R1knsQyR~%5P(yU+RG3Ka zyva&9YL9)mpIUL8r=PWuZS)kme@(`tPZ}U1D^FUX7HRvO@D}nxi5>{>rbjp?5O-%t zvEMLyE0I^Zb7-z_xRqI$dGs@i%!mX@Qp8Iyow#u$mOrVE*M!DsBs45d;k!90^A=}f zdm^)XCg5eaGF}l>9^)+<*!0F_^V5}atnbp%+Rc?E{Acl`|4|TdwY!09I11)VongQQf=Zt6w#;M4o{G79snpz2>U+|9)`*5)_|ICv^IkN6{l(Yr12{ahH%=HxKo;7{ z5K6r3fjIM-pPlhG>_D4q8iEjEAq*cPEWC3OjAqIX24eA>7jmWAtfdtmzm)d#v#r0F zioAGEF(fHDxbSA+_hI}-HqMxy|11Q!vPc<~q{~x>FL8f6RR2D7);5P{g;PY#bzSFG zp1gGqW*Sa~x1TI4J#RQ8Qnnp>B_*b{xO4A%#D<$=G=ZAjY0e0S)3(Q!msmJW*$X$x zUE-_RY#w@3u&szs4(j}$76ik-bXEJ(I2~@xqXNIp?+}=I%Si&9ww8ZWTsYUs?ZaZx z+#_}H9)O5)s5hm{k8wS!4rch%cK_72kqP(@F&oekw>17Y9%PSlfus%j>wnu44~!yP zc|$evPpVHpoa^$23IEBy0?6b5(311?KPtz10{;na&VL7XX$1xo+GYR7UjjJx?X>)p zMAXB0_*Nde4{=a}+19}UEz9#7! z3@}yyY|dCL1Pm#NAa=ReW>dWD2hF@zfzG&tsStSmvEUsHYBqg;hbfQa6|g7t_+h?R zCnvP`jT5fUab*~W+1s0q=lpx~y&d>LRy8ZJ*oC{)VZLD+zonW^DGRk8e?&8|wh!FrrI!p{CXe7aIbPHu=8|XqL zqsH%{a!bmf#l_6^f?ebhNVlHFP=9pu#X-(PLr**W>ldRLI=LY8_%$uE%r2@!rCs|V zn6B)pg&7_!Z_dVYxY1kx21tEiYBQFHQ!B*yiK+y+GmNDa^ z7W2xEI+`a^;FPRC_)0Nmmq=L!ByVG}Oa}l#pB?grU+ayM#X0*vwY?^Ug7zIXDlP-( z!OV02m(rBD9$tNKh3=_8&*@wWOwf^@K`@fNFs{T{5=|DL0 zKRrAQArFsWBi4lEh|vo#dHb$)CFl4Q9AotOmNhbdtVXViREeF5#!kDNK&c-PsbwQN zPLKs2nenlmNSq?Jzw;z6(73f5|6`fuTkS$)!c!h%4AMd6s!f%~mzIGt4)U~L%(7_& z#o5SIgqXV(FqccPn$>9oX5+yjQ6<+KYGxWs=Yk`W4`lsp!A2a+y}dP ze?NKYtfFEnTl=%Rq`;PJ=ojWGqsZ$aQscX((yY}E@a_NycgKshugec+NCD7^6abw( zR{`F8Em-u~*#ZC?OOr}uB?0vGpD?E;{*>6 z7b>|a8t))G#C2Cc;-%~6h{L9SevN>pOPmxkjsK zg|nnfk^iH$$7Q!r$E9CIDow^%RAb>>m%fs6ve7p6a!uDtTYr}0CZ3ccHur@emqT$o zz_NX9ads~@O-9mOSq~^#honzeoC{Wb&X70KcgJ9)>w!5RH<_9G`l~aSLB3g_FT{JW z^A(1gdfi_j6~)S6?dAT>d+6#{g^1$Rce0G3HW!t4<|CC8#jf~ObcX?ZKHOLjnF!Cu z(A(`Vn*A%KB17@*OkxsOI;J$M>cUJE$lq{^UAc9>9I_NFZ#=}N#4K@qEpW>HJ3!eH z0O~!}#q1VO(>2wypVyW-ia7^YuX1;EQ)FzE1_)8<&W^~KFQ9{fPu~lJuB;#L)z$+# zxP>1L@7O#}ojpcO=3&@4Vh) zzC051;*86R*9NSulK!WdFTpFcK+%z!@9no42|v|*{mEbThj!iqnhJV+JlU92T%IRX zh&TrD9zOS9rZ#ceXQHWru6}f7-axbM)O`l__Qe z*%<&X3YWMR|9Uec;iV*D&kKUY3U^*>v^Gd8Ry?OYB~Mex^WYOO^uP*_IIXUQFB~1( zPs!p(IC7{K+i+DthDUaj)th2Z`?#od+*jt#d;oZ}ijHH)ycAT5!$yFEr%$$& zx0P+h@?s>|j;XH}2msLixUZ$63us%+Auwl%L+p`kb??k%A zANn3((Y5y6)xO7)MyLPW{`)C|gv=3!j)#wlRs2|PSf8=eH!&pwWEZQK$e2zarEiAj zm(AsEA&))~AG7K!LnuJ%VjV>rWo9DQ8^NH#z;wOi57$06Cr=X``dLDp(|e26G=&nt z?*r@gZ?Ro*;yAGc;Ho5yb_VJRcVRQ8?#m7*ziA5Yve7E6Za$Ry z(%senQ(-p?N@f6HiVZ}|Cf`}J%I&pMCbPNgyF#w2GetPuDjX{}6kYpE+E4uhTV=tf z2bI5E?2$51nu_{TxS+dyq}Vq+T0?{ZC` z8MhomWE}t5!F(SDsvFF~VYz}tV33$e%r1nsFaxbHQF*@%w^Il6V1;canXPqO_Q zh9+{w$AV|V<;LePIj#h7jvkDx{8bH=XQ%ZC-TN=r*8d~BZu+LUw?BI#A2y8b^5AU+ zJOA0Nd|H;7wgX5GmG3g7y5z@Lvacs}w$Q*!OZ574Kj^i#y+&G$;` z?g<3hP$*DKGEqO=?Q_aiL2IeoWev4V}tDAGLv{WfdEoe@y$=KzQ36HA%p7F#ZXZ${n@HEXdndz z>N+~iS2)s=Q@e8aE8^hmVQrZ9ZTn0}$VXE+pe7^Ij0<;>Z@=-XR(uSvX#{>A=9hj* zU(JYSN2$CP8J6J3i<2obAr#|r&rERrzzAsYq@k4zXDXK^nvsNTz%`GlaVSx5zWP#H z)9sbbqT+%`?=`*hLuFLF?w`u2cuF6({J0#vC}qz^y;yX~aF7FK2dF^2$1-ON)>qo2 zZ3O8J-qe1B6rsZN-()};c4I&~<-4^5^=!pMEdW1;L8nEVV#P^&73q?qLPh5+zTb|9 zs!#gf`k&9_@w@hw@yPMo4_RV!I2dhZYtdow4NG+U*uluy0TPZ`!So^aRRg|zfdfRC zr#72uih7Qt(IyOtL&a4O_Zd)kvnS@!)~!_jgkQZgl)>+PS@@u!In*Xt>rG$qDH=-3 zbb~v^&Sy2wV&fP;KAU`eAvGB|<()s0M@4>6J}FAt zj9=BVbiGyUB~y}DMF=$*Qj_|}<3^9#8W-j0KbHwY&aXxK%1(3mC)F?AJ7c1cbMVv( zTxkFV7G7^^R|SyeX^ha8E-Y+N25C#chcgfatIyTbGtu@en*a~@PshmQMj&ytZxC(| zkZk&OAHLY#KP1ARxA^HtcHc|=4+NI=y{1FRkg@(v+5eD*TK|^-tezy>^WvrT0v25o zZ#tthWfir$+l%hr^q}xsofkj1CuPG}dciW6=7;~rBo&*Nz(F5T%5s)wPxmybv^!Qn zFt<2d=D*6KvdQ0g;oi}k;w3-Bkzk?NPRuW49+P-Mf#@9KVJGt&P6{hJD9vWDY5%k& zfUC5?=arHcpMkq<@dJp3r=z)Y0{hLo^rDd*vV&000zHFn4OLAsPa%#A)*v~5n>Kdy``3i%#%i+(HNTzva*_}+5Pi%Hp2l`M%E7fBAaodVS zUqwNnEHud_Tm4rM;#+JcI=o1#hZlDl0#|y+${5s-^hUmcO1|C?XTYdJ_AxJ&8Km=4 z2h~HAb8RwtoPCRI6Ag@txBIPrrh_D+-p9kHtw*kMTNu36mn^OOgImx8$m}eC8O`^( z;L^riTjJSf0pyE8{odOuA>+w)Cap#h=+>sD`cp+hxKsI1>aV*SmmLShq}}oVst)DS zm~v!)ErNTO@8JGG(jR&CV#S>RAlLg)x#~gGjc$glK4RO<3TP$CeAy$)Z6$pqc5{xQ9_Uu~*M>OZiwzUd{7j|6 za%}4pz}1e}(!79Wkj53cxt&sfalq<>Gmm_P7{TYVhQr+qaTQ?e9YEV{(mw6pT<{l) z;*w}ZZkyX7x`)zQh|8f4H$Ppp^KGA6jE`7>eC$;n{P=EZa|1InNHK9ybI#vCk9koC zNJ4$->i8eHs~=*ggLen@O>C~6d2-YHwM7GTGgBpYjq*uHk)BNRu&;H}3+;K)IpFSA zZfrAv)l@clx$Z*@<@F2Xc=YE#eGnRtrVw|Ok-pT8t-rcRl@5~VY0j-r8bqR39^Kh{ zkOgQncJ)${EFDC`WN_(vQ;Y&EN&g6SxiE zdh?G!SZ?+qQo_8QKke}@*3KnXrE#`>V6^9FEbo&eT~L2}@uyByD2J|n8&JthvyR)p zQQB+qc0K~@cP8ys_z#J6HRc+I==0@!Q+(;I_b$8=>gESGTM!0oz}-D%fylcJ;;5IX z$>O&1(Oe7SUJ`xC(D(pHwBBT~|Mzl2ECl%L(N1nbHiz$(j+GLs0FZsMzDe{}u z{ku5+V&$t@TY*1Rq;8fR>#>4$zjh>2MOa%z*g4~yCTqozM_Y@Js5_A)%@z&fE|R%i6I}I=FJ@_)YvxGXypS zJNGdcm#snD``o|bcVn2A!R5lgVV8FZ=^pGBc24LNIIm>FSJ#u4xmSOu&~8!FvZdl_ zm#vnrTdf+^QK(}#Ir31?4}Osr!&m3CHznhhZJMkZp=){`?VG;m-k@b`i#pK39C`;> z4CAGhE}sl1U&Mzx)wtG{ArI|qArpa(PM8kRjTCEsHal1tk6H`)8$UeI5ufrzpbY+- zA0C6NGGidC5APKMmXbS0=`~H}j5q1=;evzx3kM3&p-=+cpuL zD^~)CC!S~@QW1VR2)tUdPGYUR?f{|8G=pd(-VSgdI^Jb>bIC4#I&;CLpiTbOU7qxv z$@xIs^3IWWC7Y5;cqYDCOO-Dm)hNw&5V78;XiiEPH{~Q`0s@a3v!afMi zabsHaDQRA~dBzbBR)mlyTzucJUXYI@W4 zw1Mk24hUbRvCTUr=||};!EhtgD~5wuWU$JVb@F1Ddc4RK3pOd&)vze<~H3<7a&Bgi*G_?JNrsliTkEM0gmpO;6Z| z^Y9r%Fre>hq&|G-{1oxd3l5oHId|vMnQGwfONILKJfqG$naV12s0!EQBdcyy{ZK}2 zQ{Fm&!Q$LQ~FRwba-f$E~r{IXYO< zkMyEAT#4;O9!~!-mA=|;2OzrKcdlwp>K{MX$MGplR9S8MC&Kyi{p$=HnX$`Q^8Hhy zcC-$Kk2HWPt>X2^E^JN*#X1E)FY^Boj>=!L+4-7|F_(*;uN^6BmTK=grqk_2owNsT zLH>)iU3vsbrbsAo6ATk$tHphj%dsfK>Dh7SI1Qh|SC4MAip)Jyqh{RiM}6$}-%iwz z2`R7M5>;o^?3O3&=3*NG(l6=|Z0%)`E*5>|ewRpfH}zQy8e|nWJ11E!v{-Xq7T^#T z$m}-+{=vy@%)~Mb3AF}p|%8NB)nlF32tWTkwwcX&6f1xfyPjfAe zjpyur#N}j5`RIoLAz^(ASTz1HA;>0)8#8GMt5@Mk1N^Knfzzg0?tP?2v6=QOzG1ow zeMWE%(@|R&tURD0+Cho9Y<+5MO=KzfiBc@}l+5Pl7&@%>2CwQAz6dkTe9a_#ckpm* zVjO5+$E0xRsDj07bxx9BM4azOcGFlfN!H>-oQf4ep99$kHfM%{K%sOO$H6X>A8DN@ zT;A9xYUf><{a%Uh9ZuHKTDWGZB>o_bLkC$Kg#Q)bw%LR4(kh5((k0DcY zs{1BN?=|`0{-0(9(-U^-1>eH-auqw}?+e0ishBX!Hao62I#Wg;#2D81LN7a% zivms}1SS`3E#BLB{ffwnkAsqB^g?f%W|z}LN@xj!0zW*f0qDn2$!r~JZ5J#9D#YFo zI!WJmtIsb_Y{qjwhGduJFbKi|rHQN09YW(P_u0H`v6jO{bhqZ8Hz^9g3csA-CnQ4C ztEs8rQN*IstIsFWSVK|JiuW*v3 zi>FamPNi}`{=X#|6)JSmbIPIYyEPEUH31cxRg$egWLZZvqCwvm<^6GitnYq>=pPo> zcdm}};+=R?9`7B76`l^yy*r#)`+C1tVp&Q=Tn}=s>%S@yD?%HQk=Bvb2gm>d)BZGR zZ!TA*q`G#Qv#9!Iq_R9}tGK@wF>?K)9n4tJujNo_SQsmUw&+Gf71R#boGgked8pTi zm&m$!q#b?;wo2{G{DfwehI|a15;+|Mv}TJ;QO&e{>d341H19muz)^VR4C~#~!Yq}~ z_FidFuWd?scb(gGs`(d2bz*i37~&!7-YQ!WyiB0fCGV73ZDpJ{caB1ZxF&M_MRGj5 z%fQ=9|Ew7m@Pbl;-y-e4l>~VNKYyB3vqAI${x4T-CHgs)&(JmF&K0d;$&jZ(!23Y4 zd%5qWm}5GScCROq68_af=Ma}s*lB$!wpm1G=T!qncp`U_ntqx~FoiDKM22x(Ej^CD zagc>U*-1JYBo0hYR%tXqL+TH4SWotBA;0dI_E(xJvB~qk+1yvXH>V`6IQ$i)9I1_= zxoft!B+Rm*oa2s-w@l6_Kv(&Z=G%Lk$|_jWFAmW1Tdn@28NXY)o}TeG>Rf$#SXh@? z&cPpIQRgjE-0?qF2~OKR&rWS>&iYyv^}`jLvJ?I9(|t_Njy*Z1DGyyzEov)I+tgE) zKjUU=vaPD-l=hEg2wc~X<+o5Nn2UQ^-uP{k&&nB8knYoqR7b$7f$I%_nquoc1i!F9 zI!CwuuPwr^4d_f*1WK;`e~L~2txoxWnf8^BkZ#pZ1KF4(=3u(^|B9>>xVQUNh+a1| z1)wdUJ@wPMP6psx%HAhbV) ze9R1gQz4{#_U90NkqdYE$iI+X=%ceF^5VP;dKO;&`+o_O-%p+FKOetaKhEqNWz*^0 zViUKM8gU!b%HcQ~AvlYkT%Xmn-)o@OXy9%+PxXbQz~hzl~3ah|Fs|nC^SC4uO?QU%|yk zb#~mpN9sh}dRY3fO{XAyF9N>jcisgBR>Q#<=74VZIUV^8vDvi{I0~9ay8T2t%G{H* zYcIs9VQwf-I@GtPRitm-KJxDjZ{+TkQ|0cZkJ&70Aa>s5p`UY>uTRUBZw?i5R?ql# zqG9N9Z}HB`Z_=I5=E>m8d}Q(H)15AD>T=G#Fy7gH__nJYd`9%H5&ehGve`JKoz9LO zW-1=_dVK#BG#J&J(umEk)k|M}7L>oNp6r*sHL}@XQoVIn?pf{Ak-*#T3ftC$ZO83Y&Q5c} z_Z-}29jaGcSDf3;w7G`~1*Tc#99s~m2+aCjy8X{3F0CcFNeu&8$>Jey-%ir$_F;9g zn(rQ8&_HZyz}Lv&Yi-<})VXiFu-T7Rs!iN}Z#ueH?x5FsbR!P4J%;{@p;|ozyKY-z zw&Rgy@klw$91JgW(t5}w-F0-kP^*Vl|v=H(AJ!aKIJa%nL+@<4u{Q4r9 z=9)4myhV|doIhgETW8lB47(n@0zAX+M;eFWwQM6nv(ge8FqPFyx7;RgMUtSuhXA=g z1bTs}dQR3d$#wC|^`4BnSGx1JTzhVf*nfQj!kIW4x&*Dmdv6wp-b_a<+vOoAVQ4+1 z&$JGpch1}@+>kY5+B|*~r|R#QqSjksyEO#pwoX*r(s9`4UD)O!)EPjJ5sR(Wi>mM~ zRqVblzv@SV^AAG!-B7#Cu)R;Ws@H;wuv6^@H&6E4#5wHUs+<94dV>QoJC*SDZeYI$ zu*+jRMsx?Qd6@7O{*5`E^<5iIb(boq{p3#G9isS+dSAhrjX3A&_p@$u%5rY&Wp`(} zR-QEpprMcz=e+E=0e}Ti0JTKVSmu_`XO54OKAUFK*>gthIENqq_!z!4;C9v8^_-PKD^ES)!#=?`3^u!3 zNQ>N)gKy5p?!4$c@PHowzH|MYj@w9y(89zlfnZkUB+bfh5A2{P3p}rhobwQvl1{J- z`{{q8aJ(wG|@^{V(xAA*>;EO zjaX>A!8dNfH=xPKtaD=4?ed&Q05II#u=1P7xemb{be_4TQ_tJTWx*B4PAEixiAsj8 zorkS4>8vv~>ue^GRZU;Dtif_RXvssh*bQj;%G2fZH&s7+AH3o`hEy9YMrhY$f zfp}wef()?>ChjXI*)!zVF*LMsX_T6#zjq}gb^{Q+nlbM2Zm@L*Sa%&ZJMU6aPX{=p zK+GP)Kwy)x-JmhvmwBrBbJ*EbIva4UXAP|cuvt-^)sWokZ+ipiRXH>gaZnG()H7q2 z{VjLVO>SszAtX9a)d;ruUXFJYdtVK{e=TIxiKE zA*;raOTTX0)WHFjnRD&$Whar7${i4c0FOG%jF9e<#TB;R2>Wjg=Y}@rN{PS z$MyurHUyhN$il=h-dXZE=Y#pQCJUwu3b91X@dtzP2L7K!mLaQNPI#KK9H*%+G+K2;r8p1I?BZd+N{a}TpY9`s4hjjQb5bM8?vSqRL* zO35oD7ia>(YWgBvA=8QxdEto0V>3VZGvFG}778sC?nbGM6M^(2fuG|5{?Q5AXg;?p z$5$|DKycWBV-(}}mA1#2G(Cer<}w#!W3l)AmeAa*Jh$~!PN#8c3zt?nI#teP9ggat zLk$L0Pm9Mt9p{NZ@JheEzUE7Z{=!WlxNajf$xXNARVRl)W$Mks4r&u^-Z+3=MR)LB z9KIQ$)Ts9Z;E$ocom8y&|(BhVRX9Sk_qRtnscS7oU#2Jw%pPQBlxxv2JDzk zQBEhKAOg2l&v?`na~=Y{4+TH)t&uwGuOh$>MYZ_~Z6<;}7YiocVc4n(8sVym3wikj zYAa3y74pwoJO-+_i=kcRw+JO}K!J7aUNFVs4vNiyyrSb#pW^kysVpnkQaN4cz+t!y z5LWRf+4@Q>u-TSzq$#|oxy$3yrrR#!59fAN{XCAoQeg90K`E`zYH+W??xTI?7 z$!x5{=u_AIxe%8r204%^3RmDpUxj0nlD>-1b8I);+>-j4q#BAHoa_fDBpKph$&xOm z;u{Dkg=4Eex1f--3n(vRad!RcDE4CTNg&iwfdcwFpAIT0?*JaRXP|>@AKS}=?^MAz zBj_+wRHN%UoUWgqM7Zn?=-6*�XOcRHfGjI><_XI)0m(^=0O2~AG0Rqw9nxvZDT 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𝔩jO)wo%y*{puZr;U0#cDRe+dwR{fcaaCwol-ZTCzWS)o^gBP z3$;BnAa*Uf=wBc)r59*+!c4pJ~ZP<5&rAf=u>3wpAEkB<37?6>>r zIPn6e_fDmzQ|?3pY%f!wkpVm`9YHb7cn%1! zB5llr9Xmt;VJb~j22AZtN>Q1LgNQ7U2jy$!!fX3R@fS@Ec$F} z+qT|z&)ikaLM$-h{cPCcos6+5NCj13MwHa%rVET;pj{6FKzWyoCawF{vzFK0^bFU% z0lj4{iSko{t9u!h`6-yEwHWD5^=E_C`9)5A%#t0b{X-?nZ`)Sz-LPX&A!Da5H32fH zMcfkcF55coiW%L$$F6bW!bEDT?^c*&@#u^t+Q@_D*LiP%ooYs@d;JEU1Y3euY!)IF z>MKR;<1j1EhF-g5z`;h#ksHAl-QC-3a00zR9a4pekjo2kQ+0AQQEu3k4z6gDR#wq zUl?bEkSskyFe9mMj3ev9L`9P5kVTTPOJZ^aww?T$6oK}2GG3DUxAiLfaZ@?P) zES^lohWi?xKM~&a7;BvbR=kCRjQQfy{m+d)BQ5(%e5ojE94J7;M%hk{6Y@g{NFDDP zBQB=7bv1dmqu{yr2u?gU1^s9+Bm8806L@Q))>hJahh|Z>+4FABzgJh)fa-E7V@Al~%^o(XMY}@0-=xf9Xz5f=rnN&%K7liam47wLmD1kIA!?x9q_+Nw z?|A!li5Kr*zo0gOMM}}{6;zI{u;uJhUED&7k%* zEDml=Ne?~2m6Qp%V&KD-8L<4hAHf6i0;meByt%I#Mh=4XS0~4_cVb*bw9^6U3hw{A z^0v^Lf{y+Hw*BcO1i-}9vJXKg*jiEp$3rTn2exAWxxxzP@PDYV`ZnORbrBeNDe;v? zbYF+P5{NaXHDZSNMsKZp<*l{%v!U%KtAsyXdT|ta#HSm+^JCL3P!!?0-4p1{qr47$ zK&Ol{cVg>o42>qJkKdnpL^W#8UkNJ86Y@x{VZ7Fx`j&g6*k!h$8a%$+5@S{ zV1^aD*;96Nk>TYv&3FNSZ6iuz6&@b;!p5sH-c6d(p|W8V-ialrOIqYbvR(Qqz67oV z)8j<}i|3lnq=O>f3B8(y_k#A6{Yb*l_&91&W13BUio4F+U2pcc>AHP2@=sB2l5)_$ z#GTRYw)8#@G=lk$4cCWq7EX!(tr4v26@b~gV{r$YMO=9K4O+_BYd3@+#^5%-fF&H< z=#U>sxy!H zWxoQ;3;kt8{w8JAZ@KYi><3nsPx3@>qnfKyKt;2A5TO7J~h81R*RMH$mhcZaJC*N8VY#dL)g!HWUB zpg8nbVo`B!ORP;>*JU!;R=4oIFl$YkFa*-xjzCqI^2tT06YRdF5xDw1AcO33(C2Jo`CQOBlEp|yJlh|Yw9QQJD${ z*6#_f@a3tc_AT>eVEva0E7`hzPisXfnmL=Db_YGr?wn&E8=e=%I9$e zPgJYoXJM>nr_aP7kWu+5Mxu`*u7Qu#jLF{15eZeMzAv^AYX z2cD+q%cH_f1&OOk7^y}#|M&8$|JLsMA$#Cw6dB7d$2@E+LVDu>O=lq`*qei2aA`SU zTKT=D0Ta+dG}ud|#E9pMao-cyo{|>tD@fH0{fWv!hs;rsFw$^Sa79R(Oa1_KgUbwq zaCf7k=&8x*oHXk8jdD4xRmipQO60gN_-T3J%RD@e_oK;!6fN?LL;*fS;n;&gPz`+-h{4D7l#?=p~b{F`XmxZ}SBIhrPEB%5rV{ zwpEbslFyAa?nXgSE~GmIDWyw5R7w&3j+0Q=eXsjjzTf+N z^Sl={f5V<9s9Iq-$C_WU^6g zD+2RzYG;z}I!jqCJwz60SZZ_ekD6R@)mhVW`T?%7O^>qHXGb}qnRm$ie0axXqKo{B zHF>uVBiquNDVL>ec7Ex>Vgh1b+pZ4gf~i@s!2aZ9eeB&d!C0!Ce!I>t?(kOZ(2w~d~}+eFOzHjDZ= zlb1KNt?1`?!{2`f-pz-;a%ue!R9NcjQCZ=8ISp!GFI)ZMMa@^Pz3R`L1UDc7^?4UD z*^XdcDf`F8n!nglTiZ3d_bhYnKD{%-iHt#|H$!UDvSm0v9ae7&H;%pIzMZUpxM)|a*7eWXmRSmfO{k31QG*(z zc$BlvwzgyUD2E*sY1uKIdA2gIB{)XENm=lFb$$7zSjW;Fq0@g2iu&+{BUYvQ21(pl zQS3!<86J=G^D?~XC@;i{I{^4iBq9 zd%A;eg+Vh0GVXBhhBfmoR;#J}9YqxD)9OOChtzzvre45`4{t7u;w!^;fwAr&ye{Kn zVRxc;w2P+^D|!g#H@WSW+e0@rJ)=eFg& zbqy(pwJz+bmE8R#6B7x?pst6W2Uy%8EoN?lpeysoHJ707B1~^&cf(E3=B6(=gv>#gEIu|CoJ! zFx#j>J#nf=-vRyizOLl6Bt>pME`=`Wdi-)_`&9~$(3767b;4XM)?~`VG$%m9wXq=m z_RhDxb=v=w2DK?pyl(2x5ceg~DYo*77x?oODy!JgpNbKh*~cOsBmRYwm)A<`{=BURI;c zNwx;g`{wQvnDn?XG;AJ8%8ovAyE>ba-Tncx}D;#0CsM~OLY?bIb9#%_J0rXSzg zvL8E&5vFkx-#9%SRT@i4LR=>*Lu=|6BoQwf9Nl2iTuOzvWkr0_vqj_|o0YT@_tvV# zD(7|8x?lBpg^J*>!^nI}l55m(E>S9r@rq8xD@Q_(>vxvQHx|6OAdU-FhgtxvZ(ff5`?xQQ!?JBKwTaKiA~0#f%t0(h&o#aA^p1%pQbqe@ zJzJ&QbGoxx9|U&_a`Wosw{aQYG2PT!ymuIkHSZJPpx_@f$<-!#5!2$StoKMEbX*e5 z=FC;HdZC%dL2sP?B)Y!9o+lsqKwv=Width<9Q~KB6S{A}>)Wr*QMBPVTBG7?PPc2; z2T})Ya7-`YvopP*;_ygIzHUFbBx|TKQKRd;GX5!j+n&~PiuZQlukoJapsO&?dIeb* z))e;*EwbgegwzP1H*Vos67ylZzi(e<03|-OuC#YNv_K7fi@Lez_xPaARTtM*nc9qf zEIIz;vs&hxZTU5kYaB|+ovQA%MMbn<`@?=r_h$8rzDuX`k#$Q2!BIN#r2K1zbb64^~7dhN{ttF5V&DuH#k75SFF`H~y0xGh+;TX6* zj3mC%+ECz+EFB2|x43kr2wT1I>!BKckC`n$>MFbobmL+FiF0~#v?3&Zh-iU7!r35d zt*TZ~o%%!|;RNH9s350JOWXHZZI&IIhyxBvs11Rz_v=ws@R9AZ`!}g6(Es^MYRa}N z5kB?e^nUzC2QO+=BH~v4|L~jsYNYrde$)T$n5qBaH~ly8o0dS;n>OsxThJ8();*cj zDyc213`o4kA9)Pl%1v>L6>r9TG=&4zkk#{T#;imIPj0P1S?Fiz=g6x zZ-V)cMq4p(>y=$pI41DI(5UE=RkI+e2-K9{Td*JOe)s);cfPKc&PjviXJUw$L9dAT zW<7D69J1^I_F3Ha)U!ufv8lz&P!q@(<}Sg_T%bFYzK~kJs|A>h5qWJXR#_H6dbxFB zpQJA2vx6{upV#i8Bl*kP$!}~z^`Pq?z;_|^lf&@NcG`JkXN3e*w$OE47AyvBjc!`& z6G!8ifj}`BS<=lBbzv0G-m_P5kCD2;N95^;BR=yET~?S5*sbOap{h`!r(C#W{Q@kB8qGJqxQ9=;!f5el3luw;A$ET?t%hW;s*5%3E`V!=bOE`fVzXe=_z=Bwj=-SsjNeYz?6 zV9rxs6>%B>cF||L)pt@=0sMVj>?$=PW^XB$`4RIV9Xf(2Y#SYwNN6bYDO33EDC75Q z@hdqhT*OWTMw;qyjMd|_n4^85dt>vndsCn`ZDsW8=Q74l;$N0Ai{`WE`KY=*Ehrx) z1H76eMDRDz0)^MDqtnewk=1P(^h}&1{T>F8%})oQo77z*0}KEde?61R=h}j+T2wsG zkXa-;HWNLw#HPeU(?OSg!qz}2D{MibiQJMhyjC?UNp_0i(zZQ0R8zQdB?@)?tyJJe z@_00U0}bQCQ@ZVec8@S{904!fRGIo6g#f)~;-Qt>j7{(q^}lmeVm~TMdo%g z&Rn#c$?dZtL2MucV8`jxa81q@4nzP*-j;06ZUffW_ejZm$koDlMY{RX_{5fmo=meI z7e?z1Ew8c9L@=SXV@EFLI&(gHaXBBKgXd+IS2FG1HkAdA_wcG=++Un}d}Fa`QfAj@ zpXC~hB0VPfT_Q5JpWu`O&LafV90SH^)8c|qLh8#$3p@9M7NH^0xLv8vDROw6OnJ9} zRkry$ir`ID)O*M-wiI7ZPk^|@HO|bwp@mjbRBUeNVt(XVcio!6At7^7l)%{+5^ZjN>DMx~$R19P4ss=B|LQ+$+;aHKrYCc~ zXWb#Dw>!FW$HK95{q4fU!0fvKs8nO8ef|(LQ?+D0;753~w@AIz1nH<|1>W2xrC;*c z{9q?y5$aA_4ma{lfy;o^>7k+$&7BKbFU#h0W=_v>_c&+ne1B+;^LVU7cjo>D#zWR} zER40$yj1v3Ba?4=46t9ICW1}$1b_Sx=oMFl_}FF7op9i(sZ+1boq}YG^^nN)H&bc2 zB)$mN;g>4Z`!uJB=N8{2)0>6fA^35;572mO>+iwKj=(v0`E@}#Q4rq9SRK1tLyOy- ze{eIN<)ekZ?Mf8~dRGs$*GgRE1QaE(i;Co+<@q2zkoA7H0B`}hvRb*flH?ZS5Fdp;J3K4>h ziyGGjg`cYy++GjEtg)a}g;s;McE5jLws=_N4klxrx7p8_O$n4;tji8nS;DzLlTbG; z7L@KuJloKToqjv7AcS)1%UzT%YqSyb`AKMBZ(&*mSizZVF(4j!49d!di~kLULx)YY#b z6u=BV5x$IW^c%K`F8l=Blyi!0+DGVCpQNSxU848xT%sp^wG?uN5VxMXLN3Y*+R|o+ zEnEKA4!tVofQbcL{XS6paq(9UjrF2iL`HokXux`DS*V?|+an{#mwqvMsQ!Y$ARF$K zPn9nJ=)PSnj&z!0oxWHNFm^%{9+f&M*7R47HZiHkDiP{k-k3Icfk z#}*CMVwoAWF*;SKcPD!%Uo(izYu~=1k1g?-o2aSbR! z_J$kjT)3U6W{+g4|5@zs{P>ioR0%4UA}P^4M&M4zYh!69!W{BD}VpY%o! zn`b{6t8JoatyZ_+`dGQk(j-F!=!A`G|3U)IA=Z$+aN)-^!c;QdfSr&)j{+ zQE`WmJ4BNRg5b)Xm)CVL|F$o%`z{=bQ{-QM)ZhN^vtGP1{Nq;1Mh1=h?CZm;#+(klx>l>441X1emQxp3`=J0hIpD=43vC zFBWtH+CSVcc%Vt!uk&O-T8Zb-tmWAGV)hYGd7UDhM5g0DCn^bED){VEx!i3K#Hx=o-|aQ5Q(0BY;_7V;9jhx&oK5XXr`>&myfov+5O zPc$Ca^fm~q!4X?OXvqRBr+r$!6YB(+{UZ1K>}s7~gXmLUv;@uA@A8jWuSsxCWkx^| zEozaT$D)}G_?(zUXY<~MUq8(Zlr{oy#BVqA`d2EhZ)%&^5i%0tMb77xu}kBc7rc#4 zW8qw>MhTW=u-{i$uw(`u05v}aE9kRokRGJ}Z@u+lhOriA&wZ7e%9z8-feg{v-+93V ze-FL-R2>~Sr!vaQZxtI%zp9x)EJ?%asVdAFi&vYK zdC%<*epyX`CLQ~ZGr2XByfgrI+O1348>KkZ9^$NX%~j!|N3`iG%C&V`N^fHj z{D;8NclwZ-zjgqmUe^F?w3=nx!CjcixK`Ga`nSb8BAA4-(7*1+^d@6rGaU zjct6uF|fqjT$*uNaT>2hma5FcRpq^x0c^Fg%bya1pAAS%P*U zO`5L#ZcqVt{1Leg8)h{KfQ8906T6U7JrRU_si01=biN5WpK)6>=%&PNpw8_{90AAJ z4e!Y2sSaM<&WN=xjbI{_hG~^r$&SPjN`JP=#!>bVOug%%sOSNAr5yLnb)nb%o9aSq zP2Qe3=A%mk<_-aTPkJ}i@4{#s$I8o0fTO~ZjHHJ>%34`8v*((;Hp=5tq{Q2p^VAaK_Eiy#*erLvk}8dcDOugh?^uYc zI|gwKM54;mQJ+1rp*5vovl7uf2{WoL)!9OL+NOM}Q@EkL`^3ksJ~~)J?0s<|eXEAz zvjVO6e!N_w6!N-wU|>?HFLA>?l3HRl%OZfonyBQs{?mM+5f9#e&NOWSeI`cEDi`#> zeTpfNIR`kgq8I%II0ONloTN?xP6c;+iO(c?u;41NtpItD`@~(|=?;ZI{S}|~1!nFX zGXJf;;%5mUfsf%q+sZL$#6X*i{h*%KPzg?3%=Iz#UG%DYDtqm3IT|1`MbQt;dcjQxbXTmEd zL^>x!0w!@{5FTWwwKf5*0!(ueFkkSxwfsvtP=`B?3+>}C#Tt%2I;#L24Dr4jpf_OZ z=Bup0qF3(s>2GyfX~ zw4^fW*(E9!t;F~p-Qy9qH>2Yduf966uV&PRn*zkrrVMRGC##bRz}d3|IJUwYT{`ib za-3Y~qZg;-S8dK%{O>iAy^>pF@^yKs5F_BX8-)tljz51=dEj(nqhJXfyJ^`7R1_f7 zdN~0}`myA_H9h|%0Z(0$d(HZdAJN{*4o7ZzkGT4E969V zJpI?Hd7srTeo*APnI{n{P(gpsIKuHKYISsy@G@Meu*Bvi&#G^(B^G6 zRKsjTK!o*G=RDLK;5ji38mlhoRKOm#vz3yJPhd`5?N_r9{>4DS=;QN*wJ!b>{)pxN;2#Rn5@w z--DcVh}CG$8UYNpl3Hxn^~h_d^G`9R`EuhA$OIOr=}G@2GURi&;1)|@GNS1?&Hk`? z*o$z}^~b?i{$xUh2H;0T+*6390D82TI#)4-b6%L0p!xcc?%XDgMAevoYJgaOL4<8` z|Bw35e~vK4J$mRmN#=g|!pwbH@5kJ-+^p9e;4EGH9_Ct;+kWhl;11)}D#o4I$Ni_r z(bjtZT8}k_w}_!R`Z`JBhZyVzaxFMigZg8_EShl zTv8rWh7%CqY`aAOh4r0wC@Np7g(3(=u+9@$R_yU~ol5C^4mo<}wIn9@?TW2Yuv&PD z_K6Z!QP3wO7Cv!Ek8bO#6Cs9V6p_jFsbjF3DPV-k{|p)-HGRll4(~&vw=|;#2JIUP z+<`4xC~Y!g-QR5$)C%Mf%$f>Rur%h>0oKPNL6gMM|&gSxEUS|KD1PH&qW% zS6mcu#mB1dle5QOsSkEJ_0dUzh*81y*tZv?=v~YD=WL0#wRoS3y8qjlZBSKK9uh#k zn9zxAY9G%dzLvv3s&uVwjg5>O-3R_$L_G~4T5uA5M%S_Qc6IXiUn6dBU;mQihG~d# zrwEt%gmVLWDJ*3f!zaDQLXT@V&mu>%H+^|(+)&TkjSCvQ2UtN)GIn8jYR~2CyK-~Y z6&{@m>FCDKsUIh)qr*EAPXn#qNc+|h{J<}_)+5~3bC<8yaOQk@oLixY@SkB$UJqjq zpiAg%SvqCwcI3W)M!O~J{syONLH1H0a>;ixIc z0AIi>N*;hiw~lpybOHIg8vwMDbn=YP;{U50K_?rX*-tJsrAv5u58r_X9C9>e1FwJ0 z;%2%{5XarxQxBa>9kq_1@Yuzf2Dk3|PUZNCB z>2b%*yubJd|LCU&!v~7}Dai!{5Kb6zB5Iy~xuCM6Yd}RhR3Lv1SxK0oxgst#)eu4oLAt28-hd5z}2 zLt0lrD0jec$sG$&5-==nI>D~0EBg&C1Ef|!Sl)a}7u`jP$bkC(*VSL=+!aRyO4#L^ zj&-M+r0d44x`jbBTfdT{9{$F!hBPO3&t&nP#UXl4If0N!;q@U29ghv5zdo<@>Y+1V ztzy%JHF3~ic6h5hWpW**5Idq!4oks{-Qn%v)^)?*gGscrc|lC088g)%2{ zsR7i}l(-<^cL(T@lN;+fO)lUGzUoKKZ_D3F@)_YLEzTSfxeB1w0buqYw))1ebFGrP z?U~86nxb4N%LVtE)?gX#cEnY%3C!a6m#)Z>eS-H5>jMmoY{q%~;{1CWZ=?c4U{?NE zec&?&1exkba>fdD{290eni`3dcR;oNA7}#&CMxcR9=p2Pli_%raJ?= zyk!_Ztp$S0uvYA=P@yDVul9a9rTX9O1}Eo)db{_v!7d1`43~1jx`EO)^yU{O&Qd)K zZ-yU}WaIg$PbS0gdTQG;NajDM<|Ciylzgpr-pcy(<8tDP0epIXU>FySjnD$j27NjF zd>sNMy4~L%!G%C}98D@uL^WfbCycp93UVcnaUyQRG$BxbRIS%Wi|;KX`E22XZ~z@P zYb%>*aqECguewEf^PyRZ{j!21R`)lEel>=Zy)ZoFMNE@lyej_~V1k8UNg9`wyqjtf zp?u(@NB30zo$$MX(Pdy9RejfeBAx?d#LNO^oB|b|=heyyRG+t)6XuGQ!XeSmR7}-6 zR!532pr;K8=Ctt}lS|LW?#^7c{w+8kD(s>4_ zxI<%qvunUM*FFiz>+^pK~4SA6*?}V7K)S)n6c>&UPrj`o~cEL@o8^9(7{am8}GhR+?5lb5?S#b znhwR%59=NY??LDR3}Ad?H&LMMP~;rQW6g^}sWJUTn-dzh9-| z(D429$o}ms%_I6=N6Cm{cUi=2+34lDnMoM0s<>$cOkSP^N9}h0r0RGn=Dx@xOq~cb zT&}bwy4{PY{T!ZPzxA)KyiZ?bDJrL+>m1yIHK0>b7}#3&FSM^5EpW)g9O0Tv`eHxpLZu zzUGzV*|4p}h;^62zd1*_FW_-=Rn$HCn%LjHf8E`^lrg0erZn>|J%N03a;*+v8&6|o z5&fW@<&S>b5`_SV#hCPYvuY*(J>Omw)fF;qswAE(@W@Sk-(=e`Y8zhSNmXi#pCqg7 zPm)#Q>TrdX|DLQ`MSmkDZjyRdxIuaMkcN-y8hNRZ*qvmK6681QK_9E_8;BL{ZZQQ% z7AKULe1S9rSD4~K+*n+BOIF!`sr1Kl+O4unLmBnR=s1}mTb1jr&{wJ0`Y?094~B%K zXb(eyI__H4rI#GarxVff(5YoBDz94vVmQ$gvpYGsKVGzz&<*WX6`AO?O}Uf->BF7( z&^i|V>ZVIB4vM${64gBaZR4u8Wi%z}JC>r(tB`Sk_h4M+(+N+>ra`HU6Whi(L8SJKAH^rys?G=m}BRA8`1quBRq2jwZ99m;=Q{&IZ`v@<0_!>@5$b}GHn8bdN zp3~@~@B$%7yiOoX^ZPCe5WJMm_fVl!L<7pHC$Jupu(Oa#(au+)+=j;gm$Ppkbs^(KH z6XK^NrUUAoH@EtkPAqvfISAJkkDL2D6+T9S0*zo2w35mCV+nx!>QY4CT1EJNgeC02RZX2lyGK?Ui|x%39U==( zd$izUO~2pr|oNnE)zw(0U)@ z(rzf#HdYwLNu%4Lt-!Op%^t9ZIU(>|+p4b%yfQ)fYT4+NL{&qzrDcBYjQkaUjb2Q^ zGdd4>J)lQPEBUvU&3h6HGv|>2gZias?<5~XMiDvh+5y0%q9%~M%0eoLXElty2ss?Z z=}h1bjqhsIJ1ew;!|B>UNpdr#)6HdbKB4t-vdUzb{ zD-XfI^PZww$s<;S0Z`)@fT_L)5lyBWFb%df zX}ka>nlqos?D#1sO#^%l%N$yCypQ)Lv;^Eag|%EUxhvNq?K(sypPW#sw$NiOi)H0F zn7kDuYV2l9FD82eclyPi0em=>NFyC^@3=|#@RS>TDc0qUj2IycV>V$?8r|Z$N&Bn~ z(Q0TKn!ZzUyT)yv00p!9*Nzjfim{BsYV0^~&+EkO-&5i{bXESD#=2}vsB>$emhH=y z!ocVJ_PK)+1PRl8AT9?A7YQk(H7hp2(K^Q<+GkMvHn+s+<)z!cQvo1h<*U1AA+44M zVmb`cUy!N=wrGC{Qgxcr3MICq)!KyV?>o6!wmlf^^Qmu8-W3?-6e3jNyeqt(uVTb^ zA)9G>U+F)mv6}r9?6e}nCCjH2$f~)E9=(%sliUK|GT(#e zc!&p7R6~&tIVd2ag@uTzaV3r?SLq64R1JP%=$TVw+cI4-f~If8R>ZQiw#CO*2ZYxfK%YYv zVQoX2hcl0i41KfzPO0ka-Bk7NU7`K50`uI?UPOGMd#K7i!}xFtZ-NJ;wR{hAV|hHc zsIKL3wqt;~>dQ-(CrJWEV-TzAXI86RdS31|(I0Yxe+}?o7eOPwy=|moliIw`vvxyn z7Vt{zzNI{TgYZxER0!mnV|mtz=Zw=yfKb7C=VSe&y?-9^|J$qaYj9d0W_F_All*9J z)E!yyVYqBg!jkuAJXf%WW^-KiH}X0e(|c;~7M~+sy)h7O6h2Tk-ZJzCE_c`2Zf$hJ zU<^a&p5Z2n-NwK1?){Zj)dEfn&m_9a67MHl?R4KiTD?Sl;4SpV7{*$U_+&c(Hib5j zd5sFWh+ zxG}0y&GfwWezz@2hXSj=%)WVcn3=n#yVL#-u-KBM;bJ6hlN87P_pQ?_uyvv(T}sGnMaO)WxvR4_o@~D}DY3$oGWb3c`ALM<#m;pZ z|0^TBR39{L8HwjH%n3RU*6S7TvcT>{d)I*5xd7dfIZH7AH`dm$R14>mtah;WcMv5B z*XMJHX&n%3NL^2UubJ#3$=q5X9I|AIYHBHeVEfx|ti_$i0EM$=2gY(W0b);ee_VIE z$o}-SQ2k80ZE6@dl*ZeVaeiAzmCN|@#AAXjO~Pu;oIx0i9+*kkyX=woahx#rUsLLh zasAb{m$%c0_#NLx!H$e6DZJa4*a?L$ja0z$fkw@AOqO_C2NO~BR;C!!{LPDstj0W2 zQkjjnQXe7EICKk(-iYB-+xekA=Q12OY;$WL3dr*v62ndNU(RvBzb*oL? z)0hlLu)d3b!F-Comhdw160dklH0geNg*bZ<)spN-s>pNZTe=GQNVE)SzDSm`*go$1v>|-ADurXL;fVUr^Mm@X` z^L?F9Sp6bGfL-+OlD+41W1peIiu>cz4M0_XzwhWw+pr z_uIHUcxy^vRBnATpBBdUFj}!Q~p{+!u)81x%yd5tJHuPQb#Y=e72@MaD-)3 z!^zF6e|wUE+1uOdaMh9GZ}&RK*=nAcpU*{m{YP=gEWosPUQFns-fqJsd*k@3^_`Q$ zj9L;mcI6EnrFnKk8KwIL16d&fyu%!qr}20ctQI-6%@uZT2Z?hkotgGxcD)(p=?+!J z^1%lcwz`rdH~%Q_eC8_w7Z2;iad~*0cwka~OPB8QnqQn}ua;8cjz;oG=oRA~qF_@! z=%8k$ADEw!G`pm0uP;2amCs>- z*NAyQ{wT@=e7dVvJN_rRUcqm5gkN&KQg~4dCO~!*czHEtz7hBcPV9PV2b6|OHcZE? zlHgy7QdU_91l!>f`y06`+#E%FRgpqezzR86om}W#K2onaB{^twBkXTyPi{g+|}VMBJT`{=AlOzg})v z_nst(mq6cxt#Dj3s^9klzDi!b1BY+-pYTsbxp=z&E%+ygQW>x71tj<7wLir*P#fgi zHT4;J+NRx;bMAFVdBe`pNu%grn4|3fQT}WOjwps69&RB5km_+(@sp^^Sl{*_E>r_D z>HO^xeB=-i!eVg5u=$?pQ7BA7_bHrl z{&WP%F(Py~%mSJ6I57#r59We}=v*LNU-eQQx^4kg34Zobf+3Mil zQ6yUhH7c|9u2hl2bBnMAyi3h>>UjwaVs4;YDy)Cto)$?H&$cc>EZ?L!3GiKL&RVIEoXR8Lbi zDd-167HXO|wjXsXU^i0-^EBD*;@GP8B_iCM2s3slF`!GV3xYK8x!&*1uT^pM#0f$J z@oF&%g*okTB`Z444F#Qx8Rw1N9w5juS8wDTyhtWAgci)^yMj02l2;(k>p2A$sFXKu z2X1{GpGrrk2>x6C(>?Fp{*@br33}6)3H+A9ZF3_1DG19TgfmH#pHP7A-O;9NaCHnO3e zWtw^=Cvy3-Lrj=)u8PTdCMzC!{CVF4%qQhf1Su3yc`U|U`PoDxJeM{y(+7-}QX4<_36_j0}b!Bil2Qol}|7y2dBEVDI+{JS-lu;3`jx85_Zd#qRzR+8sO6 zY#N-EYqA;V1C0}NzwjsKv=Nr&XYaNOGHs(kDr&L5)JQ z3lrn3&@&u2yw)V7?e`%@G3oc|fFEjNy=zgQaS(c< z*t_TDa1zD}K9;5Nel`y|_D<4R5xb6EE=Y>o4Od##(X*2(3%$lQh;>X1XP7yb*Qm5{ z^6B$3#jGqPtau${Qx>f~`N;tkIpq8x=Fsz^d&@+KN&q`7DD@DUSKB?ssp6!H0@* zBFLw-!gO@;7)q?s)nz`@%A1FxOil)ST~@JwcQ`Brmz4iUrx)`Ld_7vBpKSk+?F7=V+6n<^IiE+VK0 zSPsGGO8)F|*Rl-v^KhOrIkUhvNxvw9J+<_r)u%TM;1Asj-J^DzR4Wn|(Drn{F5PAc zU;aFzsYJ(A4z{`$pBmf;>CKqrA>T8Y?~`5|q%ttAmCl1(S*i$<$8LuMx4(`D{fb{@ z@VAFsZ^sNy#v4JG-J4DNNyi=@C;I{s5@BNF&f^&<3iN|etUmMwoyx%pFKUwLX&Q=e zXT9*wxBptK2Y69PA0~_*a_(F6x{S*P(#1V=TPm8g-a`7CbKQWIym5Tamw4b&QMTS? zQey}hE0E^O93k6YH1@F8i@^4_&6&6%<|tZeX&ALxX10_z1mB(AZa)-I=4?6fub@-m zrr5d8Ts!S5PZN|P)pk0cq_FbpAX|eE4K^a~Nn@iP6^0&2%aYsgcAJM-0WUHI$CWSp zgboWP+xVHg$VIb$Lrhx1@<&xmsk-xG!v0Yfsq`A@zM_nqZIbpvV@=XO*|71~m=%~F zzi-c_Qz@_M&Z$+;+Z|p>)A+$@;jm{D1jX|w&#a} z5cPza-h3DDq`<>UGuGyz^O}ashXUxsoCkcEhatkH7$b`^&wzd6%)(BAHsh|*?Fzzg z&ne1#Q*V3WP&sFJ=*RUg0}TJB&|nT@28x0jDe4)5iO_Qcs68TdDby_@&dWQm3CR@a?4C@L{fG9B2EdEn}|m|Uan{$D1oejO$1+u9kv$NJ^H z3n>`*eoqj;qg1-2{5wc}B>WKdy+ZU)bqD~IEwyV?{@UdKLeHvWGpYdB1RO5(ywA@R zCc=$}zvpEnLcmS?Dus~S*ZH=#Kr9p1NY?M@{qthsVgGn^sQ_hXB@?F$e~= zPJC3$X8tgLq`~6;=%0SR=t-LEbr0D;l4zl0)K|&6|JzYb|409t|7zQVj7P*d<>4iSrTYl0wP_oF)tjXPtT-RI}o~ z*@D8(AI1UQUV^5il+4wp#mIS21Em}H4?j)3Acx~GZ~4&T|Mh@wLMaxU@sl2+K|2|j zUbczn$6Kd)tEKWL5Dobk*Lxi5-RCi^)Sg#;R)w}osK3073=41t2hr>8(wm*{I6e;o z-#YJQG2U6q9#KM6jW@IIab)yntFikAk+_A!^ft64g{;=Qa>ywW&Z&d?fg$w7mNIeA z@tWQGw;<(tw^p+4hDCSBv$1CHH*mg8{e3oWEixLUbb%!&Ph0w!e zDb0oJ22rS=$%TE=e@2~zWEuOPW1$g-CBa=C*Bv=eJKbNjdS7;ycDhP8m57j=n0N8Z z%f%nSMqXIVJCqH|Rq57g98(1d{qV#}>pd7;L=d-xRId53MC^vlVGzc*d!;@+)6< zB+;_mR-C2<*Ejc)ys0*K*`DbOOL)!sM#|i|k9{;99ko<$F z6I<(VQ75iFkIO9kKZQhrKLTAiZ(R8MY-q|dDl!wNQU3`OsDD}8( zRRUgEGERO{LbJVWzG9ucfp^tzKdz?1N}UP7*P|znP|K>;$RVUNE0#@bEkoZ6dD=AS zsC4=wI=75JfMsdMUeCn?;_(h+-)w-vJrQu_(emlJKrXXc!MYBK!az+veRTdSJ?tJH z;+<1Ug(}9s0fu!_<8EGlk~N%V+MN1KXmU(3WjFP&qP^$Q!~EcPdSQxfqLmOsvGgfa%4qB!`H@#zRLTNb@cV}@>~Y}jNqYP5QcVO`E093c zW>{kGZ_rY~aLXeNm>=Hf=JS@v$DO-8XhW$yx@L)-pN2cdev<6?)aKiS#}km?x3WEyaY=_MtphSs+(Ts=ej^)! zGkueyvLh%*{Q7a_B{QK;ymXC60oxkvMn?eF#omw|9dPRbH7MMRSj)Jv(*nA%*@a}7 zgX)^dj2EQ{ed1?1(IWI~DSpqOF9Q|!d4)8oamB}sTq2}H`Moxu9(K2M@{hX%%VDH# za-jT4WQ;{LTDNC@0lqY=4CY{Xd!)62wEhSD2)kqFl#1*Pc_&m1OVmpd9C)e+i8r4q znIVr>-MPnRx`>&>#fBRp#jE1Rl0z=M6JSuB(!Ywu=g)123ln)I;Pea!d`;lkOYw}! zK_}}B|4HLO-PA_i>jRV9NZQ2vXRlY`w81Ue?b;6A|*VguRCc!J57E9zLxH z_$^$J<6aDSD(&^1v~BQLu2?wa3J@W`M+kN=Isr{JY~Ka&ho&-B6OuK!@j`=pixKm8 zI4jim4byjU%y0#ZqH&}ki3pvn=wz@{?8<{AthW+Zfk>*-=I9pV~J&h_QWiB8N(qBo-(m@US1ROkt{!cG}V)9S$w}B zi6B6XhOu;;45{W)>qGylOPGv1CRj*lBuKek2$Fg#S(nXI6)+fd=)d`@cYZJNxjO4U z=%^?>J9^+_@2>gobB=Yf^5bGbWzE?J>C~9~0Nx62Cf+Xd_*u@mO@wJ2x2GdY3U(k9jTy5yIru*W^h%Z;9@)kfu?b{KS5~N7N2o@>PF>=lQM|XI+RDS^gJ5YW z^A@&inQQaS%1dKs?iFIGqH)H~jy}5*rWi&dI`(+0=V)1Rv|bCN8wM{|4tLwbG~R;XRJ4}q9#{G%e)nlrXU%HkDV zHk9j6Qv}TxnwBbQeHb}u7Gg;XUYXQVL?ki8D)#l`%6+{i3yUEz)2z`O)UR-{NtSkG~NM&VqY!oSPQ#;OjNq1cvW(;aM3s1-XG2WAg&eTfk#5Ltch zdWk3?c!|1+2p;2OTE3`8q|@J)zQc_#e^;9N@Ci22WYD9}77~ThritOC)lRS?rbj$v zQ9|zTl;Lei4Ja!8V!o_3eGm~io;dutx534G^bkJ<#g7u{>`(6_OdU}y_@94Thn|Yu zZhr99ax5u~5~-d#@ol8Ujx-HdH9Gcc|09*C4pt%ov*iGFbu4Q^a`)|)$4F!RuWl`n z!!P3e$zNyzZhY;!$M=~pSI5b4qK^E>R@u1z=r;lXA(jjEx6Ka^9(?-t#c8f~Ilk`G7^UAyFOPw=|b2KP?X8;W-Nea&EfpBwS&Q^57NoC}DY@Fwe+YN#I_rho6a z$@6o{$RGozCTXn~18z##O%&Ehwo1bd>HU05cyz>!)RU=+75%6gl!Ef>wjeLT7`zQg zwf+~-XWM0xPA19FxvnI^d!ya2=>i|2x$Jw?+^QL7;@nb6i({?m2YmK?H!}?4OCi(Z zWE+=${U8j z$D5_+h0V@k@(Av7@YOIRH@iixI4xl{KFpWT=w4bvyf)muYk*AB>+*R?@a?0gCE)V3lYHW zS!7;)E+{h~NA#NDCaT&w@3pr+{->DWZYZ91WDDcO+TKIv36-l8L!>GIr&{bXH;$1f z;PWDn%iOKK@2}NLOKAO}O)lW93%ezAwB6EoUaz@_wZRqtrbuUU@YDXV%UexE{O?Dq?kMWisMyefa|_m>wY;=L2weaQW(k4h;wJ`PdI4(q8pkLHNv zNlLEP-75tKjy(Abjxugq`UOWFZ@8_~CH4e2QEN$dLV(+>iF)3Y7^rB@TJMpfqDEHi ziS_FG*r^M%5=!5bC*rS{@=O&zPb-6IDaI-+d5#HYS%ek`7VUG=dG)N%BZ6i@#M~wP&ZX zG@MK6712Zy{g&vmmVnfmR!}RT;Z?VTZ=x71zFEq%p^~ZiuH4-s63@m$u~;=ABD$={ z0;=;ABm*e`g%WK_WsZHE@&Y}s=+lIB;>k3N3_DF_ndyN>i0lca9&%@w%!x_SD~ri& z)*DXb9-PRV#o2MJY&{crxUP2&dQ&%!u*w-}y;aWzrMVF}bERi-g>d^u)(q++*%sC} zMr&q>wd<^=`PJlTS%1t&V`pn&GoIWGz@V|u-$E26Tw`xoE! zPXr1)Ji23tDu81pT_nhwV(%)XEPB3OeoQ;^n!3l9S^a`T;^IrP1(Yv(q`xF^p+`Qf z|DKII>)Tju9wx_}0pVU9O15xih_ec>2?A0^Hy-ScLzBkqWv5(2zeeO8yTszJ>lThP zEu!)>d5Z3uM?jaJ|zo$s7 z(cMwob)Tr|@Vo%ytpsN@h+A1qWB3NwH?GhyExvY9bN)Ceui7`B*>Q zLYaMI*L%}&UCZti%Qo!<1Gi>w{?`_a)SR3v7*bDm1HHSaW$HO*jUeQa)Jpr59bUsnNkj^rUjeV@?rLwQ&$PnjlL5?hB1!wWYNc zDS?`}(1$NwF0$T8^Gj4m9LKI;a+ESLO=Du+fBsD?v{6pA-lyTJG~kk`tY7B?UrKyg z4}W))MY0yKdTT9$E8=C4?Mi9sm zEv-KzCRo`zlu4Yn>W$b8cqPDNJM!bP-7@k>tqm~#MDE}u)T2`{y%b%Z%juYlCG)(U zI7lVLTdep=MA#c*A+@a{p2Jtd&4-8wPElpfe0IW|z5?l{gG;R!B)M+u>O%$;4p_)} zL&Z+8%7518hSUVfFr(Nlp$L_HhG`o2~at)J7KcSyaxg+W8m&!E5ZPZ2e%*y5g6W?+7NlB9%s-xdU+uB*q=rJyb*Y{wNiFdPBTuLd zPaYPsJh0t(`Y5!X-mT-*Eb4LQq1LFC4Au$-?Xm!46ec>PJRfdA?&Iax_2n@#iOYUd zSPBuybVeqzW`R(i9I%xYBS8e{?J>Vb5czduq(=7>G6M_z>`Xk>CI|BpufwF z_4?=kV%MbR@;pD_&;@jc3GO)fK^OF>hP$SA;`&t z|7xWRTTjI1w0T@jPWY+uhz-$EbCm<;P~GXw*$fv)iTyMz5E$YVfw83hj8~KF&Xu1c=@`E=5&A!`;O3 z;i=9Nb#!UlfO0hh!`+GMMCPADY)=5iHlc;DvyV{r%1p z2Tw9e?9L=rENf3w2eqZll%08}>x9GD7p%b3yIiJ}(S56y)-;2JD=v_XAq)O5`Mdi+ z73=K16=-kI7L8aMNTEw|oU>N`< zY$7DZFmGyBnK7>w6A)Pr))wA#f(ziGRJYHZtSMZuYa+;LBYJ%|Pug4pa47ilPeNM3 z@{Ng#gQN}$xE^fRP~DThH!s}=qj?Vxftgfdw9>)KIgG$eD&IdwbItF&96kiL#M8v_ zY0|`#)!K=~yNRk;WQ?Up)p=&=a!T1#HnXi@##M^3v@=ecuZaj4M6ZKQ`aqQZMe@w5 z>QAWc-IB-|8}lV{MatZl&cSVhuF^tw zkJ09nL*@uFBk2A;-F;-TvoWa1@N$}+*qa42$fCj1~tWtvcEz6{=*~ba~ zlZQ*_TQ5i)m%n@#H7!%%)1+czYn3TfeDzWU{c7bnXI(+l=w10fV~xf)uJ+iv>_;|$ zTE1&(#F02hJSEfD)Tks)#obePM#*1ZLhQ~O3Q;Shs~0L7i(r$q)eX$rDjFl|$fcGA zXQBk6Pf z{$chZ?ZM_5J+tO={qJZ>tf{fTAulmiu^P12{~saksQn?W1qOIy1u@KH+YP3m&!qq@~yA8~7oSW~K!{<*GuSTvUMDgDf&V;%l0M+3#F zII&0QPl+^GWaqsQ=&^4-O`WS(Gt|(*Ih$7uysdAyfm z0hKe?a7X7r&|g&CEl9#Fzrbm#2S6ISyRT;oi8tK7ajRA9DN&M+7C!-oP_@xjR;j1! z5BZ0=gtQ}w)44sepoye(?^{F=Ev+URnAE;g&RqKW1|zy@NA>$F{C;p`T{G918G5~7 z*PM~9f?V(Rv#EU1?w&rJ*voe^t#3c6x9i=VnQaQM-gjXMt|Cg=A?8U}N;p(m0hS$9 z`P{94v7Iw?>z6opjh5N|+ASD0H`U5%Wi4$1=&P|Qx^84>RPCar0kq>{5l?4sOT5@8 z8Mu!m8a9rV#UP+%ZL-fTHcKFKhxN>Qj+n95q;ve?(Ai+rz@JicvZ}SBu)Kdj3~3!J zxcW{^bnX=UAIU?Pm(A84I^%N~wgg~yi!PTo(EObg+3AO`U| ze-oUb zkL3|GrjbTa9fS5ng$xnOQyZ;jY6j(N;8dr$Qols(*&Mguu24re0A^ARYR;B< zAp5Bs107r=ZZBCSU?!DTB?2&$s^>G4b-79s`xYOF7^n<_FqMYj_ct`KQ@WD(hco$< z5f)`+QH`w+7g-&=n=O-%Eo-+yO_OUlVV@4Fzi-^FH$admRnv1|ph0aGhJcuZs+J74 z!)6kuQftt7alZV{KL{+&@N9CQ2_;BAIQ)XL-W05a<^Z$jwKEI8Coyo%R5+)rapoWz zIKj~5q=#(6MrttbGDeXh*C`lY8;=Ch2b$LHt2eI(8!{l7c@m9u`UrUPn4q=(gKBzx^2J0S}k=K(RufW%@OiZErhv8U5GH`liq|s%xs*&wq53;8nV< zHIuFr=gF~lY{O+rWdYXK_%zogWjzaEnX92JjxSb(VsfA|pIuUNt3s8qw=#-7m~Der zl0~{9Gkh@KycoArETH^2U%h9U`wsYhJFyryUN%zFEQ%Q;Worr2VZPvKPKJ~{HkrA_ zzK}|Mb@N)o*LWlr%slZiF_&ADQ-C`3~motI_V z#vkuhX*e)S%MxHN z6%;kd^ojY_p4`7`f8Bs^-}{IRZ;w4_f<%eq;~ie{Im(3ia9MrqqkKZ2nI0rJe)P`u z>{=?fhf>58spH-6)Jb9IFYtf68LxdmN|C9RG<-Q$#=?SxYB&QYk1Se`!=I^pt*jWDm|LDK@Eh`AWyP{4&1$n+UU?Sq%V2CJQjsH7q+i`N|$)CQ> zkG*$F1#FAItQ+75lB*XlPHwDoS})I!TyPM9i?P3--9A6C21>^uhkySrw|x*s?OD6M z3;5JfugM^C{yh@OBbsy zt;`44yBh)Ct8MHrRxfj|MjV&E$FB9b8@52&uD49k`dp4S$LJkJujf^7JfU94s2HLF z-^PJ{-+vamz#k~3%0COZJ#F;^(IkLtk4pv61P4i;Kl+quXaUPqz-p8TNU0w_l{#7=+FArux-5C*!vDm*1N5=>&wQRQEj5?&BtiO z)^g7{Y(j{e|FrG`;~+~R$vN1wjh-bcxIZi0sBpckzx6ERb_9BpQBThHNY zQ(}ZXu3aM9(3WuQjbB-aAQP~GnHE&1IvS3!>6Z!{wsmPWu51$rf{UN^b`#monrKv7 z2gFB4?iq$IK_JC#&F^LyZl#8ebW$cWpRNBa?BS;`WktWOYq3q`OTkn@MpHDw)EmQd zPovT|3%bMKfR0kbdTY*gCQ>dZ$pc&m(4&@|x4h1W<;{#zX}qE#KS-(yW8Yvz?Ogw+ z6t*D$k@AAE^hoA}u{hU;y&A3;Lt2Y_aaQ%dXwAqd=r-nP1?(IypX|7aacvuZPES^N zud^5e^HTE+0n6zENgb{(FQjSalCm2mBDbhk7iP%D8dZ26nLAfs%r$u1>BVwMUb|&K ziM%y?vD!Rdo3@ky7JH%FBte-KMw6ZT3>DH8gS_}-Xb7nq2|e{(O&x;M>5TT|Q7@53 zDC0sjU!xmr69+|j{k|2uJSQy3`AsZmF271CD+Oj*K-iXjx22dsT7T}res@5FZ+$j- zbszctHqfPjdxuo%ch5z)c6QEscDT*#96^+(<4_Ll82(YyNX_P0s3!APLYeC}yZ@3f z<2xk4h_Lgjf|1NmJ3hBP(5*nx4&|ZCF4HJQ@2*<=DIzg{6N>S@b{!R+jQi&|HsSL>rU( z#dfQS8<*zjm_Us*I#&(txOHiiDcWf@_I-`|kvZ+{^HnBTh_+eXD_2GOXfi0vHFSJp%d+%L|mBkZ#&!tr_N`ZE9C zGCLX4#OYi?kO6RtH@qw<_RwgcoWljLS+H6&1DEjWIyM9x2{oMYkkozJe+|4JQYy zO^d@RFs>f2t?j;U%sFi%e3i#RC3{H9Iw4|b0O}>*V9Ep8qo>iBeINq%8sgnytU+2c z@$8qDwLZwPf?WytG$~)xU9H39skNVV+loC5D|lTNg(BadM8rRD)L?mHh|fYw?B(|6 zgChu3%V;RTb-0;m$cEEDY(xmV!F5>Z61O*5?~2dfe#vNwCg zW2@lKLbC;oxBf%U3~J+MvfmmL9lqA0Q9W4d#(MicmkY{Fe}i-`LeIzedtU40vo z(nyvmVjKHXgR?8>V_HKH zJ?si}v}ldKXWhm<9IK|^@`t{TGtc?3eko&;$Dk{!n&7kB(#C9`XlsG{I{Ob_xLgpU zTb?ocM4NKWW!lcCz9@Xe^LkX8L}6Qc%WA-@$;NfsxCgpQ*VT~u_2)2Q3j=ajW?hWW zpv?LY8gZ{H*7Ap1I!dIINm0#Cs*R-86@(uszy=g<=%{Y7ROk=Rd~nqlTA`QD?eFa{7L}yD?YRxpSt%uT3bhR$~El}r-3r~xtZX7xF6dr zGrzlUo6Nv`U+wCiM5V$f^X*LSXmW>Gbh6Cz7xfGk4?bNbM~)j%pbWgE&FeB=he({V z&dJr46*V5aocirO@JHU!bIjRvEq?0GV(NNz5zVUUz4S+j#?)7q=EgWIOuZ!Zy>_{e z1K|}NX{Yk#E5L#g`y{C&$oCFSFkqqWX~_0!p+S!=&|xuweHj#ZMlugy$f>OA->*1EReZdynHMkg} z%L-hGQ`(jGv&_OGDRd(b_qMRq#`oy!X~AJGx4~Z1vZI=tP1RWCYu*Xs+8gDogzD?| zb~yoVm~0889;HZV@!q&_!~a@il|SK4{?-C#lG-D4_iTW>_6A{H-$nd1d+N|Rc|OI$ zlul+pHMGC_w^HDL2-nr4c^zPW(`?nQ_{ZeSu{kBb%keg!8zqW&NdM}gyaBXh-_`d@ z(HrLPk!eBo_NRBAaNz$7N!QjDtE3h=+yHPVRr8+#Bey=^K*@ zQ#|I?HCN&Srtj+o3helEt4%Uma$3;K3p>yrdlc5jwkJ9Fq}YFMl+fSv*ObAi4}0SI z@G*mw)|?SU1&ro zOuoFatNVYLhk~K$fiYOem+5zhh+Lb%VP!89Va2FaE~Wut@CYo;sa30`}mCV8IH<8aaedpw)_=iZY9SucVa@W^EnDW7G$~X zxv?9~Ac1~Zji56HaY=@vmwIkTD(vcNNL9~S;~i;%WymxIe1?-jX98fzfclL(v%U3| zH~087GV_`a2jZTYz*TIP7DfUPlx*wP|S;~So z1&nZ-nxqnFU8pvvogPnVZmN?^r#+r*9`mib1x7ek#_Wdw7~zyX%LH7vxA%PrEE|M{ z89mOCJJ%Xg8G?Ys^)_VeA(8i%za15ER69{QOs9SDpKgl^XOxj@V`g#9NP-~4A7=6O zq?-?Y*Yx4G3$CQ_woP1?ua}y7rdCxF7t450!LYfKd~*pU?aRbBb20?qaQDH0kPUU= z-3gxKBN|ICB7IRi&V!*v@V3I&y$(BgKZ|hq}}mASE|4{ z9{uS&uXekocG$2`Ev>z}2k)lc!8LNiO=?u}pPQ;h!r$9h|F>+~EIMO(Px6}ndY98k32`N@p zZShW4rNMGq3z>|}Rl2FZAgSt^a$(U-;96thGmcK3&-%kUk9!eWzvPlyPh$vB`yxKz z2ThS#IZ-euHfcQro;e<|SQDP^D6c4;hXZl?s}BdhzX7y{g_Mi;Y4uoCR%^f7QZzcl^8T_#21vSGMvg$JO8={*O=2}z8yLhRY&Li#f*R{oF<~3D<+7e76A&{^xx-Qi z2-~Q`UBv_>PGv;BQlKjjh_OeMcwTv@dh<16NgzgkE!lD_nl1sCV&>1ny?RD8tc3pM zeT`+-hwnI<^p0O;ScjEaNp-3u3kPzLMpJyAEA*nz$seh#`(+8fa6L7Myy6X5(&p*ogKv= zr^<@5-+26OYup}Bk$V-jmuc0ywLR*m$+R6WZL)n}*;9f5Zi3L6ZHT3js`V3XHydWk z$r9Au?^07~8?XOwZ3|Qh`rR1Y(bOm1dj3;>&^Om94EGd6zB|7&cUj{Y_qniI;k^C^gh1Bd~jnSnApdeRaxJW|0j z!&cD5#8fPx@b66SZiD!`^QSb6C;js=#%s%BAb6f=%LLQm*_2yj+ybAl5Mz!W&2ZxU zC{P*8*=NOsbrhQ*Erxi7o(yAu<4cmn1K!}D>nC-p7XolgEoS424$ILSyPXeOy4V3L zqT*z5cgrS-{r2m&;|o~Y2Crja9sk1+o{UcUMsu;*)(B(jhtWLNb3Akw`;z?e(yS-> zZnc&|Ms-lT;ME1^*JcJP-bzR^l6_J&m9bEQiqJ#G;Wct$N_xEk=?w4SDwiS=QPFF5 zQU^flGEM)Iy1)Zn8lc_l;(Sqstad2%F{o^owobnGXSZA``2JXKK1?i$(g6uQ3!G$? z?ZJ^?MoK+QyVY6s(rwk}<(S0YmwwV7IZjxA%^_mH8vv49=VMu(!LFxVWJ;QTY(xNB znwHO5nt-!5ddPqyF_6ZCi^pf9qo~i80&)ZxoWHw4i;Rj*1%~G7c%qW*uf8xV1;{{p zs^$uopQZdzYLuq(g<+@Z@7d-Py!xreDsQ&Ug?G3~m51Fd9=U&!BV@~11&aT)e((J8 z@%Vy#H{Uimz zCPi&Mo9aX+LJTUg)Vlt~FsG*UW?RO}85&!RBQ%16x3B{e58Wv*xFSl7&`7SpoR{$|re-#{LO7c5@ zOVDyVpmKLc9IuZYKbqwIL+l2=hPUni(aOCS4fhjr{7&~*dw(EIM_-n1Id5DmTmMy| zzxvU}rM-FI{_l0DjLNoZ&ym z?4!Uz^!`0%JQK59P8F|p*}wSoIdK0F9WkG@#l=7#v2c@|&7z)plpgp~%ovA8PoXsT zIgT|w0B_0yR#!GgpZ8|AC4;^Ug<7<-1pa z-0c((G94@5M<#7Rdlj$+;+|gL2im$G4W}^e!!!3kaeXLJgmzkzfh@kjwwP@>r)%lMlS?1C@WTMU<8?xchSAU^*NsNz{f z+uCz-Moq$IF)BF#;FnYW+1G|fFpm>I{L$VWiev!reT+0f60hyEixibey?CkJ0d#lg zW=HkMR^zJT77SURTQJ&9 zTpD&^-9rp8`IFjbXI+!(;*nHmu65b5F(c)E6+&7QZe=!UUcs_ak9`dfE0EnF5R?|$ zRQHtPK67q3kqM3v@9Pj8^%cG3?{^zB64M(=(9lFTtx|p|Qc%-in@WfKx`bQbqY4OJ z$hl2XD`&fyQa*J#Y%fPwXHfIP8E(UN*SbPOsQj6Ptx&0;1UF3u_^wAX_ZC-u(lwSF z69)BWfsZk8kKWvSDUX^p695i76CPT54ZNz!^Y(Ez5>TQzBZG`|9O9#U%qd&sQr&R4b5~ECUk<*|duJ7PC#b;l9v6OT zonji@;ZG?zPI^%73gwG$!p;^4VD;dDKh+D%43%1KE1qSO9M<-5>ZQP+w9>*oO^)*Z z%7Re$if1LXni~^OpWE?Yl3$o_i;+eF7IqQiXQl8NJE^7+o6UmuD%D%sLaxcs=Iu9B zPq20WBJhr%Jx$3AJ-lrM?VWp;X*uYYfp-38tlP zU7f?nu+1pb(Rk|mp~riU-BK3tPvd`vmnLO$Jlo3|P^mj(yL)7BtGGEX%I|`!Xj{4R|87f4*Aw_plAFaVw+}OvAkn? zjEJXscX@gNNgIWrxOes>Xty~(Kn%f9;g7K;gsAQi3Xk2(fYVX}T5r|ie4>v>gZ%*q{BiIv91l8>Q_=7>+Y zLmEOY7t4!b?YQpjwtf)NpRI`3eG7?RaTz>GEaIl} zI>@3K4A{vw*5&JnTq3zN9p_w6NiWh`)B%(dRfea@7r7^NpqgdbOyRzGyA`H7 zU(R6{KPnPT((W)$>P_sPa$VUpn$L55!M#~yaz^h>!zei>*kVQ90PxAS0%ChtHCKtg z-f4A36lgA&)j5s#S1e$vY%2>1=ul}2|D(h~@82@kUyQiyvPvPnO0R`?tq4mlGAIkR z$Chgr1R%PyT(f@&rUR`%AT@B@zqT8&83|{&$s zxays^PnfBe^35$>Y)++*BOupuFa$K&Nrt#im$A+F3%;ZzuWb&A12xO6w*2`M<@v^o z)!oPG`lD1)Cfq>Eou8`YPmEYp3aK&lQh_ZFL=4b_lo)N}h=bC|^lB7F)7Z~RRY5Zi znAzR=CH>PuD@uZq_aZy-Ai9KbNe&0YGCgI7duW22D}f~!6w=C^9oajB#K-h3Z}nX> z&%rAo%8CfiOm}NI-&pf%Oodua`GEx(q(q-#J^-w;aBU?M=RD?x;g4X@j@^awiCuBU zB9)Xot8(LtU)EpQffylr)PpMbIpFTjDwD} zL;<0HA5Yr>#|Dvg{c3{Faz4A+Vu~NS9syDGM9bH7 z3nx&5zPyLSM@yuQ*_Oik5n4TqF6sh@89s(pliUyDh+SU>!XC&`m<25Ih>IUEb!lcM zB~qs_n*%H5;yizIw!y@lw1@$|tpU)*!eS3gQmb|oxSg{YO(^hZB0=bZJ3Nt48!=Xv zOe8S-Z1l5&VAgqxvEqZPVq^G8IAyGldGr2f8_Xzk+wodG$IkV^a}NCuOJ zlvlHakETwMlvI=U3kLu@);(Pgn|RliJ6Lm-ZmqSQqyz3%Wg7*)1e~keihTw7{zkB4jSG;vmj) z;~w_H9EEz_O*R$}ww-!_{T4l-Y4m~^KQmP zdsDOZC@Et;0b&d;X=K)zr<+M-Kt?wS{;xK}7hW#CzfeRU_NULzx;%0t0Lp|Zt6QKxn7qlQm`VLz=%_90=G$Udz&OAx z(VN#|>&9RIjTya*f^$Iss1gvxF{V)}&O|SF7kguW1a?TKLzP$S?fKckMS~%Rpr0Vam)C&)=o5jG@2d9y~$qp*`*Y_tNYClO+XG zFMpS+_wBnIMXz^$kTg^?$-lD$|K4i=XfzY~Z$;_<+qs&fJ5L^_(~sl?0O{=k>0t5g zaUj3)J1pG~oCv$^`^z~0<3-PblZF<6fI9F$zk|RB56=L#rK0hVe7L+=@;{R@D`V75 z&*6b`=lsl4fs^Qk-DLn^nvQkKxmu58g@Z&Uw^t>8pgNyXN;4EUI!qB?jF_jQ;&M-J z3CaEC?%%K3Bv8j}(H?!+%T@kVn;RY&p~PaWWg4cPoGbUJ0yp6LP^a3!_)?I_o4^DOS?Scgc>EC8<5tEVWRd2!EWnGsGCEz)Kh zgt6P^V#x!b)txDl47`msAA{7htw_QahChj-$PlB{Rxjw`-oUKXWSQfab;dU)d~c6g zUKBpy6|N2V)=gLkLe?wFvV<+KGG#-SbA$q(UEag}6!NNlw!VG;!(5(O(Q=yaqzk`V_;Z}rpkmGyE`glklEv*jr*DPt@t}W z>JNdun5t(GS-ZuBVi!2salP2xt@53|<}S6~1PP{<#i#M10N^865HO-27j+1}bl(Ni zGk@j(WSf~dZ%I<11c5_O# zom1`$x@0zDYHDlGDI2-M*`MKY#_z`N4-{6#7jYnzW}pYv3lWU33CEXw^+wQFvU!H!urqBW)-Hpukre!6qdvsi@q>d&lp?IE-jU8eW0(G^FwHF$JEfsU!Ry zq^t#tYqNXSm>K-AV*W%H47z-iPO|_idPb1oGi{%b?`m55dv9xrqeyJgLP2cPIeqsE z$+;~slUeE18=c{93j6e$KM^CUt)H%pzp{bVt6kih@IS(t%%~iWnR%tHgchNNc@&Qm z^8Xnj-yTVfkG}7vW31xWLHCRGNV1+ze>_00+_w%+oczMhcx zR_N)oR7k|L30qIcqv5yD1$H~v3pSK(yJ;T-F^{1{u5-}C4ItX$S&k&XiMs4^6cIVy z2ZUQVy)?)G$Ie4GELbt%)RXn5cCnlc^pj?Jas)hh73e8gm_h=ycJ%pjqn{_=Jijy0 zfJ(x+sOnWF7H!aixy@ZLqUU>xH2qKDh_SpCoR$`EE3O?s{mA|WS)2VAqN9hgcfr1U zKk*NPE=^5Ah6CM;XLt4aXKSlz3!8aZ=i9u<=6y4w^~_0Ac0ycOfXYu3v%1e#3-b@W zclCyK%_%doPe=$@lYRHGbrb$OtAd5nUs^;iG`fJDw&gWn35$-R&MMP1eZS9X1r8f0OdiOQ+h-C%nS5+NFey*& zqtc#Pkng_XqBYD&yuq{nK^pz9i%O^z!oiAmaSARpK%};LyjUYq6tdYpK;p^3h<;+P zZnQqp2RCS@%j!-`OTlSlEEqMR(5_Vb7g^S6sRAWG*rSxvpWmSl9sp>lG^3}=_SpPf zl0=PEfJc=lIG+Yz*TRqg58)$In6*;)1q&fPEIyP;C{|+sTIVk7|k8zLWSG3KYu@aaw{dk~4KQ2<{ zj^_~R#PE%`H$;?_Z1rsMC-%m(#%uDtLvRVOLT<;7e+*`V(@0N9eIE@(1@j8cF^Mkb zS1wkbcz2s>Tqs1>CO@0}L+N_4yzP#{M$DbZ))}_{^k^s;!iH$v)cuN$6Q4Cil|=G* z?pbCEKI5<-VEC^TiK3EaagG7xb=@EYz;YE5=Mzj5E(EK0b|3!DNl%KYpWmN(Udjie;OT8 zGLLhMlrz)CXIvbT8?F;<4w<3TG=RSyO-f*M1^L6tY4{cxJ1R$09gz%~S}%FY)A zq0j@@SEKKQ@V2e3924|))KGQ`*egQNy*FVp`9mzNMJct`Rkvua_*1o?M1IXniuwNk z!n~w1$1OEsCK%5*Wb%P=N#oSZOwBX2_$>>ykJ{gP(qoiFCo6tBF<449pmF#LN?21W zKdHQDTYm~!EKfHqhNDSmBCGZmNM$yK2Q`MpVaRWnT^p8l-m(zS{+Tr&)Fi#$@6ab~I_P$_R^ zsTrJ%D*z@#?4;=F$ALC&e%vniA7vTvH2)PZN0?7*J$>5@%u6~HvT+>xnK93@f{dIt zAj$k&q9S+)fuOBXn4B)Sl|qm_>n0+s)?_v~f%&YUJ}u&Ra@{|er00Nw%%KTj*Q`z+ z5;2u7{yXQf%mFW1{$bOwaRFa(kaOdGuttR%RQc+6{*7NO(r-lGfm()J0LXoN-bA{G zeOSGrIPs2D>i*3yM9e}Jy}VokB-?w#x}%Lvpg@F={28y5pH6zC{F_*4XbP>$nKG8v%Ev{~n!G4X5k>l}2hw zg=rI6BzYuz2ghbGdNJlSQlZ6q+DFZSW)hpgj_=eavS)V0OvZ1^6%;$t;PKl{Mh7 zz-`0E)s?7aR05x-6fz?2y~)ey89+m~;5wvxxSPG^rvW2*b+u-u}G8VM%9xc~!8x#CUcvcO7d!T@YD zX-BKvs^=#lPpubCh9TvPxG!O68vDh@G>+uXc=q!+j{kToljrSe`bGQpE=<95}0H3 zk10yebnTrcuLoXJ3pBua8gc4xYO-PY&m05Su+9Nx{QP1C$?=Z?cQdgz-w37rs?(#v zrCnSF)p$5zkC3}o+!s~SEYki2)!sEha*Hm;Dco}*Kb|p%8jukeYppFhs@}70Zr;l<@;J2Hzgv!gG5&Y7`+`-y3{-xqyX91U$LC7I^)S=p z5H5Y!TpEz^Vc2DGzvQpkKX53?A*L}9`~3x@-^tOJSI+v1iSMK0_Os!b{gK{xuDE%= zEw!{emHV`(WBo5JgN^s`_a%97Qa{j5@|9#%1qKb7137&jqcVA?W0!HCu!i?Cwv3J{ zr!Rq<OZ{KDZ9dl~Y??}VX1 zHn@Y~soPBaeTBRTB+X>GzJ#)0a1z5(7{m?Cs{A^yl5U=}yz?HXB%Qn$y|aqZ)Kq1I zTmk*il*Y+I^&kzW+HH|40*nwCv{xVQgN*F=z%9O({1j$u?n1Tq zoLc`mKgkh6Zsueri6EX;7U1h`lB=AzI0zC7QE;(%luz}TPu%Ji{{rpdQ}O=KOpO5O zUs3D7XD9tIHtv7pO2|GZr#PR0Y%2rPlSuaqR2R-amJEQ8YuGP8*EH)@{R+F=@Q%`T zL7&dg&ed`;Pe+y{gSA3Ea2mC;u~`M^ZZIuRXNLZon)Ii$;Svz5ELd}y zb^YzD%XawTLldLnYtNUAn}Z~!!2mw|GWUTQPnnza(I_aG@UBl8rjXkRi%^H)qo?3U z!}=qG*)envzj;>OAV1EUW$|v#AL^i}o5fRU!iT#Q$HX1~m5yd!-O3*_B6+kClW}j} zrT-}-8?gjkf`i{+y(x2uj`I|`aM|E7aixI#Kt+E`M~dA9y=d5DiTa=7HVC~$XWN(C z=Mh1^{a}KUrbGtU{E$9?*tb6u3$azRji~LD(2pi_$QtJ&iZXnkp&d#@kS`eJik=Ri zh*UVX!-Vp4!%6EkHO`p1mDeh9Fr^ZUch&={jHO@~m}z&^r|G-$)1?5_D!JvrA*5M! z5rO0G7sqWNTTBdp&Bk9xMmGawY#kVlK-1^1WXP^J{0 zaNYEA=xp+b;+Im57asu_sq*eWVWe?FRtk<%4gZCwA6b4IqFqpys}U|zNT zZ~NivEL%Xoj~sl6Ql`i)yEP@h1nZ&^Y84ERjG~Q(51eT22WI;| z5$XRQ?7e4HR9U;OEh35_C|OWYqT~!Bh>~*>C~_2`pa@D95RfEFl2}O2smNJCK(Ik1 z=ZqjhGA4o&&s-F?w%zY(_da`!Z=CU+pWQvQb-ApX^Lg(3y8c2^^8ez#op~5Z(tEoS zz6XC=Tx;z`@Ne_*sqKeNA67qGnx4Dp!?r~`*dv`)A2hR)CGUjK z3%x(iQThV6vZeoW!Niq+&G>_jPGHT#!q-C2{V(8W)U_EullgoF6M4_c9~umc+N|Dc zQ@X#oRe#fE|3Q^`A362RFK1#8Om3MIq6X|#!rN21g*U#vzKINs-I+TajhdZ$nqyhz z;kz>n?#-ek1i}@xJ1FywWbsD)1<9^>tj9;%rhO zH`ctBEU@ML3?Y*X#!`<*&n>=WX5{;%V|{R;F)D}@ab-Z{TbjQP`L3Nm zm&d1f{{7eW`DI$0$QOr`8ucA|#oI;49{yvVQKq?2reYzk3p&YrEy0|$2pq{X{@tKAQ6Yz=7WAFj5h<-7lT5< z=cSGB5h7L9{ZehxDH5~jyVqdGQHb?c;QkFrrulp8ZVYAfNaPLmn^rKNfGuSEtQ}EK zL3LDJbIlCG5Fdu7R!0U4->oc>u(c&jc$JH~+ZzcCP)q10PgIOMa3B$l=y(1RPW8`(H*=jWTXgfOvFp+8*ypL172XSuXiWo~IO`hg` zStUgk3Z+0}DNnZYidMbC?v?OnHLQF;9aOf`HC0d*&;C;-mcdkHx_|SK_LLtAS{D`A z*~l_a_F~1%irGWtDc9W!a#fFu{c$)^+$MLw-Ey3xVsS2Gk{veY*ed*^YB^x3b;1hj zK!JD<7BdqC>rIreZv5(;&q)%WP@jyiAREXywtXUUgO!Od#po8C``_53M>aw6qG4#Zbjbx!c4T5( zI98STGQntP%qvIy#Tqtv-b9P0s5dSBrK7O!{?sB#oH?7pQ{}eYsciB>$G}v~B3UGM zU=7xxe%=;11f5;)r_d_*Er<>#V2r%89&;`rFp6KEITIsXssg=Vrm;cXmx-@qvXkjA zxVfv!VQSb`2s!uM8bf|T(U5_vS;JkJd<6IKRE(SGx=N|vQ!w`SFLTrGH8De&pbH#u z9^#f_Me749%e7&KJ~)&(nRI@NW7F`;^QCIky-bM}J{lkvSidzzmUY0Y7rWG`@2D9T z6`O_OkzVfF1D)?%tM4$D^W_DHI^K;5kU#`kd=R%o5}2!Pfd;N5LbXkeA|IP4@)5~! zi~$AU5DD6)SCD7ytll=e(T!73Uu% z1wsjzg?6`>vX!r2G+b@#^HU)~mZe_N;i695=IZtiIU0ud#k!Lfop2jIg`GLdHin|k z2V1DbX068pTh_GZzSxxfOT0k?p@fX=;MrcPsM+H?Zf_2$4A-ux5Xnm-qPrPO<-#r)#ydH zaHlAK4pTYui#L!OTfPn|^R}Wcyr$*)%UJyW*LuuApUP82av0`ng09yi>&IMDhvWR~ z7|M7ht6XF7Q)Zn43EJ)cH9{4#*`R5d(Xbd~^o*;`QOu&A4m4pRP-@^=FQLo215dm2 zp&Z#iUL&^oEZl>8`w@qnAu4R{l$k%T584^f^}*s}@gexB(w7a@Lo|}nU2V>G&zMq8 zo-?gFt~|T5j?C^v;U}@x5NlHz-3cL(xy+p?!5g}dVQMIe0oR_fs~}Q+G*@7t%lCX1 zKJ+*^VpHb=yk#ZE8+z!L8M5gZDJjO~nh|;Xb=K|!^59G-i}(DP=a$2@HY4S*3rBCY zeH`2dkXISU8vU2{^HS172fmrdj6OR&6%S0F@gnh6d1Tux^JY((5fO#=F}tv)5nLfE zhp0%#y-xwTojS$~#%G+up=AaQ84At_`AH6)SQ_102e`pt{j8IG1j_o6=m=%Ux(Y{i zx)`h~Qh9G*l1W09Pc}9u>!Ab>GPxPMt?4w4kFWkyeG7tbq$Dg6;-ws^n(C|45V#ek z;a$<}+W$wObll zcfgI!8+PuFs;tkmtg!VzJbS0qe6^%&enAS)sTqYm zLeM4W49gQE1(!d1lu7F>;Sy>wsL`{g3foGizxFF+mWx2%mG19VVujL4>Z4{wsYYdy zLpBwuMI3nOs;tol40IAPSpy3x5mcGVK5Y)p`M34c?}||>z|k$@Kcq>Tt_4T&q=TYRRaw?kDlyj-0D5SUNiKHCgr4PRVd3KPa$~nnQB-oeUJz~6_W9KLwvpAHXQ>r zJX6Qe98Pd_lo8ZmTqjgRG(AH&q&+ z5BBQoi}l%m>qnf*GcpH#^*+$?tk&Z_e@Fb&vufmTrrK6tCcWLpw44HsB-^UD$Sse} zFZC}zO`d=GX-|27zVgHFYr7dic7uV*{q@2Vcm7h#x$f%g2bw~Q|B##ljdpc{o*htN zzp;ngX<_lXmE@1Vrh+hsvhCAWK>>q&$e zc^b`Hl)_!*eA?EN18pV_X~QZ=_F*59Q{fsi@*TRbpk0xyA= z{OQmDCdGh10ZgBdWp+$&oeAC6*R*_0nwK$DLzVTbROre& zv*~(rEJ5RAr6ndj_5ls+tnEspw*s2-(I=x3frr0i9yEdJSqPgyLdv}CeKjQ+JYBDf z4YjetVOOj;_cb4d*sci#13%^2JYoG_so;}^M47Mj;R)d1{b(Gnx|NkiYvCW5nXj3v zg%|lOcKLY@`7N=(n-u&b-Aj-Tq6$H!EbU{0rI)CCNShmMn(V`T@Nxl;iOk^$$Mn{L z)&vVN#A1BH+HVQ`T5hM^;RP-BTly3)ef0iLV{$?SKaz+&s^G{u@BUh3HH-)@>_7t`U1y22r>y>1$8|7>%$y3?{6$(MD@9D<}pgUxq+Iz8I zFic3v!iX@3GTw?=KLdoO=$FJ9JvM!cKjIBG&m;WG`mIE_z%|l5glGv@hPKMdM=@HQ zg_bEtiDV7HgW)xr%ip{^{`e8CQ2EkNg(be%E%G`5Zk=FwluJ1zS}Dw_j3SvLwW0@| zVs9yN5&XziR6N>ZuGR9~YmVLwX>v7!Eu^&Yz;Iw_dct1wZwc{oJ8Hc$0at_siY45-*-f>&s@M@ZJ;_X|G0l`3_Q4`utD<^F)9EDyl}tE zwxY&jvG?+E!Dv|Qqq4%7&zEfBosjz_ZFMhwN53c#sX&4EQ?F&!L} zDV>|I=zk3m&rQrt)|gP}4LZN5$3{iRq-i#5r-6V_Q?dS_ESH+v}pzRyd7<{S4ZP4`(V08Fa zgjkOWtxIKm4@x~%txd3hrYBAhiCsd=AwsMSl`)G3!OP5mE3ar4}cOEvwJJQ}SA**D#tzb(|aC14*fG5D1|D!_iY?0^eh@)kL2JGd@g1lr>)| zraQDIPfcQMKTf5ip-t|0-3_>&Mb@d#THT1(SAlM9`?w)?#tbV)K4*^CVDu!5CbKfA zJSqz^sK1<>$bHD!eJS`B9*@VOzc#JJ(rF>H1JZw~Xh#~LH$}EJFa|y-P|O5dB*Xl9 zjj&3{C%2zbSqM<|3FZD2{v)qq_`~<>^yhA)+ZJ_78Rc-5%6^_li}a-I!)M5S8_1^As$q1=GI79E}L^X9{Nj0i3d+#=(e*R4H765Uo@&+)PS{=Mtt|lxqGg4&3kR+ zDV}rhbja((y8f)*W2Ox)lq-MHw#Jq&F0PRwCd8@w*^b8YUZ#65>iGzF;v)moevw{V z9>)N2v6}uKE|?g*aE$l&3d>?!Murd1>fsW}#KpBG5V_;q;=rAKC-=d|oJ{?^+-=C5 z5q(%67m0wiaY38q(M9;!Gor1@o8JnP`o+P zn1J8wr$7LZKelduOX`~31$@&PL-p^J(w=#vkN(?R#7Bo?PuJzMP>>36P}lF?{5JVd z3XUISQul4xC%~@>KuwXtDj$!6{SmX_f!@oewNkDAgi^MPZ0!yT-mgquIiE+~J>0h- zl2M@q(@2Yw55*RDlwf>qQ6uHrf_vX29#G z{uSl~O9f}+>kozhG|!Dim;P#=qcc#j$UT#dVSvrZ3FiWPfiGdLuT4+yt$lGA%F+U@ zNIHrjDW8|UnWFdlk2Q^x4+jD&>3~Soxr>{;Sf>;#7~WW%>+R1$G6-sayxW>?`-nST zC4X0S7-t{gOZ$}|2?XfI@d|`4*nL#YTvCr>NxK4pSRwt=cPcfKA*Y~~?Bv(B3kMyR zba<{KZaIHh#BgNDyrNo*G+Z`TXKP`8AHq+6T4-f%Kk5)vs^=-wbaXhzM#*~v1)Yy$ z`D3LnO^v)+*KIq^XeXIc-#XXp^;>^&(80Ivgwf7halrDN=oY%W1^(k!h1nhV&G=st zrNbKzZ5GTsdBXk=(fO(Gpi*j#$`dZYKzA6PT6HjFx#ia};{8&myW%CiH6LqdKmSaw zK9(XI&bH17k5NV3hNyLXP}%W;D|oTyjtbz#sZ5st<62*ihKJ`hu*7bhI64uRhlb!f zoljeGV@?laxE-L_JP90gZd%~IDU5L$1?d$~OIM9h$qbwgb;`2K7Nkg*x@l858G`@v zaHDryZRzHCm`U8Isb~eKbEi$*VT<=}KJ@6In(3i7P2L;$=LU;_f6ne#sMDCPQ5EJs z0OPuYlk*D<)*-(mGsfsoMRS?650P;+Jc(=D#|keOb;#mp#0fOgzqJjNLOmE%w~k#3 zx5=dZ59i?vXtY!Pjt(&occ_>P2EWx{y?U5%u$C7hK8Ot#bx1ks5buM+Q^iu5TAX~Dz6!S{pezgR zW$6+Twk?Wt*`KXTM!5!7PThPZH3Oh*Zh`iwGnqq)4bn74 zY1GC;CYm}Db&^e_&0c2TD5BX-sAXL*zcb=7O&U;D)~xxl$$&oGUr0@cfZ?s2 zk9xbimfKS9B*22L?R+{K_qAui@j?0vD6l+gc|Q@2NOs7@ab}YAkmMLDWE3(30d0b1 zm9YDky5p7SWx;aZ>$2m&ArEWE8+Q zzl)Ru)zs4f*r|eTcmEY5_`=|4^;2w0O^hd5xAMtFb^y`1>08&Gnp^38uSIpy}+Q|at?O~k78F;>cW z3l69mFo|N{+Z?sKmQVD|Z_; zKFa6eLhd=+B-;WF512Wnql34R^EH5T!f#%OmJL-Z3j*w8#{EP16DS-%@9SR~Owd&T zP02z@0Bw4Cy|h)q585%%UChY>=hHoP_$k0Eg+-t^&M>JfZ4+uGg z-`NpBX5cn>7c%g&04dNX*Sl{-+s5yGIrnK*`C#cAZQT^N@*S|-(IDwb~;)3o(uhi8p zTza85;C|vKcxkluTR+`9h;u`05&zcy_4ohwP*PA96>_js`Qc!9@KW?oDms8G^+Jb* zexjQS(%yLi=Gq5y!3R=FJ`I;`O0EUW@YGF zavG-M@>`5Ul<83Z8eyIxmYlyYW%o6MPs*+09xvs__A(rW;gg{fx&86KW|JO3N0^&E zW}j}VXR^;Lj7wt2@~-UeiQ3G>mwJRE~MnAJ{e6lm=?Wmcq)bP!LzfC z=H&1AWG3@~N3sY9xy&PMDy!RwkddbzkhgMtBT?ybz5zT3oJ)wn=Tw=Awh>i!{-59{ z_?19r%-DE3GRtj%RuGvn_U^Wj<~u`InBo_Lp?lhm_@Go=S<0KE1BAE6PeSo2bH(&4 zetHtO-!3&4lA;b2Uyi4^30ei#j@J_&6yOf%dxsJt%7qmuvc*4tM0@C>q@wNxmacy0 z%|7pY@x>bSI;6$3OCAN1i1juf{n18*$g`)&eDcSvMKBUjaJcaa6GntWTh#Db?CZr9YJIBNaBKn9$UJUJ zJ>^zyl?&c6SQtF~4@&Es{-uQG|5zKtosmQP=WH{wwD9oxH$e->rP6XF>k1pmhZNl&Q1rHcB2N^YcoN=RFi`m?!3v22{& zd(n>RJoS5_WvTO*h&rsojt5)4Sub9cIwoJ_-!k=llyWRvd@sNZ^OmWGxNC*nNPJWn z>3V=bnu%=z&Ncg(3i2;jAJtTz-{?%vi4Jt(Eba3%B+>W;zq6BWzBG4R7+zC(9eTP`fZowy=l6fTkJ6B-b5vX!!)m^gmN43BdA_>#78%%UJb~O4~_6&dL zJQy;sDDn&7X^l{ikS1a0JF8{V3pkQTd{vK)Mut$GokQG78AV5vu;K`?D)r)x1bxHD z*rbY7#m#lK27zS}kM$N=DNcI4H7}d7h3X^cBj(*M^PkO39O#bo@X`Jn;aJ*ost5=2Y3DoT7JMp)90_ z)+n8X#Lyz4rzww^tIE}kl8G>c^E2MBpVh5N?#T~(3u=bhYb2w3bonx|zyvHB#Ph*7 zJ;pUL-j8CdNbBd#fdtExSn>3(GW{78y$bg!W~5ti!v|R#+rf$at$jRb#}|&%w4d1s zzh@2MLLc|f0bnSMyr)R_NJ5uORerk{z;J|H)WLPflIBX4z;nGy!1mpT9)9a+gx*5a zQ+6OuMit3jn_5BRRzU@)+DW7KH0SXPM({CWsN8TJCz~MHDw@&IS$Rn@6TiRV{!bD* zS`wT9|Q(Oy-67%>tR7{(#oEty9mn?!D*9B+p+uLXr++apcMRH zBt0>HCiK3teZA!Zqf>i(nJGg! zS75Am=@!HK@I62b-j)B5$8c9Mvpa=H@)TgR!XolERqvCe-$w>&0&y_AJ>m{D(4Hpd z;r?LPQ$)?8P7I#+v6{)D`TZ9U$hI!wfU~{6x729y!2<#`jOakDMYXu{1ajn%{Zz60 zTo=d7-|MZ{3Z|$$(Ym0w$|*j0bmV3002033Tg?sUDF)VL#s~#oeH`@r)_Itwo>L(*f$80SpmiAj@a?03>a-Nw+cK> zGl8+mJ7*0YSc2LBPM99e-7noiU@6ouQpXDqmKuC!?4+?#1X@Y>^&* zd72UmTC8Sp3ViSd2`cb?GIj?cfB5Xx;qNqYH@vT6;Kb=(%fP1e-`TSAHOw*(&BlM%I!g1c?nbrY8tudMdD;i#u6< zIyFZDw(8H#;GV?|x-8#E=QSyjUz}!g57m5^)T5vED#g0= z1!R9myPw@EZLx~34BP(VvW>r0yOA{2d(1)c2KlE)V!2Cqz^HsaYGLpQ1^cbE{=YgY ze~t%TA&BGml&%YH%`CD4pLKr6KA`R7?um*PasAGf1d1(r%I_mmFzs(9D*a)0sfcFh zN3!G|`C&Nc$M_uEn-uCAdv1=@JiWw0CJNv_5qB$t z0-Hp>WuHAkALo8>sAT11L=hF!`*!!y06L#sA^9LD()l=U9jV9p<6YBws#40U+=>HS zZLW1p5zSSwP}6l(?DKYjv7VqQ@YjBXQyzg>Yy_V4YYY~#<`Fr9une6pn6dv~9BT6t z`F5$da8h*UH*e=a{UWO)&Qr7+B+)D@?Zlv@G|znXJ-qasJNy=N zweefP?D?1~8`tzfyO@fdo$#%Li@X-Q!GV`%2D;#{*R+9q1^MuPQwRy&hr}b;Q>d09oH7enp20qeEf7nRRzHQGV*3m`y5;`e9mkY5B&T-GtUS?E zy_Wn-a}{fg#=2YwZpGteu%e;D?)3SeTg?Ds}c7l;Az@#iQ|N3l*nAads(01@Clj z?85}J^Yt>0Z5F7TY9}d87W1LR(u)9NW;%ishO77>w6w&>uIujFGCmY|9Pebx6g_d? zapaTy1m>Vjh#jnzbtX{7?zGSyhSk6d*41P2I~9(pr;wRT&zv;ZuS~CjT6?k?*2q%> z`7bM^Uhe^}i1+pB4Xkw08qf;s(I53 zYgqH&6QnUVklofh3NE$flx$|_E|W!0)cOzUKm9c1uYTXb+ZwD#A;^=*OHiq5d|Qmo zAv)N0>8TWe0Z&6@2WP66W#vm+{$+gwT8%0f=en*i&fh-AucocF_Am#-dkuJAYqDDC zwh@=I*mxwIPLD`D1Pb#os$;)Hd4hAzZUxm=ISGh-v?YRDs1!VZb^Kj>k2a;$`#OEI zz(CkbHl%F*vz|YT?N@j#3_-UAo3%%!#w2bc-$oI7aPZ}8el=Sc;Vn2NM72tbi4!yn z_ub>E%i&T~_x!o66oCGW*ue?HJpFG(a;oqd6qSNI$!vBwOksj@-o1|;3+GvroS-hL8`D^al2RH>o9Lr zYb(?8W%%uwRXgGGbG<8(_Oc~<@rZ|#N@<8=$uINgQYX-q-}}Ym_MR`p|DonCpkh-i zuKP=3DZEKV)(U3Q3II=Phk&PTpBoRP3!MD>-5oV`1I4Tx9-eq{zsxT(=^FJsNa=)I&yc=FimRt`WYKjStSdEYy>WEP#XS$(?X0 z(;p*j|LLJnP*_NeYKMBW58YgAQ2Z+U*u5+H^XO<2gd;-5q78jnv))fsD(q63%|G6d z;#l(i+_prQE6bko^4qjvQtWpT=IBLJSxrA+BX=Tm=YMIUnFH1}E3u(CPnC-Bpb+A4 z%Hu3b*K>$DnFhe|UFhV`G8DbY@PFu&dr^Kbx!vr_+?nqP-~0S(Qb!2i=s=?M@&lF@ zV1Ypy?_CM4{)4i-LCqs)hnPQlF)T_j@VIeR@%ixq^BDR(rbjfitht5qVK|!f>-N}Q4iNczSonl>ePe7D;~N$`9~y09u5r>_;4ukMd~dad z5H?hqrAh$EvGPJSGKCWwN{oG4>CjpxHnMK?o_0c4Cm2oY$KP%Se{<)T$<$q=52bQj z(iblN{P#ERa~wyVjA61%La(!JqkJemuN<|qoI#_O*~RMu7Nxrmf&6jA+t;J$EgI6- zqbT5P6DW*l+clb=n30{7m@3W3$|e8XET{I%KJZC_rte-k1c~!HX@Ti&dc|a`jP$bc zE2xFse9`mqle_JV#v~UMzl-I{6h?6or(au~i|F6ekf9DXh+$X=($1d`5_)Q%d%11F zQEsSI^14jCahZxxRTI9NK0fCVgPIe^MB$))Zi_B@Q8P9K;ZUwo7rOw7c`ut~c#2B) zpv#ZE>p)H|3t1$dd{Zy3`y0Tu>w{pt_2AnQ-Droja*2-tt#}a)ZqmA24I}y7oe}Iy z|N4THt2_t1>*9gM>1AzY)Aeh8#e!r-5jWgF(L`gK1UySX9uFD*~{ZfPfDVem%e_5S+5pZ~4G`#-24w>cRE#!%?!xpiZvuSaU&sn!M zvIdM7$<Y6r40R4zG>Z+OuKcfQBp3C6NF&L#F|kdQ$)sEOH*=ZR zYRQ%QI9@-4o>Icx2Eq$)a+6JdB{*Hakue(^8UWq1Kw#5$$~jC}apcq+9an+Lth=rM zR{)$mH??=K(ux)d|Bi*38Y+VPb`wKt{zserQu>X%VL883GXQrmz^-@)_dM7EbRpo% z*uMxVogtv9JN`+}Y)|EP(=^|JKN>XK&7fF5!1ry>UOLcXN32DoWoneyGYjUn3DzPe zAAG`sJaWrP2mP9ojoG)0d_ITT;X3@uaN@8}+K}N&i>Nw)ppIm;rDj!mN)Bpr5u1Mc zl56&BE1dZv)U({nHZ@3mIs!O6KIM!>?&mg|yB2SW&B&Sisrbi?gOIzVks8-5uED&$ zy`|1`yp`uTO5m^#nE(uh-RMYw{f3_x*u1Ft!ctCJcy%77-Q2E8)`BE(R0YmJeR6k? zSEl2Khet-_3HpWXujh2TzL=tqx4;XAT`DkxIr#U^0cgiqjyvzau&Ong2MyB zcJe3%&DPSoRQO96uT(SrQ}UA;H(-%KMv3Bs(dO1>C0f@V@^sMD{;?!iZ}J zTi+ErQ#N~bdOAow0rk1&lE>7X?@6VRvCdIG52zLadUs})fy;-D^frN9y*9Fw_@g&B zW#y`w`%M92tK|nxir7NM3-Ia@DcfX`e>r!fY9t)5u5tb+oRnhlciG*{w~3(xJgiI& zX?~UFSrNQz7w7{oUW2>Il#Yfai;K^coyi7=ZkBiGL-*bjjUTeR%Ftgs0!OmD%%>L{ zX)J}hxsY!m*IiTY`AjiF(U0i7wh~H{-o19mq4;yB%oRj~IRsWs8SmKs^Q0|YQGPti z%)jFV7TsJTxvf((IcvIwRa>jF^s=Q{vI_vJDn1o52`8OJEj(%88pQ7#ap69HZ)LiF#0v;|CD!tX{Be#adHa)hnqJuul)n z<++6;_-6`=vqb=s+);B&h_BWGuUHsxMKA8PUWgZvTB z2D2cR3%@JPMs4-0%PT9_=^0lHb`#e!eT;LYJNcm6IOYsbAbc@lAg|zccw4-nked|g zd@08n6}Cm(r3WI6Vn0WsQfC?I%;sPF1#~Z1VZ+HHU%daTxf!S(DMoC@|BP3F4~3&- z6n`Pal$z+je(iSrAYldvZF)@>U0ajI1C1Mav;0be>SiLjZcp6HN{n50^!}5&*|o}| zR!;{UwWCgj*BXD(3NHedhbhss9{Aip9k`{ZLu=>s-h;@-#=P(PT$GIUtpvtbQhh3f zC3jFY+JXJ)6JfMpL&y+G<@r%z>m(8Ke%|(fkk7WDKn;LoptUgMy*BOOEe2UIe^%i$ zBGuV=06jo`<94U>zOZjO(pVQ%YU#$Gf9-Now(i4yG@44$%M4oJqM5gQM>2+nz=LFt zzBZ6lD@O_+U7uz&JfE!78$Ixi3!RBtks+q8$9fhk9AFy0dP_S6r^}n$`MeiiQK@wR zjR4R^5VCBE+?8=OoDab@IGxr!D!PR%=S^~+L=y8VDf`~v%6)4X;urMQl%5c zClc00O2&n3oq};43W_1ijw7#y`jAOfTf9z<U4QW}X1kF~!eI@a%E^&vvz^PEOl*4MjKea3+|mtk+Z<U>JicK*Jxl6zV!l9^}1<c1m>cbE<$IQT-@{Q(gC&tH8{g+9LLYF@aA zTgvt%nuo4>K$&z|JMMC9{9=p1QcD=S5q~!rWTo5jt5N7Jy-WDt3GV*B&`6qCPMWaB z%(?P8e$`qeTdY@F$KS>0E-c!^%@6gRC}rBz9#g@qO9YToB@N)bedRF6=<~3R6kJWX z(+5OkZ%dQHS9QRMsPOj8I6QtXlhR4PM~3AlY-Yh+qnN$-Y6?CaT9gKAh-#$GE2ug8 zdm&%^@hF^}pjg4^QsOAcsktnTU>{Z7j1lUvQM4*7}KMs0hK9oKsn(WblKn*aXD zNJ&q+Z^?evH2=v~F$KDnKmFsm6XG-54>$JVNll(SgeS&`erswBIV*mzS{>p3avKbY zR}4#bzNUJ9w1aIC-2c2=_1`nwO|uIMLMP~5Xyp7;FzVIaI^$4XxBe0$NHYM&*@o<+ z4pJ@mk4Dl*)@Sd@hi3>yL!*B-52U{LKr7xx8i3=V;L^Jr-_T>|r>ioCB&X8?8t_}i zTiLo}c)vblE9?s{V9sm2eQRAH3wIux%XObaf#GvXiNIuJ4|gw)`jb^jK|0o4;^8wR zvga}rHT)_~N}t5*k;74u*zk$mwuYNM;thIGYnIOy{AK0tT%Y3>?%lgK=3;}RW{3=f z1(vO6S6^^=4xNZKC30LEFb}Z|+;yJsD($LKmpPn{e0RhH*`sZPEhsTIQR!2$SM^^Jdb%UGSAP7R*i=y0j3!yDo_eSrP-^Li65m?~=To{h>0 z)W1xS4o=DGnct_l;YMx}9lTDvvzxO#cGZY11jPnMc@94KQbPG)r>wDCahg*%k@Vsz>!sP|Xd@O(iX) zoCk#gst5km*uK*aJd;VeLS3uCLa;BXT6Ocd!<< zi9nkcqSe~vgL{m=^&!s{JP?Z!Vm3n81oB+@P*P8a361|6kJ?LMen$!)TeeW)!oH@J z^x=j_P)V)E@mAN+=vdW<4zjo!LWt{{=D!&4c6_hTy%cMnow812mZ;(dMmB`0Z^z$_ zcg|_N;oT+m6cN2SNQ{9sdMo;ijLA>vgPL1fX!b?c`KwVy66jWe$RBwxcvMVchDUQ61-_rA(E)jq<4O-yiV{Nk z_4^+|&ggyO;|Y&>a7gEgXB{+a^zd^Trq?!XAMLZgQk_sh0VQ{QUq7cz^-X7|#4}RQ zZ!l%+M-V7)^``Z^e>?-(kat;93!lucV2XihH7d%jp|Oi@-<#Oo%?u67DrPE{?lIbQ z@TpE#QCqR8NT>h^ZmJpG9@>fo=SSg(mau(HB+8eE{&pe3B4KLE)8q95dz#OC)ydXS zH3513*YQ5mR;2TE|0i^l##zoK1JKiz0fs@jG!o-Sy+y}``sCAiyhWu$`uWq!>Pd*g zn#j2^wTf0nifY7*`8t^igj3D)cyG(OBK?y-m((1t=J={#3x1xDWArvAv=IP z5Zg7PB%lMyd872chJG0>poa)(zV_)h6?lNOOj% z;_~^`2o4_;_SiU_ItFNw3qumTIU(^kXn&Zqk!?{`oC=OeTyIZl z)r0U)p`};Px+|Jn;OO?A?4ynxx8d^E4(YBa%5xh~r_f4eHzSd`HesGj&#K5Q?APBJ z)NeHfc(6k?(AoWeG$Hlob~arFiVN6FOn8vF`5*+CD--cGIgKCAw?6MXJ}p~VAtLs2 zr^_Q)TwGkXh4?79V@HSEZ&&yb72y-l=9<8qA2OIEb|^}I>k^Mu8Jb<#9Clrow)4q~ zjbQ(%fVd};OIF__o6@XmYkNOP4<|q2%Bx4J9H)mE=u6>jP+V^4#iAOFFPQ*lSa8yn7rX%8vRh*=Gg!=>8b5vmL zb87XTw{XJL6~oPzm@fAO(Pwit+Sz)yT>i5R(K_3`61YGD+X(}pqY#wZPd(tCDYjEt zsvW(1c8n4CG8NO7G8H4qlkl?_QL(Ca>Bom?GtJaV8dwa5U*iyv&2jn1igfhGitPt9UJx2tKChV+-` zcLl+G6x*2!J}V*Mj$stPE;5~j4H<e?!z z8eYijuZVT?bjlC**SgwX9dP)vlDNrFaqWVWyyj>20=1{I8*1+VPsXKAlGD|XB$fz(b5r8Cc%9V`2hflHnD2*6E@{>Z@Mu& z6m)HX^s$)-7}Q|Ib~no_6K0WhnYtVS6Ak!rXP7Q_(Vd&+=qJ8_oX#Tq?pP3ZBL;sK z^FYQ9No03~4#jzib@vs9iJS`t#*r!=PrbP7 z?G@~-re#vh=yv%e{O_EnWk0Ha_2z|IS|FU4pT`I`QK#rHEj(kUj<(noN~grUnV#5U zKe)sXM*AAw?~XNO5w~STpFVx-Jrm&o8yq*8ga^E^a*bggv-!8x7_opvTL2xMQo$qc z?o=pWu;2(U(#cpz_dl5f0`U5sWs2D|H)cR`;7&nCTMFN;C&Bh$g#_g7Ry+hTMG9&x zoj7I{H+7Y+=_=(8v;GWyDs=64H7jpvenF705CyuT^zQ_~i$C(r167K-I`}Nn?qV*_#$> zmMihG-ZFikp4)HjNhCKnY+x)8evH_Um@Om~GV=d^q5I|%QWezGFUSd& zdY;kK`sc;pL*URTzMkov*dp?xI0Z^MPYvsK=Sty+QuW{R*8!*A4+RK#r;IrM(E{?9 zWS-|i7X(w9`wi`$|5y2OA0xf}`h%^j6>Y!wBOa2R=>X>M?Kfk}(I7iz#Ym+#MEIM| zL&~pxx}HXOyWjF}vJZ`Ye%s}-AANQI0;WLS!JC8K&4dFj8@<;}x!b^88HG+zectS= z-<$P>uCGNO0nP~cZzF$QGS%<-GSj#oY~7t4)0B+TU|0clixJ(A1UsLu=IRjdkF}IRsR3e6Z10 z8J0H2$W};+@sZ+{&_hsa=|k?YK?QFpbFjY#b=rx_5rt|drm`j}4(D4-FBq=$7ol-> z8mln*YPD?gg$0rNLZ#_w!K z>BSRFKTH5lKxN&v%HhBG@lb9% z2{)|IH`XtU@uh>D*^vNc5Gv}F9&>mvdG2(e7yl_;!n5-D200-4@8U!=dlZ=}9L*D{ zW+f|ph3%?+D8}ouk`nJ6Nu12UxZd=4G2VQKNE&sRnI#QH&`a{%S+@a2#^6(q(%FxE zVTv!6c!HwOPuRr*!<*>qVV0rPL)Ut^A_;$VnetV)i|VL8sgSD7Z$hPh5!)=^t7#J6?B-G60R_%UWL=)72b4qcp?C|CC%XsW{Wg2!=WFq zz0vvby+BIcLtd4k70v`!BH8&m#lnfe1FJ1m78!9BaDne?tS*DhK71AM5&Db+5J(K4tRp>+7fR3+n)hky%KNf!Jx$`ZQPL)xQ)&g9YmMa2>u~2hiCKiR zNH8a1+1$2-AVZ`rnA@(k5}wxPe&HV$0$TR2{FL2Ew@tv-Ml*>w9nhUxnyPqs`sEwl zLQ2C<3uVNSxR5NI4ZXVnC-sYYL4n zKya+0#iC;?-6zw(^PGgDsp2qjb#rCN3DOO^L{Sr^x}?7h~()% zYPG)`fu3grGd6cldP<8tcH&DV$_K2kZK0Hu!h|#OQic+TuJp(=CenFQ()M=3LE_v; zL);M2iAP;j`ed_sk`OsPOR`MW{r`IQ(rUzi6^9QG`V-KR3(dQsY31G;MlQ)by*2f^+PDuA>L`bpnW;AF0hnJ zi-eFOBlPtZ4r5jx{KU*MZI5b6U1g2L*6^~QHF$@8_d^Zd+0UTVQY*ztVcl6~tstU` zA1AwzibubZ>QIVa>2S=9+eR4cD!Kfv*m7IWM&}B$kMzsQB*KhM)tMaFcu!$RyZdod z7Rd!t{Tny;n38s@sos1fY{G_0b^vvf^mog?KHBBtStC&OO&Eq2KaeC^rHo@xLnn7Bs4 zcO7@ZB^JxB$V!V)aK%=O04=}^k>&9oBJJfp-WjUFL&GP;E5qVUKS)1q z6$>*icgQe1^x&a|f$wwhtGZFD@K|%LEncR@qP^_VUv5^TcJ)xv-CSGJjWJ7ysHd|z zl0w>&MJX^H)_c5TKQ6!JjKHxUc`1W(^3jH)a!NA4iIUn?9=F^y_XVa^Z!WGds5J6YLeW$P9bzkA~ zbFAO#12nWiN5Xx!Z-Fu1<`E4$^P~KbtJ%>BzyilI&DjbpZe@) zOH#&Z)QW#kLr&q{8U9mUeK&BaHfe8{U&qqR&U`joo?+U>@FVVm}kbD;5_LiZK) z+cs#&uK)(zX(&YXVY05WFn~E^8I1UjR4Y!xh;&f%ohkaMj&g znf{YHY3g{*6QpS<;47KgMYqUb?34tND53M*eNiDLLRoYwfqtQBgC?=3+P}#wKwt^% zrk^+gYx)J(et__AQYfZO;)|K+Nmc1o@%AByT51X8qrOD8+Pi~<+#wd#NR|K}Ry-$e zddb#S^rrVK(0rsMpklupT6IbV4A~)ORVs1Gn43osSMYKKPu2pLS~K?5pK+Y@1&Ix> zQM%$<{F-te##i=eAL$Ghi#f5n&2j)su=oD0h=Pt1mGP1GmE{f2=or1mr4tCE!dt13xU6bM!Nnf0tCp!jcL&-(AD=?aPtYWBEs zb))L=cAvX%K~za1C_>dm-P_UUdqxopzH+vTZ7NeUDYM3bEN)7)Z8I2qi5fK;4^yw# zH_9=fqZ+Cy@Bpjz0{-5{51_5#xql5vOL_Sh#w;-;l) zcZr$ghp0`a=!&%2E}^oe?2tttl6zq-QW0vO@uwUq{t*qehgx-)az{3a=b8JDyO<9yC~&#?mCvB9Gyw%|?M558hK5=mS<| zOgTj>@R)xGw3t++OnZ+%}^ z{grR0AxB)(uTx01JbUj_%D?!*B~NkxReFDa#fIMyNdTfv|UNZk7&Cq+g?FYG@QD~j zzogrp!g>tY0-Gqs=N1d``ovjZW{>=ak0ik$E)^g-;g*U`G@N4v({E;#e3on&IBO-z z{79-jBl7g{$DCs@Bx-vqA8A=nq;(;4cR-x1dYi_72|~C4o3MM2rW3X|f96bRK8H=-GS_Mj^(Wo)_VdMy_fV1IeR-&; z_5yn)!M#FX|8B30|AZ~{e=c*oHDLTAD3E*PWjm(p0J|#{!AJ=*L=kP!$)j^%W~-uX zophc4CFkyJnx&_2#%_Z8<0RHxS{8Jwudf`9qC(h5@WMwZgqo$NyVa>L0H<&NGN z%3dC}(q7!t5xxau!@r0i!!1&r!og3@L8mV~#)0(adjzW!rp-DwW$iuI`j@8~tU+`3 z!6$h7N2iK7E+QN)Cy$P8LW^uU%z2sJxsqRvbhKXKWIAjsjA{~EBEe*h=G#n_J(qW? z$Yy`_ADh?~SaO(_M~fdgoRW5+0{TLlzw5-L(9Gz!L z_fjN;y!7_u1pS9xT2q8G4DEd4bYLgg2ey~|i`soF*j*T@m zRn~VYHj9e?t>y^g2sw|QKw`lf{&QKx2A1aFV zE5SG2w4bll$Nq^~*Igl>^pmz7UcHnOSu|*l1ZboG|erG3*&1QtXkCFl+Ns)(L zF&+^g_E=0RvYQ6HmW*;W^2(Ou=07|$ZpmxME!%p<7?q5dM)-p!Ai=AN0Mf3Fg_2cF zlgQq)7P%fDc(v|6ynxRa7|Ul`Uj^~k&n zzh1&=>$lF|3~inRqKNOlEYtuY%Hh2LbhXyGHN-Xk&LjL9K(OcE6UiF!{t;CI%h7^1 zuh_2Nob5E+T{LV~Od$L8+W)AK zdiP_;)l26?_&@$%X&~JW@YbKaBWtmyzk;P#btLcxtVCxn>F4lJBCZYNo!2pvu;0~P zll~Lsu?}!AQL@p+AHUxnUIUI#{abybuY!m9UcnnX-w$+usE{Bt+}C&81;ZjKV{lmi zZdQ5&V-N}F)2hYeb_z_60T+kwFaO)Zz>it3h1j{WLS7s^H{jSg67PE-Zzt%Dyw0~i z)tyD3u-CN#8hhzK11RbtDbF3(^_Y8+m5Xh~@Ex8zs>`71%gtt(?KGA#Ky@$KKxahn^++H5eY+Ipz*jvqmXV3#lazS?LC^wSmLyDiDl;LV%3`s;|uPn_2Q zq#AkK2S}xe`zc@TJaEc&Q0|_cY<*&H-@FZC3;CYKxd^2(7TYIdsO01=s8b zwV@X)<4&9khuX8Gi>;N!Yeobq>_7!nr5ZFgn>B$F7(y1Bi~_yQK*PioceBzfu_4Sp zmOMHH;-U-8ZsaS+jhlCHhP zwL=Y%XV!q1$W;r7+z#}kq=kkEV_zVasnb0eyS1@Z4e=S!T!;y`VE;4?%;Hp<m~t!4cp%BKQ?Ja>D8fPx9&m z>Yh~}(Sf7H!x#@e#K-n;eVY?N%9M&R8tGFebnySEI5HpmZN*WIp15X*fq3@OInccQ z5FPPZKnq(mxj>_AvOEuCREG=AccRc*9>t)V7>Jk7xI@FK8S-$jQ8D%huDBWDz?OGm z%&-lfxK$4jfX&(#O*%W86*QVJbL|TLEx?Z1Rm~GA)-h#WcrFto_%k$DM{dcik4L>; z_3p#d4+!Lj!XaXRo)m7|B(SluTvdxoUfPj(Awk;cU2&z*TKWnRl3F6Z@%zVIB<_z; zIcGI4j3UsfFPz~W9OUzWh`45~&-d}sO#Jh9?KJpz$DJD=7A!zie^^Qel8NQqtc;ll zwz=+_gQUQ@QW^|+pus~7615_tS~}WKz*&ZgFAPzKRvbGIkFR7YIz?0DW>&26mU4Qr z3d84mZHk>b1{Z);11#d6OH~7D6GdFme}+vf-@>f`{-6e^-VzpyVfM|J*p&0M`H@H_ z({ag<3Up%T-c!ny8FfHb0=x-PxsaH$pGULe%f3rx8xAy9ritjw?dmt8Rn>%EMmf(~ z99AWud$5EsW5iO&yoJ_6BLlyu2Iz}V3+A75Za8E#4hoJ5$H`R8w&fM^JB#Rm7L_e< z&!SIcH&UCmFkCg7MO8Mp+KF6)R%9q%;y{iwdC@T7`UwO|7mF4Q)JQ8&%CoQ}`U|#bKDt(_aH+8cEI-6pC_u!-Av^c9e_a>YMKRm8;>7o0j(rmvWf4LMW z_gsB{4dr;*pe7;RUn-;AtsnQ28=Y504_y=(blnKER7~WB0H-C;kV8TJ>1}o%)elQ1 zDUJVImQ13*W65+3`+qYwPADn?na4YgJLh>OoR#XNfWcAgDqnk ztK|l=sv$}uK;$Lj6&&`tWqjo{e$KPbT^SYL8L)?12i69R4BC!}#%_t_q*NJX>jt{c z!!VwB0)01Nf@PL^-6dB<&hl7aWpmlmkXl4&azJ#c8d%R5w=6sclx=KLF|a3;`5+-g$mTwQCNatt@GxSNkC_FPQPX2W@4dWA2Li0` zOng5AVe=!?;oq84ced8QV}jy?+sICquYhSYNZj~?71bkc`dX7ih&R)OFhLfQ+1Lvz z!9_G0GnJfBUq+VVm8jVKJ0N@q&TEFx`+vAIy?0=BArd!!H=|Sr70!^~w@zzZA{)oJ z$Dxc3^aMVL(xd+odYwi*xjmL=blkX5BoR^6X#$rdUVp~;-lq;(yH|Z!ja{BQ|EaLV0kA14LttF=!ONSM0-L(oM=| z%1R?Xwh+U@7%`6qj=tyuuYJcpRiGowx!R@(b!bg1T}OrCcrz*Mm+mfcr;wfodK*Ni7TV0hyS z5v_@PUMTKA_jtFP?B5-lga@Rk5oHR(cNt_g`qiCD^NP^8IbfK4i}cIu zwXWGe&)2@$Ndury8`Xb$G&M;-Z4vPub|A^C@Ml4E3<}E;lrGet3awqH7c#3WXcO-? z`&MkE(M%5HU;}v&VWk7;VYLpxs}L1e*o)OL!z@5}V;sI=%v9>BBILBwN-`!T15vq< zOoMO9x$}hPRY4B(gn8G>a^Q8DLoE?jusoOY>?RZM81}(r(MH3xH~`sJq7_%dz`B9) z55C#~N1nc5?8Qy3{5Unv?)-llhrise0?zy2wMBD%Oi~edMh%Gi8va-~$6|K}*1x&E z@Mol#RS(cckU<-FppK8-x^O)PY+uc5z5{-*W?u5h7SfNrScplMkZo5(;m%A(qJh&U z%fSjAmH*Lp=jb{}Ak6m@)Lhv*#Vug;sA~w=)`khfbB#Q2Q z2ruYEgs6c54)IvZvn>2Dbtb!kA^3uP!YK!yqh$~NXN9(aWe7y;^m|iyNWk3hg)OK6 z3Z{d&fA+ln|Bpcx(3AdUJPcRA0Z3}j8X%!+&n%>VTz@7%zyLd0q*$S z-8UdNgN*(~G$6zxC4|^A5xhQ|rTBi)57-7j!{CFYvvVl?yJhg#UQ9@)91!k4-A)aJ zpWH{u@#*p3<;OwR)(sR04FLWH`|hazorAawccNZlC^D|H5=J3PJ;!PhjZ|9lPxbCkgulUu3UVk zh3<5}6!R-!%}#cvxNWXQ&{54R+h4~X1F|=`g?6S=)~1rg$u7jT4`E_NI499cH9}{% zfLH&dB@X|P3XlAP`?9=YOH{`Ruw-)5eORppT*~huvGTxf)H=zzam41uo3qLv?>zbo zYsnQjxJ4@;_L=DF&_LL$?4Ea6#go&%Fj>$YXCKe=4f4l)yh12Vdz-+UpC6QLLVSMz zG*#kMC^-j20@VNd{(50cW0gayno~=BY~fe_o}Vxfvt9VgTHGAAgm=9|I;$c zNmrXHK4e<+V8MekrI70^Z29k=QIIWmCBV*~97wwBP!puSpcv0s;`8-mC!JIEqAT4` zr(4xz))001ILU4fql&egsX2#GX#iAcR}xg07GB}wm=(f zt&$--=2Y-SovF|fqwy6k9fD?VCzqiJVXRg+QejxkGfVZw2ORDeCHoTzGNRma*f#Z# z^~&p`sFl{O-ZkaLh(>ViG6ph)NF+yLgaTC2gHc-`uFX{Z{SiYMM}fYNmzvw*&y2eV zAvX_xI?niMIKGE=G#QrGZW@9LmE)5a%>ELh+@q7C_CoA=CA=$sM7P|V;C;39-{=j zo8*?x{m$-Fju!0+tOb(9fugw>@mv*!V67{5Av}vW_#G3tJOz0FCDx8j_+9|@^``*p zHe{!&tzjXNY|gexmU1WBpowGlkC0T+**^rlHNW$_e}Cw)q&(;R5yAVp-5*WQeWnDm z6NSXzJR$`lJc5EHP8Ci8lfWqE2yW^v=4y9}B6Pt(hgo~b04K&}3(eFFJ+jtGjp>99 z#*xy+ol3ir3PD>SPo7(J?fdtb+tRNb%+~HlZC>t&O>=jieYoZ$XQjqtmptRIx3$d2 zV^W!yV^az)vf`XWFnemYp=g0Unz`|fbzXrkF6Hg9D}(~p*_^ww|N!hXFgc(9CybbU|mk>>MWSj53_ ze^Od;NticOPuTspdurS_JoGp-o>|mokqa@*+0+mo4v}dUfd`xSphWrt@c_do8NN~~ z%9@2FJHI+SD-1tg9HaHAB)cj6caq2k`F4$*f+~qjdml8Bmb@OCHZ{GuzKTG#kk5I>mQ}uR z&dM2+cBcrxEJHIE4@^f$Y#T(<7q*qX)+*9`YBXauvZ=GklQIq)y$LJZqTJ$W7UoUZ z(l_{mDH6eBuEi1;dXcrD`-aCSjidK%DEIQF1sqwF-A5z115YU+Oa#vjRQtSRiC5AB z3h!0ekpFufYilvzaVl<1iq=O9ezMdC4I3n(hN8M(v&X*Fh|MhIPSE33a6FOJ1b;M> zVmJRIU)ycN`c1WY?;o*@o%^DTJRu&U6F83LGi`DL(M~uLCVH1xZc)wZOzGtHO-tH+ zbsOX>@u_Bv=(~tSP)U}K%dAz@e(yIdq(%l1q@>v|aw(fIvE|A)!f%;s4L7J{q+jPf z4;wXA+-LSLn9DLvo`B6m#Z)$nFtG9c3rW%2M0g^^qGt=%>%{dAC~UB9NF6fM zSX5aYLIz&gyu<|1L)+O*B+nG=QOmK7PdxK`1GuHx#uoT!R`OjKWTyQ}WP_cn@lC(Z z+*SSfoUiDQMKV}a&wuA$qukW(6Y8^VPW zkIQe}Ep~3}RC?vb`@GcnT2VNI2C+u}LCVL;kNrZfD}VUqTP#Gg(d%+4jTFo8LX*g( zBWBC7IydE5lA{OT?I!08Z&z_Bn&ROVOpOwK~+KE8oc|GdxcZY8It=tiNbtLxIUSZi$SloxGTSXi*Vt}JN#tBFIQ zBTV+2Go%wNqY=E$9eB{i1j$I17i}wc3{+0gHTHuW6)tLSLoXEjmLpWssOZ}8vOjza zFfzbJ+nJOvbl0b5W*Q!e0>=nUv+$oU-#VccdYW)`qVU=e(luyd-SkT9DT{J@E_maX+q=UNqP-8ZsD$D#L)o` z%CVd*`EPwnMh5XoBn9N&`KF!+P1lE%^0%2#rK_BFOO|2Z^;T$2Z98Iamiwel_YRSv zJJ?xBK4x-61b|j>z)4Z+M&YT+kCO8*t9FhWasscEL~jx>R~M{_VjsCr%&M)CfhWg1Bk7rJ9M}(2}o0 zGlJt|%|%_wYkO1KhIflltc&Ww+8GrE=XnXUT8>9^PQ@#-S910hGdBXOZo8ojV0|Y3 zh*zqzQDO1!lilOwW)hsxcr?53SrJ(ZI0|SE85RuF!|eToV9ero|0sFP7GdN5wDdfu zKjLeaL|U(o*Fr;0{bLI(3)~)=9`fiPAGrKo;xsibd0OKFcaRSBOq7BD<1^xEM81## zl=P7up?*K{!s{G_*O@g=g?=H78U>;%jl6Ct&-i=~Mv`e9QH^xD^J^&N;UT=o$nl^T1C{snl&(oY4_fu ze0-Gav1iDtn$}X62K?K#6AdeWWW(yOJJS~0$7<2Si0e&$vw3821FbzmXG(fWOI`OZUEA?coGWdAvH0kq`K$_QVT({|f_DV=o=v??J z7tmf=(_(PimETDWnTh!zp?@kc5U6)W(1eGEW~uU9-Nka<_cSG>KJ7#=>6YspkP#1q zH4mSlCLb|-=+vxx;C5a(sdTQsJ%;wCGO-Tbq%g06I)1!oH0ZgJe~*lJ5p2dLsXK4p zAp7_@*SU-Uw1-y?IE&^XO8HhP!{e@6k@XO?sg+V$Rc1ZS_`^T6G%X%G7TTCQR*IeR z<-m`EPk4F{rSk?E z?+mW3@`SD`egm`B*7%?2slK;huY1XCkVi*6+9vrGam}sIP4;XAcwD~Z?9E4O3kqdX z$jVLKIHL7Pz=}zq#`q|8U&k1vnUM*gj!J+tl-^53_K;heR!5EPw*h%%lA3On>8|WK zJqX6M*9g+ioi#7oFLpnFeC>(&E$G47HjxpErKNfJO_&wY8CsBfw?KVC!ZHi-M^h`< z6AZtT98yPAvBq9*e1#xB^ZoqAd{de3(OS?9fYkJG`jUXU0cS1-iEl9I*0WEt*>3pZ zx@1u)l1+b)qp*Lkp{n*Jx-Q05t>b10TA{F1emKq?6lBQ&<6Bw#OualsN@N^RIXds~ z4cz@$ri{dxD)IR#OAL_9<_?p(Nuz73x91i7nI!H3Qp`&v=aO{7xZKQ|ubb8u_#)Ot z*Hk!*3lDFZC5NX<%_0n+p0U)Wdp*_Jcx35-6$d*vud)?Hd?v$GYS z7GnJ=#Hy9?hHRQi>=dk#U7HtZBg8UjQ?r{9CkNg14b=%9OWrJfBT!|PPA8+M=AFk| zn1>1f1MCvE0bqB?xWr5M>u0lW1kiPKn!K6wP7YILWl*`prW zrsD}C)TS@q&NAH?vsK*0|p!%q;S_W7m z_>-SdXzI=3XwRJ?UywxGftzCSGz>#GyCc8aoafUXQ9EosD_^S zU9zdf{!{GF91+eF!zs*E7913p@la9_ZC2G6^;-Q|G`3Y3{Z9;)bf>(NvA8<-eIx;^ zsM3;kqX!joq!tDTlD>tO;q%>;CiyMejiHD!e$ep#GBIV-@otTaGP;daEClH*sK;M7 zQmnUekP1MfGq<%g2{w7MWS{b%qG1s@ne-U4!n@xFhW!G*K$0GA`gEMQJ2K5Pz%42U zJ%5DoVzYQFg>04Z<)#_c?SL*zkIy@|=Pa@;;|PH%kFFTE2BHtFRJytM9xNeR#H=ss zX-f2?+L>nXW%YRYu&+z5_*FL8hHt!5$A-`0TeyUuu>p7fz`kz-(s5zydY+79T-d=0 zTAF5R8OKPNVik5vj@Arwe)1d&aH^kZKiyA0%&#{Os9j1LZL_eaj;+jKU3Cj^6-o=jVp~0_Z$;<3EpCXu%kbZ%ac`#IxTp_TJ#+5aeUpX=Q zJd$l(Z_VaZG>yjb7?t=N5#B%!=3ginrvHDSVBEV&u!@>qb60}{8UH1fEQ$u6^E^x8 z64U2ca`JXX;FBE~w6Jn?iTS1=0+z*5Nf(N=PAdVwB4o;t8U+MXGkz`w zkkPd?MXle{EDlZHluNYtw(Fof>O>eQr-8_brfB7#hUAer&~V=#g!g(~HmYV-%R^R#+i&H}d?fgqGUf4uy%zSrZ}LDgO$D zb-~OsODoh?0kR6xyA3MT`N@$>`onjAYj8NhPKj>B zhX`bHg9hWTt*uImh(|VrZ1RpkG!7_7Mr5rqvV;!!Qw&8@k;y6A2MTONH6F=+D6%FP z$6B`MV(EEW(=%JxeeX6fJkN|ikIAh0t7P-hxbRPRgTy#H@$zf=mjtw_*S{2`NCW)@ zHD4t9F{vXzPFwUfSvFt8qrSvCEukL8g6ibu0K=j#Ixh9aLx=8V8rE;I+Q((au}>0T zKzfU<*aFn|2QcX5`-+CJc=R_$>tdrPnSYppQ>Q0J2mBol6XKhm^)-QJ3g(HpI+H1A zMU3cZ&ql3quWAvPMP;p>2IG(MpZ_)lPD@uoV0;STUNx}<+B>H3JTH7blixs(Lemf8 zk(rHZp-QE!nzn{|zmrV3xRL#YzaTgvShaf~eE%ie96;M+IyWiRJz>atw6&*BZ++Hq zlA~3vY256bZWMv-8OyA7ge_K+>yb|;ZYC#4F>;@sUo7IWXP~Qz6Yy?MZ zX+u$?Bu+as6qDrb{`8i$@Q1TcQMo&tH{&&LbgElkmjMn^dE2};0HDPNf~3`_Haxk8 zxpi@vXM@bIgZ}A+J*LKyAW;B=SO^tE_ z$s6EYS-Md^S&G^$=vB|$mgZZ_t3-ZyNtu=Dn~E2G=)YvUOkkNQio`P(X%_F(q5M3r zM4sJel;nWHq~6(GzLb}QhHeUg9f=3s^xu(3jZFRfyxB-wGOG<(RaY8+K;;NUOyI`J z*<=PPKU8Y?(SPO2v!zMmfSMf*J_neLaY(5`U}Niem};#}B&?cl+a1C}>d6W?%fMCN z{*`6qnpWWTVdZ7C$?*f7aD4tTa`CiYFL~y=`p43brWu!6q-D6|op$M(no#s*OKcJy zHjIuf_ymeb1`#m#MS!cao;Er|vN5@EiTV}iNs&P}ZIKaGu7g#wm{p7AQVQuQ8_8>! zg(Ozhee#j$+0c5vPVU#W&ArRrQWi-elgk-^d;Badv{%U~#=dCZg6@&Z%e*Bh-9s!> zo!mxWlB0yE(J6$F2C`0qhJ$A1&9-HSKtsPUBa;&2nU|`?O4OfJ#F&&JR!LLvJV7`c zl&`=}WWersFc+`B z2@4M;%i|rV@k^GYsd ztrR|4&&=TfSM+n1NMz|Dn4XYKEc1-LF&a!u$MOm^-HTV@mb;7J&c$#-Vy6(VsT~8J zzLl3#%!@LX@1K}9pia%u9m1gPJ2$8!JBAqPr@uc;BEG-Bp{1a>qoIpFWiLDkh+N^S zZse9)GwjRVsqMX(dNH})M}y6I0Yy8EQl9(POVm&5Ey6XQ+_=U~LL>l$UCc)51{Fs| zV4Y(;*|1FTc|CCrTk>d1G#gfhW?BuXQNTHJ-|?A@Em4V%Wr%24Krxc(ic}(r<=u#n zxqCDVrhJ8jBu!Fc7IlJyp7ml8L+y_WjgT&8kMrSQ;;ex7sZeVCym$R12W3cPP^^m7 zkh@u=R!TQF6~R(9Iz;<|wXdoWKT1ET>GpSv0wyeU5f~4#hDj9NOgBV;+V>=!I%9jnU?jU|a%aGBhoNwzFjfoO7xnGCeK()qnWRPP@!N|V=d)ZcmZbU!|^rXNC6oQ#6b)HsO$_vVJWT)MpJV3ma90b*w`B0GD0nO;0ej>Vx$>bigMW0=X=0nYuS6C>4@!uSB)r0Uq>FtR{v%`<})B~uJ z7~8oakwh4tzEr!qELUQA#Jv2`#NG3`$};jrW*Q-ma($>Ic>r8$O|evWMVye&E&G-9 zVP$7g2=0F208E)3K9#X0#X9W9sVy#B)bxgf+fQg(1mhvtT)KDQfC;|n&!*>Hy2c8) zVX8GcW|w{2b;bq;asv$l(>f`>U3>Us6u-Co8*lOHqGUrj?GaM4#T(uOilv-hmk{md z5$=phLzKdRP3sIl``4=o*2~6+P;5RjI13}L#IhXL(Ay< z;Uldn(1Pkl$FJ=eUPv-hCo7y<=vW3t!!m2-@|o18#O+#&&LhG-L+@66YFa!9 zUWWf(+0Tw8knz$4;2=V-%X2v1f7IGXg`9L`OB7)6O^z_Rw2uTuwiOGN6pLs~YKh+z zL9#0hKRB(_C{G`aREx-_?k3w(WX?TnRe+jr)$6iI{ElIK@Z683@8`1;wP1jZAc4kl z36XLp2a%}7ad|}wDP5z%LBuVlhn#->3QTIw6HdR9Q*GO>SvT_I-ttEXtEiD0C=x*| zf?(%auMT?FgysX8#;u)O8=bjFnlW%#k6B^zysVym*^}rEs0rAekW1`WuqQ1lBIV*A z$JlokO4GBhmPf1x>L2N5X4y8c4aS@*lX!B)5h1^esA{f=s;KG0bWx!5I`X3T zK&fZqyC%<$h>p*+Ds^wkH9-dEZb;5GK=cX)GC~OYdpGf#=f83#dp}@>T`DPJqhsA-}j_Bp}kGW={T$K>g_8*@zKR8ZB3oyT0O0Ry(WUgy^ z>9EA+0m!vTj>QgZ_}eYJ6;9lRn3<4*znW0av@ynEB^hoH6iH;7CKI?Hd2%Bt@w4E) z0?cN&#avva+Hm|{PHLy|8brTGywI8{CgEzH^$91{L>2r zaun}OVAYa6eoe{sc$+yoQU0Z85T1T8cNyX}VBPo(6zU(piu8}~^Ar$gJthMER(k=o zC#NC5^G$dEka`Q)^(_%0cv_o?_6geJBl#G2QBU|VHqhbc;LzNC6H-1dj z?+?HsdZ+dqH3%EOJ@)zz%eyQ1)TI+D;2IS;wtw<5Ah;?^S?MYanC_5HPi>NB>0jgpF6}_qXr3x9{lOgm`@} z6yr5Rrki*$htzo0u38DBZMRkuaBa)^)zi53OKyVhA6s+oZAYdBJgzR4SmqV`?9126 zj|>_)9d?IeB33;s*pb)+k#yG`zcm>(sz?5v3p$VF5#~cco0h>K+S~_cpsN5~$b#nT zRa1j#`juU!UN>z*+Us`@MJPPUQ_#Di3sS|#dGv^$W|wvCz8n>I7d2$9A*w@^w?$eI zO`T@#rh(EeZc=`39-K0g$Sp0=PWIfS4QsGHsM?^0g{HK!K84lQ4sT|k zm9q1lH6nfc*xksU<^aVXSXgtRsbcZiyu!>$_k3eL-%=+WW(cW`j9pdlYnrV4mSUpG zHah}Z1u?hureqs&DZ}K4n6IUTQZ77a9^@J(WG3Ms&wG1}c^ygBi5x}OaYC&6)pgn+ zA1^OCFH(^xqv+%<7rK1vQ?{|#1MJUm%+QaP{WS9$ zvuPJz1H5Oc<&I>nX56fWwN>e%(zU=IcE#p%g8kQ`idpD;mfe%7`|-edv&AmhHiy0a z(0zfIEhSQY8E+|8T1{8=$Xfim{P99u$<{JIFczquKYkOoBYT^+%c3b>;dO!&5?mt}#mLsnu|Bkk+b&=jHOCCZvGr$>O^^nK>9 z-qhH+el;@D=qC>}@~t!_68q^KZkDoi;_RaXhaQ)x`5aNea%(6n>wE~KTR{EA!i-Iw znEE=XO*}+)=M}%US{f}`8yyAPf86ub3L#WYw03cC$Dv>W6FXFu6I=I8 z-kfTnf+(;{o=s%BT6 z?@;2XcGqGcK+2k>VD32_tIkKt>35sRY!vnfHr}noZAGV?U=rqg!mx@e&-w;=bKdBT zg-EKAd-v<@t8RpQ>c_c7EJy}medteK8eWgdP^^4dQ!9m4f!0KETrJ9vM90QVwZk8E zeE@Dy?&r$IFYiNZrapArQ*+qR@!msvj{RzaM{D*c0Yp$>W%PWKP8jL|q~|<xK7$$Bj)Jdltrk2789Uhf_Tb00< zFBA2Wu#Bh#2N2-N(J}B;5qRmj27E!a;@*P%a08LQDq>OO4oaRT|coELhK# zZ2QuoB5F{}pAow4Rvx}*H+BH(mwPT9Qg+0kRQ;vJeXwgfjpF;%HjfC!&{V07iF~Gr zvCv}KkeSgBp$=6E5?a}3$#B;+v}}9ol5l9CZGH50UDjQPR(g2#rCR$PJ8;6xQdFxn749y-_q)47TT0xqVHjQw%C{#4Wl>RX6ayDw--c? z{QItQo7OzpnMve&s^J`zpNFKuLlbkxK9bI4;ebOP45D zyfkRz^&49@kGFGg1)s7M;dZ_G2K1H?d6U6-?ff|sSYd1f0FCVH1A`s4O$!)?rax3k zHAIIhz#XnTOVAr5EtVAq#HsbsG+aH)3SnR`H5gvZrr3BJP^!M8a>_jo;PA@&o>8Al;~rWqE{JGs00m|!f6yqSgm(xs z%F}RYh8`Z&y_O?G#Oc!vHk6=Kn0t5>0~%VlPwsdUcG6!QkAy`g{!!I}U#%Dw z&sn*==GD$t*4?HFqi2JA?EE8uZ|pLpAb{`ayhyFKx1ED3=?273n_EL-&xb?1VTm2% zGkCMDS~T}udUfR&ld)Abo7JHh&f+NUU?^Kuo(OO*N>6XAA29o@7TV_>c|44+T@nYO zF3E%F6#J7||SRZ1jJ(Q);{Td4u#Qbv_ni|dX(0`Poms>remJ_Ddz?8~S%EH8$ z1m{KDWU5(1O>N&~79EU%eW=)}{u!UKne>lyS3TLkCArctdVn(okIEF{ETtDywVTrV zE_a8;{B`p=x11Uc|?uf0@b)_ak#Y+o%x!3=ub(pM7^@ zP#dpj&K{2oSg3lFcA6h!1-Io0g`8YXcUj&^o~wY1;gLYN%ktC+AT4Gk=fAHXAHnByiXioW>0zC2|g2R>xgi()KB|t2e6{}zhbZk;W$w#DG=sSYV>Zz0P zA&z>MRO;jZyyWUFt$nbS+LRueDNC zS_zujiKkQ-X`?BSgi?sOVqukR>`T~wo`mZ8$f%$C85RQ+q>@8L2RHgm`)=epaF{a?SZ$?CMgl#bDLG1|E)dU@xqw8U3v{`#l|#*=shg&gPqN z?lDd$i^gZSpKDP8Z>cG;Qxwy-XYIap+xlvS*(LLXD#U-FQCRtK@LmO_yAFC(G<$p| z;Qw@L#`||klOxcK{7LT+H5=vpCp_Nef4_+SPuQ3Mf`g@>-gy@b!FMREZy-SygYubVc(AygjD) z1#eeA)V!r#4|<{_LM{wiAy6KG2{xI)LDxQ?q3J%HTneov~C)9~gCP?Wp%$FSTP&1yOeCF`1O4;qPBMFX?=|`5&u|A6+!^F(IH0yyy z=IECn)u(pLqTJ1wnotg<=t~yxQS~hg77wK{`Eo~tyi66V2X%B)ldxlcB3%hm>Pa!Q zL5X}sb_&AJ*x%Hg`!O=%X%2>N5!Fot2aiw|5VN|4p;%1;jL*72)&)^KIWw;2l&Zxn zwZs%Sed=Wao0wOuGap~7PB7N88bIaAA(#y;5UXzvG+jBFdeOf5KMS5J!v2@w>G}mb zT!^}+cfcZzCLEMulL5GA7T*ve-J#>+^-VFTC}6qVzEsabqnz`xe7vmKp;<>b0^+Zy zl>b^=U|E|oYpd_}A(M$ZnVbZLZ48gW4;Wet4v>^A1=6E;8)lTPgA1R&n;!l6h)Wvi(|E{xcVY&J*1;T9YH5%cYdS$;T^x@v{G+b9#BPu&q+KwqhhWLzleQo|b1d zj`o8Gn~$v~P#~pP@Q=K~6{hioPvn+rkkOyAU(|bTnjp^Z#|6w%g@L>OdnJeg)pF(h zhw5w9;p}W()1aw%{8D2!83(oXJM_e(HvkY`1@Lj_wR@x7lS1~Vj4>wW*DjmZ4v&ip z#79tIsxbusj)f_G+vyK$LAW)Hq7;s9y4BBbB@0_;l$Kb^BpQ=c-nt=^bqg%;FbT%1 zo>S7W+@jENt$^(GIx?y@I2ve?%VUT3DWQWy>(;z!AIMz;dNHp0@Yhif<4gu;fa+zU z6(SVLvdHu;+QU*(j*;PMTjrL`zUe^G`Fdj-8Ah)Gf57ax#F zwLP$ng)SRYv%2j4RJTmJkOQiv!D~p9pS(e4b^jeMC$z2yp%=DOompxYpX5$yTAt|K zH=AV8!IsoGpo==52xSD2F+_=nU%QiDm6b-P_FOam@6soLdl{vPL6On%FI<0;2=KJ1 z1@lqjSqEZh-08DTi%!8vqUBd)Z^&^zxV3y%GGScJ#-y_^Nd#Sx-OEiV$6d$l+pk+X zuKnp>uVZ?yV$V9Cl~>`6uYG5Txd4@!ke%^6NxDCILn1}mrD#2V|^F$k(?5LP5gjHU#N{(au#@eerRd}RdX_32o=2cwzesz1b1 zPJ^n1jtFV;#$dHwqbK4i##Ir2xaEw+HGhMTx{gHJoP69SzCw#7hq|HAT`HpWUy zwk{FjG$7J@q@L?2xeygP*mMK-DLQDBR>=>d@R3seg6cI2-fz_Jcw(94A{i6dt{BQ9 zU(Z5|y~;zAn@%>xlJyk(@LuLaX{dy)NK(DljV=qQt&&>M2tc*~o%0VBI`g-bhnoSn7_sC&v>{7}kqpP}J+FAv*&H-LMWCcH{FC zW1@=*+7hUXGB1~klpEj#rZGKxFB}vlr&aZSKC`%OgSEccHYtjMh7=0LmiF9q9{!y; zX*gCuwt_<^$14o_5*f&|IVDB!u}MPU`u%#%3o@_+a5rhUp1aJ;=av!{X5t459*$M% zhf>dE%2{#=%*Ej`8B#t1w_W6+1%V=)VYSyX0ap+v3MF5tw-mu&dwJpSNc-)-BW-u{ z9xqoUU7@Jn?oYkly)5dRO;_A}G^HQfWRU(0qIae5Trv&y0=OSB1rcZY3T+j0YMRIq`SMD z9cM1g=O5>J?)lvF;+%Wm!mc&fTyu^w=I{3%-WG4J;AU1VG*W45$=FLIV$0e^J%2U1 z+z56)yptO%3F>FXZE&TUCy~dhtDs{MTED$g?~q=0&PBIy(n}Yn*VCmhSQ{N)EAG1a z`=|tIu{~(l?teH;L^fr2=~CpsoReHbsBA5#L^IIG*I?6QqlA%@F;C#MI3 zsNNZ;;U{ec20-PsrT-NLR!}_bKB!jhhqgat0=7MAK{o{gkn&t(Pudi+RqlP&OKu!8}LMEGL>uxmJW5E)7 z!J-PjSk)2WMtH-Q&dD%BKe%<7lGU`-BCfh(Oxo?%m8|7j;+*LGQvzGEs28@7(8|M% z!BVxBtf2-11`dxio`vv*_6GV_-+$ z6yICz^TOCO`PPOfsGQ`22p#)}L*euKjHMV|4^kN`^8Blzi}>)hp4SAJEJ@ED>PJ7b zBzJ)k5Ccg|w6es@agC21+hunhV++L^UBYgfl%2C8RH6Bn8OKS1$m!C`pty^1fADHy z3UO?Lpm*>6VoK#Ps>kipysd>PkQ6teWJUW$Q!3_n}nRI>$zS4b{;+gia$_i)8! zbyXub7TlV-V&N+x_Zdnh`WM;1-Z)_VQb+yh*~;2IXX-F|p~*#Eu4AE9?_A8DAjZtP zPf%esmx{dVQ=7XK*^WK?k#GNj(29Pd#xJs)OE@|{@%4I-Rduv6w&e=XWl18LT@!;) zf%`YE3<>$?-~F@R?9~ zTy?N)UF!)fp5H!i6B8E{ z&F~%(dw01W6l7ttTZgu~ z*N^S>PVXHKW&5UWZ^JimQS^;V_g%g>7G?HE4okgm{;^SVcl6D-7skYA*@TrC|Ik^n zW4$l5!d*UZogjmchs&H~sbCIcW{@N?@&Azm1d5EszOLz%Fv{0T1S*t;#P=)iK6o|pGxE>>dcKGo z`_$O|-7r#wnMdQv*k8f_-Jk&)V0mCQA|Vhsb#s`m+b~58NO-M^*#o#FaGNbj2ntiR zN+|}&@7^}qx+$J4NU4H#ul!C5TY?f?aJI4ZO5Bw!_4>x+v9{}LzBb40Py|Y6INR+^ z?6P^e16Jmh#?l_!A@7ZyE4laH4q>mv3O=Lz_)C2C^#gO+Cnqc`!qRE}^&V`BM}fLK zqlbp6Ps772=p`Ii45vtgE+fXC-I-Xb^GQ3=bC=Q$H$_*--Elt(6D|Qst~kv6CJ17O zsXM*WFa3+;Y9(hj+&lAbW;($jIbkgB@Mc{rV_O6xHLjQHLfsPs-=n8Jo?++tZp#%a z+%~+G`MpjaDe5bH$?Adx#xq%Wr6rS%b*eYx$>n1GN%&FbMDeZ)u3C&dJc380T~rd0 zoJ#k~gLLlwIAkD?ryp33u(&n^vwa?~Uh=J|1KSAu;PRQe$fr3*OWo6@D%pvRM_5{& zbd_-t{x1jEXFto3-`VcUYqp7vw_laZy;;^D8D!TVa)m(4@e!T(nHf&D0rD5?qvfDD z4y+LuoZiz@WY`3L)+c0@Hx+P-SYfJ%KPY3k%zqHYi*G3#Wykw{@Quy?^qi#R#_(oO z>%7x``_PUB=z-=xF@JsYD0t-XnqGaR5GE`@gyPYRTl@Scq@xl&SD?m? zq4&)*`q#r=FZhuIz}@2}l2Ob}@xnt`1IMu6A!KQgl{V-iKIv|2AQ z_gLSfGuQ6L7l1kLhnqZ};$dt;_wkpF_=4G+da#WdiDicOo=X{0_I4_1PVwH&uIrkO zn9+6;eeN6AKOlDW)8}*Jd4k#M{czeJUSiL9eeut$L@O?M6RLyP02j;E+M`hAR^gX> z^$hIPO{(X6wE2H)u0Z;ter|x&ITrg_JC0VdJ5kL4RC6VGAidvIPbL_Po8X|L*Gvbb zIOdKIgrAo(;qwKq-N{;yAb#bane^byaJR+O! zh2`q-;5;cGEB}B#O9zG6*&9QP<&WPgkT9ja7*oD?j;Bb85IU%4f7q;u;8Dz+dWGaJ zgv`fOhcZvfOV~=}+i85WUrjKaFajqVw$7ZE_QZ#8K*k1qEZ!=TsWc@Ql?>el7NpsV}C*kgBbZGWeN1blzQrtjbs? zeLkyRz!T6;W=irhE`KdBjp*XB64zk7R$Om0@q4^^_^SJ9AIoB;Gqc*0%<)Xdllr#Q z8R!$q@b1%$;;S!SD<<02{B#s^A@!Cf_gnU>2@G^p!16)q`UKhK7(SWv_58&LX7MCz zhWFi%eabb&*XHsq?~10}F|jM;I+LlYDR0$KD4bquXu`WE^97Vh(i%aYY?XlR^ul7- z`1W6PSH(mGRY$V_qeWzT)?!GtR47tY<5F#UnG9S_ zuj%uV^3igzb)3!L6y=YzfvB-3pZGt$!sQ#90Qp4d^VGSIswOa@1Z;y#VV2o z?RDzY-&q^lraOPKb zznm_8%!OoXzr=v^m;RJ;`R8{2AN?44rD(Er6wAI zhF|haN#lc)y%^N{w>-<1YTpypx8+l7>w#r z@i(*G9WeR*ecoh2^7#2wThX6{wmxs^xU?u%FFS}HcEx#a$LVg-a=7f^*X~zZ9W|{X zYV%xX@I8@b=nvn}n@2kP95s8O;&Jd;7xg~xV73pD1v}c(-3?F25Aum-prCqGVBRLU zR)r}vezL)X>-ROB40aoy8a?bSLw(aleoI$e&s6kS#7B+ed#+=`Y&ysiq=Y63sgL?U zdG7ncoXhc@JG`ju5&IiJNl4p(d|=`F)M2$^?%_dD-@}9U+Py(*bd7q|A!aRNGfHH3 zGONvVFe2DO294OF}NPE}KsaMX5sGFLXH=ziU&U~2TT ziRe*eS(On%PGcITMzCA`K_7C7+Icrn=Kx(blK?|Z1Xs@nD7x(rTugNAJ;3LmD!@mFZt3oC#$9vnioy@7oD)U(q1WAN_uTrl!O5}i z%qJM)6PWUQwcDv29xIP^kLth>L)Gu~Tg%~}gjb&&D?K}dJ-3HyR))@YPHr#GcP3M# z*8O5iZWlwB?Qv8FJK{igaU{6R#{78`{e|Z?f?)_n^tNIx8o7q1Mo+;IwI|UlwMal9 zY+FMdz|jZjOfOeRLenoliOl0rBg@l8 z)(Z1H7N)3RbsgnjIZ%@vo||ECMCBRi8mud3BGZLtm>Y^#)+xs}C~FVmYMbAz9}mj5 z{FwK;rL`Ny&f~o5Ui(E(`shRUu}yZqG54)cqNoknmNg0*rC3IhjThGLQxxcFMbyW( ze8l$RiZx7-bWBjCOjlY21q9y`X{@H0@$Hn#Z z-B|&zk_x6mcKC@%mQ#qdOQ`?Z-1m#`&?++vw?V`$TPtnO?@@{zH6vtd}K4+eR--K@y>d`zCqEY zKT2pLDOhOjw(fQ+{>_!w=_2#ndGA3x+09}TeDcNa*rU7zlN zrF$0oCTpRSnMXBy)oakhohC zeOcu>%!-VjB7lI5trhpCZZ*TxURO2?PmPt|J=zj@iSs|kN_1sjCywSqeGHMg-N zk?kb>fk+egr83VYXver+Dc6D)^LO#|CGGuri;B0P)f53vC*NQSn<`O8p}^7!p0b*Q zR%_H4`Ur-uqej$$ePv*|R<`^s5JU;j#@ksT>dJ&>$#wVo19jIC7ayWup$}W&sITb5 z5mCg}Mfag#;l+B^+HvJzw{==P(RDo&D%bUko})=z#f#`rvvDKbBS&(>}AK>M~lwx4Z#nt zV3Y#ur!G{jI^-RJXX=)r=uW00vez1U!S<{3(9mep{CN-5!5q3r5lND)4MXm5cy0ua zfhQGN&w)54-c5kBvS!tHQms86+>9t)&jV`o26gRL!rFPXNcN7#&WWXtB=Kddboh8> z%~Iv)POeh|AWJnM7t*0C69EAt=E@DzQUs-jhO}qREKgqNLd1_}=j+SElfCNhjq1Y= zeGk3tFG8_E9av^&6=8?nXz11ywFv~mQ+N0DaD(|C0aaLFB8rp)HiF)o&O`WGdw}h- z8XvKt>#@jX?fD5-yMKpIbSkT6Av9U{=&Nj=^I=i2$bngu`vfhu2V8X(os#}=mo&<= z)u7#DxTE~b@luaIszvLfmtapoEG_Da-07>`Ey+Xne-ddj6avF7F#)do019uQWkz@B zzV42==(ad`NMJ5I-_+5AdB{t8hah<0?=7p@sb2HoD1+Af)~`r7dJBeHT|=!xi=2tb z7Wty*Q-`?(ClT~Pdl@11Uil}Ffhdmw@CXFx9z^KgKG@%wYIg-KjzAF98Hc3Q5ZyHp zHOADic5a{$L2U#Z^h3K)7o()-yg0S%L5prKMz&Q!H>)vX4u+cJC?9`5zhLcz;x~QEoL>o0?APc<)7e%wy&d37T;vS;O8VN&>q4&^Q>*=uF7Z+2vO_Es97)H{4;MPjn@8Le%Mj z%)Zkj@;Na5V8}f54zyE19;S^k)DY|l=rCHK72IWxPD_n$MwWG834mV?97l)HL_F94 zPdH!I1TGYfpmwd%k%wIHgRjdcdmAt6%8C1yZ?y@6?F_aJHaau{>=GXB&P`lEX~zgA z_o$OcpHo3V;{HcQAiEj%1p7uid)e@f;k@Udj`QQWU-nKHMK07r8XYKiAwCH`O$X2( z$v5ge^`;ny3Z#^XMqNZBi1er(+iNEW+VY{j909F$R~`fcStIHje$_!B+WB!Lm!Bu8T*cxHxO_^Dc$;fj2~#Rdh!cia(SeJwM(-`wI-faAxTu7eOrHA%y%29)7T~_JLbgi*V50 za{%{LVfO?MKM@-u4usXvM??9VRrO9Ucxem}D@8(4G-^!8=ZYvAvE>(czkmJ2(m^{+ z1go@3^dnU0`aYKZc2I)u-g(iZla?47wdKXy>Kk~J0`^VhCKBoDO_}M5U)D4e@%A1U zK(~0ze(ru>?Z%ltkM_Qr$uajh8!#X?w1S9vrHR%xzv2-W}fZjcnO3dp!pXY{r7a95=xwg@}R&-33I2Uv{43 zT)U+o3{8To?pt}0kY&6?JPCHYj6FX@zzr=9@+ajb3rd;Z zIF7nF#mO`Fm`PJ&3FN}S#>91vHyZ4W>!-c7J4uQR8!)olbvri8N~L}$ks#*>7gtoX znFZfiP7Y5OT{;L)f4Esy*`hAAMlHI<%JEj)op~m+-T9i2%v7x?lLXqr`DqD|Q2(wy zSb##*N6$Z$7W#6~U%veQ7H=|QP}eB9@%vpgO%foB$$t5JX8T`0Q3U&u8OYfzhx39M?!3uKxU&Kf9{X2Z5PzRmjl@WtEhk zg`mV?1L_f?i^{=WFz;VN?UxO6@haocBEOwXodbT2N}I@$MP!rcp%i)^?AHy?34d0U zKKAa%A0-@~_>y_M0*w?!Z&7bQ@`O_$)GmrioNqBm!!gYD{OPBhk8g*qo9)5IFUGr~ z{Suk*`FBwGvxoeB#y^J0o8aLeqX987|2O^fJ8JWnDyHKfSa+e)_+wA> zUQE$(q5GdvH-QrqU}{zX3;D7YwtXm^JuNrjpBaiDV`{r^wR&%dYFUx5ewqOWQx1>r zIL@cG%WZ<~K*tYxE*YDFmgx2^-#@XN{PyQ?f`=W40F#)KNpL%IIJ-lC@twtJ!!nJF zzdJa}ienS|{4HH1OH=QCor$%}gxe@_1-{8c_v?YDXaIkcKHtXFsxz5bG^|xJkGmrh z@B<3XBIzlF7cc14n{ugOdmi1wtdF0J$2C)vCbnv**Vo)GxO0paQhBf+x6FyEDb*Ka zYA!o1p3O(QvKD0_QpN~;m;YkHv|X!T$_w_6zI~hM!VrOyv|Gm0*>gX@xij}-6@U{( zS(d^YTR^QN{z+u+_I>yW{iQNI6X?`L;KSg_Y(5=tz!iF;H~f>uIaa8zr6G8CzspFK z%If);Eq&LsLUHTn)`))h3DX7kA8HqtW3`KX^taz`%;7zcvsiGE?7Y;+bq?n)%F9JV z7ry)B!sFbk&h#f|c*p7R?yq1_p5qF%<4ws7VSpf#f@PU;^=b={I^WDMNpV^YpSc-8 zN4Y`)uXe!QO3~IJ{;OFTOV+^Xu*Oj4?z67pqDCvxO@NDVU6khZ5<{-??hO-UbVOpy zYbTu&p#9nt4N)s1R8#H0Fqj`*jH)z<3;nKCHyd-Hsgy0YrfdRAby@qTJiLG@Bqy}; z#naonUj?``3lx=|Ul{u^4Z$UCWqlMv@?9H(UXq$xPm;Li_Z9Kj%NP_WcTw2pmYRu~ zpE%0q+0&05m}OqRL_*T>k;paKWO})$Hx5=6dG@UszIMSEz(T{y+<4SV_@L2SQ~rYj z-%DyZfOPik#@GTnXP+1uQwx3ErCjCnL*fEn;|7p5(|rqJ@vP%DqUP#L6}kFnh2IH6 zj}i*60+E6@DNqzhE`piVRbb7*BH=6i(OZ%X5pi}hb29!LmiVH#SZqhf^jDmv#zoEV z3?b1Z9WPE(id9awlZ*DBU&ei8YF5oRoC8)l7hX6%OEZ4t4|7*i0@sNQ zkM<3+Zun$(++3OrG;6Do98sfO3UMvJq&go!v^$kz(1{h7sR>l;Nhi;?Hqd4MkgMNx z;A-%^}Q*1SJaa@QV&r0zfnP%G6MHUmDqK))%s6pA;gNd_;Wh+g|IVL%!7%B zZb1}vV?#maF^wL1Q&z^oDjBg@RSjzr;=a}s#@(QDP_ODCAt zP0N}2j)mzLIDAc7F*O)Afaa;O!R_hu;7M#fKN0PBcWs!M($|O@{>7oG<;qy%XTMm- z_z*eNIQahvsGicV3u#Q&?j;`BC+sh+1trc4{i$VjrY0JT`n%UdMyx<}T&rLZc(A z7Ju3}P?T_+#Nptp$yq(5!BT`>1#nIgQ)x#wI=$N}H%`d?Vsa9<3}gn4;~-#4)hFT>*(}KMo8e!TC1Z+Xi1YaV950L;@bg=su~J4RUwuouj+Ax? zPI=u#cPYc0?Q9R%IY^9CT8sKu<*a)uRQ5zp-W{~0OL{S5GJj{$xY;?jf7Z&4ye}ZmUd#5~A1EG) z1STapV(J7)7V=0eSvUpACz%8Kk~XGUR{N#!Z5M&aFDZ|tfvVG+tT#Cd?_NE{;5@_j z?dxBCNZ)okDKNKogi`N${&h;< zbTd0|P5vD=A)YYE1V*9FUxykATCH@rt4bi@NT57t6s{ll!c# z#i`a2X`t)GGJDDO9dga6aKDQ=F5|KLlh^%poE4UvL}2P=Z!8Q#1BrJTaQ(5Dym3v- zH7z86ljy5o3J|#^`35gfE%z6S@}2E1vV1;om#XK;_6S4R72(X3NAa{HoL%d;yyH*r5S&Nvb5ohdB`enGDmVMrl_pnEp_B9N{sULFlfq4*9)$ zh`-r=dU8s@m(kMM{xK0p8l2R4BmLN>NqhJ%b3DObjJNezK~BGUYRtp0`D`t>Qh-zL z%_B(bAs$QmypbKT*!ujHgB2tYCxc-AK5piX+Wj_u8?(DLyfop{%pzYH!X^{41Mm$S z1@DeX=~F#%*aJjzWw_Q`L(-kxcotj<;l=JACcV{-9RK*o=5&_IczmY;i$`@>fzRX^ zvBQ!YhQ+M+Huj@za%viG_jT$7~&$e*%dCXdQ`m zZQB-h^|P?+VHy!YJI<~8E2?+>BXJ=RvzLdcI(<4>Xk!Wgjemr-`7Rd!XOV4|)2m@e zt*KfKKG_GuYjmPz*H!;nFr9rZLt?)_!HKG3rF0@q?&xxY=l*)CwJY%)=z3_F;FAlG{P8snq_O3X`qaK@9e4rMi8ZTSF z>tT>3aaQhmt5Y=h{=ITs93-cbQ8s(st)*7!sU!L&~vpLgKCEFU*@Pt=DNs6p9AoeDDB=WRbTecX%u$!ZCXJizR;M z_0C(ZM35{V^iw92%&*uk$=)% zs@SS0u`;b?pW-LA19TkuOfS!$03mOXMR`L?U3z`aw+70yg$901E1c+~RI`0#V zi}(U74PIE*WB){JvX%*_{0Mu$of$Y+6d9tbq3#7TQ=GioMAFB$Zb_Rz^xDi!@t*Cq z&tE0w&`>aWs3?iI)ihs~YWudQq$WsYpP?a2k5SmF+AqVz^yUTqwq*Nf)je4Z5mY@WhY?s$l zMBF31O_SL{Rcx#wbAytB?h-4G@ba-KlZxFz)JR-V3yh*etE1`=kGR@{3OWqJG_iLQ zwd%6C3*9E>K`R-*T4GqpEGyq56CQq3IAjJ%{}McR!&ZPH%WS69uP566jWO`8fIbd- zYWK?j#}6=KI+g0PNrl{rO(jWySwpu zZ2p3Z;_E-d+m#ce&p+^**h-IlB(&OiC3R11CiM>n{VxhhfPOz!*IhvG|L7@dK!0ht z@K(7BOI9+K@VEyxYW|(6?|M(~YYvq+MC>sFJ2aBU^m~+IQYZZ4zNIspCyD|7U*R(u zA3bYU++!1K(5n6CLBW@*n*0;Ze*XLj|Y7&~UmSn9x zG6v)TI^uovk)B3m?nQgo-E$1nB(-NWHea>{;~Y8Ma#RjU8Oo%HF}C*RNy$H1x<4bV z`1-;W2+AH-=xAP2G5C{|{@rys#&>bV*sy$b2+IxpVF7xYb4 zu82ff>r`pvQY;~=hg)8{0RSx_Ltr#*&?0my50rVW&-o5wkQ_l0DxNKk>5y(EJ8CjY-~aza;oF3>vd)P;x5$s3{HvV6-D~Ri%s!iF zbb=u)wB%hvG3W}?nB0RT0;4AlMC92Cj;ei7hZanEDqKFCuFfK^`y1RXKiif}l3>k( zhDxuz-LovxC1a}zN7H04b?OBuGi*b`Q?hTjbG^ z+#KP%ZGg)>amx6U5EQwAGnDCRn%e{_L^=vmhSt?C zeHG%!wf0b{k6{VAe|6c0>U*JNqLkc9DRrgiToE4*ZuNSc4>eDN*QpHoCkodVTvidmcn)7!>Ta=Ox*{|^FL1iu%7s^M3pzk_}AB*&D- z;T(xrJE8bID!N~`$jZHJ~yq)FPA~s z#`w+R)5*84j~Eh)<%;ZRwjQ@$YWFl+Eu!%Adv{#@eJ-^#0u^Ny<4nY{gFL9t0TLZE z1M|SJL9D+`&*d|LF8iUIIbQ>?$}q*hGPkArz|*XF_m6RO`3HF7VZ|DCi{XXsqj#%>RuYtbwj}*ao}<^c@Ayjmr}v=Ub7IgBYhYXYP-2 z%zx!LU<|;^kNi*>@cp{S9MR@JUWwbK#!K-|BR8HU&9^7o3e3KXy|e@$IQX98MkCpOUbKCv?4s- z0?LL59V!3VUwK?{{tiglh~ls&zM8ZeER%0&GhE}m@1AZI$VAcAB!V9>YD`BF#%8uk zc%N4wkfK$YlRuAw_ZrKD0-0s#kTK{4mCYIVeqh`>VpMFz|C419<=ORE-+TQg8S37g z5!2SNTEp&a;pQO7Sm~Hm7`t-)davW+hZRHEwQ6iByW<>Vd$z~7(!0{20(7|{PUzl! zdP~ja+g=M>80_t3#K{Z&C#Mst#H5q3r6^)^lx7pm^rVEQXQUE3j)}J{#8Am8 zv`agim+8oH`IPQn!lT8aKPtL@H-Uvb#4@p7P3-nbdA1}zSINZpx|jU1g>4#H0!@1V z(j_X5f56n-Y?rlzQ~vyG02Z@tPV;>X)le0L_xBZcU1h?o6XB#bPo#$|Sjm9pLDkE$Uq6a43 zrB}=hzbdERoAWGTA_w^Sl4$6QTko!LG=B64MWi3SqdMobxHkFT`}C;N8Dn8|Vnu+$ zbS&MlO2HLxv&i?F&CRK4k;b*4Vbp&L)(iV=Uz*zd=o!`Wl~We$xzBZMGdK9t=Q*^x zDYxh!+;FfMo-pn+G`Jzes??>|GV}P`VlJ_j{yqPQhYz`MT1n@!6`0skbC-I?t~{Nc zeR7uiiEVau!wHH&N|p_glAJWU)3R=ryKn@1>%&joFSWce3u7){SyB7VP*LeSZ{2(g zn@ILfR#f;>@#EFgp#(S$h4$5Nx{IVZG17+tz9$- zcoxfwiFN|sH&>e^<`WJopL8%5)*M9y5iJc0#DDQkYQ5?x%g+GQmqkNgewc;R$SsSX zUe8=*k=ff((%TrL%zbpjYYr@{=l|dIP0H)ulXR0#r*gk~t8jpIVDb_A8HTOSH??*# z)^O(OqBC`|QZx@U6(5T=K8Oh!{lQKD`dTH{sCh9zYo9S~$G~>TJ^R(GM!&4ds-99X z(#4){dw7zVy#Y+R+9yXXh|60|9#5>S2~XV6w0-x45|`X776D z7i-=B-qG5hZavGEW3aDBrA|e{bOtJ zQ#1}+%Lx$@ZL$k=j;C+ou@53K(&X%HTiQ5eVkNgd?%FlYP&7`XKA$alPjS}Fj=MoW z$CL=0y+z)obCp`!N4k(9#(19jN&%s&QnDXtK=t}|ecy3vpmi@>E+9z6Um>J~8e**! zDm-KU)p?Z&SLC7%OOM=$;=iK^^ZV?6WdxkDNYKaYuQhJ3@vy85*q4)TNMEcN4E3DP zC&(}qf3E`X^_J;j0p`?o1=lx;#+oXWRxfP!^~D(3q%98RPLj-}mKr&T>5*KNi2VE9mck-Vh2Bpi;?1npeaU>*JohpJE%sSuBk!~$LRJw$D&}}5F->fI ztNua`H9hGNNSS^h1~LnlFN`tkg9HoK3Hc#Z40KY23id zkV=eAPz!#;{-vRH(yfyPF^eVju}3NA)T@s4orZ7mtpX8EDUbu5LzDR!trB)6`l#m# zBO>{XqS1Jbf5M zqc0H_Gz(-1M%*`jIeyGMo9|`rvJQG!KSJ}&%%`SVR?c35^`x2Rkx=7C(_SD{NelH7 z{V*x$`!(PD3Mtf?hBhGYgVKD?2N8~a7t!G;p1NK-u_m#g+Y4|l?~5F$3uh_XF1-~O z)uWPXeVvjPI*<6EkuiqrfbC)}kb71Wcu=KmuIcfTH2>ajUP8AS|U|ImiQ4%XCe0dt zD5N2yma1UHQ-Rx};-zI@XwlrZoRy!V{q}n)YQ8rUtjhLPe&zx4vt0q15uYGu3GDpb5-<$UFHTbXJ|*bF?pZ8Sg;T zGos3BZgZ>de(KKTgouXA!TH+~SpS5@QaZtijHuIP=QaRu#=1UuGSXQ0r|L!{_}Xu+ z4)`Ner~Ok0)FkyLg_SpfLDE0zI^_Y?#r+BX??JBrf0tjM1dOhr&F#&({(^t3|LFkY zte<);U^e>~zhdnFIy?Rco#&r(;tv5r`R!wfm;+#O_T|hS&Aweg7G#R7YGC!eb@b_q>O^K)Pq5AJ;^VnbY=uoYGLN91u4Bc@L9p+yLJ8LTPcs zz1U!WWA^!pmx+65RtgWwFN@;*42m~9$VD8UaOk(F&NvKthMB;Nrz6&b z8cyj|?{AVn1G(X6fQOHmrB(a_#7L_l^o5$Sq-tc0b`X}kl#dUbHhH{R)_Gu_2=c#Q zM=>wQdt?^EJ;XbCJ)(>@lr1EP)c{`xd!_Q4q4>i9*ozm(J&+$;v{j9fvz@3MNYxMLbUafzz3F`)lI&Ua#umvT}0f3fD}BlA93@@akJ>B@7gQl$0I;OgAd`(6Hs5 zaL4-S6bZ%TE8b-et>+R4-(J~F+?B&*J(bifuaN}mNjvk@XI<6yW?M=(VpWA9Dm-_3{WBJ52n)PY*9zH2NfYvY0KLd` z)!OUAR8dl|Z6fz~*^vd;`-Kz=1V!M^hj;mT+&a0xuZ3dX!;NlzQ>P@oUM*(_vBCw zOQ{i$Y^S(iem^*xVFd`%0K1gC&fPw1ib$i|;@%&FSR1|RmHLI+#*O7(v9EBBx5m8Q zV5BcGp^rS49!36u!Bc>^SdEMI@+A_}&byjsSL~&+Dj&qMTT;nHvZNQ!FB7O?{%Q*R z#smy!h7Az-<{9uS%147)dHWN#%c_Uk`j5`Q7kmW1e-z;qXmDOI;nn^d5r3o%A>upT zRi~0$Ylo`ZNvv-UW2P9y05T@qgA4o-*(aZ#0u9D&w@5^bH?21G{~H<8@44+lPxBuF zq=8%40gBVO*Lj23&g$TqxBG6oU-r#V_CtE|rj*k;>j`2aQn&rBSiu2JIOrr=Xtdod1Jm$i~r(V{AnV8=Dh1bQ}svtFt z5K1seXk;*J8oXO8RFuf!g+hQ_n8hP=AACUN)^Cuo#^xJEk(6PWMj4;4f zV382BbG$axX`#wh;ow6jgnaP`Cwc!3%|Jnj_nEuRCpdj3(mLFo9Tlq-+2$%Eb9j3U z&ou#tP|Ck`4{LV9|Id*4P+XocQ#9_Ik+%j8rdZ3+dCjB`hc^?0EX|!xYO4$3`l05M z&f*ZqGMiZq7jDFcxP31zi@adugMLei{*a1%FqLA1RH>{XXchk43D6<2^sJ{YH`aN3 zKbO%6k%*;*pxrNkm`koFeu$|G2{kJiV6#-TO+Ri_0OUt27#rN~h6}~TE==UDnTY^% z%NeUWacjU{zsTH0Tse>I@?X0Dis#G-0a!2(3$#BWw5RO8v^8qtXJZo!WQI7gQ|kfY zQJ&5so2Z+_NNH8qMnJTr1Wwd{~rHCp|+{+y&&$DjCer;xUCx(=(9QLkTMO1r^YRrv%jk)`dPw%tO#6iCSO zb({r^9asz1&8FA#A9#i$Ckdn zKYTvnZFGlq&OJFEnlg5-Ge6QQ)vKh3_BK2G@5XT%kKr=rL>#M^uqW8_ZRVe4mHFi7 zESc~WLA~ezy7Dd8^S%q_xq7F&LX3HeI=9$L9%g~gz*8)3xthBB((Z2fJW~E#P@mto z5(B$<7_^GN+xt?#aGNIljLjG+lfAKNE@|TQ!hcpJncXG zPvl9RTpSf^_scN7m3dc@s_piwV? zgyhk0Jp6c$TtcJn*l7q4FM`v5+c(b5lfdLx$omrz|3|>9#K|vUsxrZT2a4M$+(pXo zda~5S%3N=*UmAX;U!jsNX7^Ul+R4*hr7Yr(U)2t|QT)!gp_eJoSSDz$V!R-W=z5uQ za6Z6JHeQydRT7`Nd*tbChNj15=y-Vn4+AvqZMI_I-q7Sc17}-gT^XXNCjbhp%A*0c zhu>PEBavP9Ygq$i#3m3e{%h_=Xu?9Ta%>Iu)OX!ISzV?@Y=p|i5_@O}s2e4@^?W|R z%gpb>3kymFKD+|!A^~FD9yX}4g<;g`yt~uk>kciL)U3VJrTxFoHmb>xr1s}6aR-Q{ z{dP5Y@)pVjE{bkxw1cgS`{lwRxZ%Pq&n_Lku)d){qT}zX8m}I}&R-_U(u4RmV^;J ziM-j?RZQK6<;+Eie}u%DaaQM8XDUR4O1p`6Mg`Iv_g%L`3~O6e0KzyX2?OZ**syfU zCMd##*p9?3HSck!q==e6gS1ISzMXo)T#+7U(geo=lGOkaNQQMuA9-f*5j#rJ>G;$@y)jXBuSchI-0HH`jfGbsbQ zt5~@>(s*CLn?%#P(jJr3_N|LY?xFX*l3wSN)O%`|-w?2W*C_F>+7vx5e0C{u=7Wbl zqDPP};dMS=Be~n)l!ddjY-h{rSs{8g$DC}!HxuB<<8T2CV)?)VUQti-vax>f`K#^Q zxO`d5L-Ro71d120 zAG-|9bUyC@TWrOgkU3JG^ii!j5XKR`lT_rk`Nl&-Bjff(^d$=e(jf;?GayH^SgF&fQ-4t;kul_lx)W*CSA&60}Y- zto4(5BS)4zCNw~f2I=6e%kEVn{d0VFPByLW{O|7&Fhb@ zpOKDcUi=u+Rb%a@p5#Nz2y@%wlvTIw28ito^Kj({e}Lk_%%tK?(SLYH%TL(H=5R54 z?p`~PJ&?6#GPRZmsYHGKYIRQ2Xo;~A{re*Lb13?2=2(_KzkPTw7nAulEdQbRwJy^lv#Q1?8q z{|bdqfAdwr{1w#sE)Dr0=33#GV{& zSU^`+(h%WFa(XjTAwz;DC%t%&xa7;KF};d`Fy5c?(U9TLOKNnq&uY%2>}6VP(=Ud4Z7or**-JhH@}(x%zm@?xE(m> zy03ek$MIQfsSCyLLoW2k0)sz0c$-lunu9sOExRQd7hRuNFL!}P|gMY1iG>y`86 zCNgT>8L5M_(w=Q`}bTXss$Neo=tPpcQZylJ+@Le_yy7R z$GtDd^CtYdh45KHOOwzwJ;)sAJSZO;(n$YU0?mVU^XR%i>G1moZ46JKLP-rZog*v2 zb82QPgs&le5__U?Uq1KTyO4D9RWpP17AspFTI+GEJ_xR=?-Dvo%&dCP6D+4ZA!fx7hSY4Np8z9T(6B1OYi9Exz0Q1;2i1m9tHv)5Zj6bRe|oBXD~ z#8qE07L9M1hG1Xo^(iY{^tCsNl$_`i+_|WEDna0k0 z5XmjJn++?*ADG^G%PFN%d!%8SrP)qNA1F+dT4gm0=Dt(C7^gK8xhl{#rNjxMDnGDw zJdGUh)}C#s30NHV2P!b18wVw!h`~&K4xOfyt|LAEs8(cojaUrTu>32CZloCT4@p#0UbCZ!o*GVkzOvDqi&{2Ows!N% zb3@8ygs|EbbR})5sEcRN)z61IqSID{Z%!6*6<@RZBN8KI3H$S9B9p~4Bp{%Na>u2f zq7_7tTBQq=cSOXVk+z**J}VhMuSbyl$6!OSCHO+)jbD6cc7~yo^53Z z-?M2vE7kfgCu9lpPf`~I#V4V&8rQ;GzT2Ix{Bw8c zS7D#f7Ets0n$h;3MKOi*^OLxXr|UoB76}x-tq$Rgt7-qglk%=nX(AAPd+6u z>dREaRSXGtSqKVr^5x?t)efv`Msz~T-(^Bp!?Tbr<0sus*T3K`h0d7Y2Fbp>DjVH} z0dq5lWckTQ%kH~rs`^*S=K2wZ3!v0tQ$x7MXnyAx0cDv1YzRs;T zLLxHfy2r_GGVLN?3-%2L5S10=Y23oJn#mT_fj;0<`P z3-tyhvtb+p=VT2aw!suyeXcPAbVLp{z>~fz6cILPP*Sd#sNlu{q&5J(Hgs>DPU_k^ zffpSuY(YUrchm5HWRdRD{bZ3);;#eU3)MuAz=TC;)_SXdd%FA#ot-f??Cs>{#OvTe zP4}GPj|tR3gIeTWb>QP$y35gIP5MHW!os z9prh`A2gEI9eflptG*@6RQ|M@zEm@;=CLQ%0Ec8&U@;g~Yf3+PA4?dXU`&SetImnp zCB?K16RO0aN80J49(dj3WVBnDjDw1TSi4zJi>7wPAjvBH$sqk@KCO30p^0BZp;=`G zSAW&-0|bZT4esmh){{P&o7B`EU8_390%7;!{eSRQQhD+>j$%LOfjoQZdrTWJg8JjI zav~tED7FZ{lZ35FFb__VJ%F#yeUi_D28XVS!Wt)~x^*fej)|T61Y3lcRHVjNTj2Bg z+a}Ovp>IpW&t*MmOB6F_6SqPUB0WtV@~hx!`mm=`5>#-S65IZVvQyO&X{g!2*yJ_T zW)O#heYDPKV;rdq`!+*63GHy4k4F_vfHumoW?6*GkN1>_i@3+D)FcrM39W#k61;AuX@l4qUG5kXBsx!{2kISuEDwZc&38={o)tpaMmU=}w@H<(_7Kbht zu^Jp=`a#RO@9ip7iNquNZvMEd;YqZA zx-Qxts4tA^#mEDoB+T9`C@I05%|xe7S#B@dQmM2>KZZk+m2NtWB6qaSTkgL@Ny+!2 zeg$ncOMPqtFZ4`e3&FTb;#|YbTdMc05wBG~8NMgOf7SReKNi#xTH^-r6Bz4*>H=th zzGX?OnC>C2TdAn5o(a0AJq}}UL+@ob2yVZp0+D=4u2g~LlnCxewa9Gv^#n5%o!6so z*4+1I8ZotKx38899ZTKZVWN{6@&XLU|bG%D_^E874` z?{TCSKfj;($OL$bW^A|WI9;?HRXA!V9taoeHsu6+X9P_Ih~ z3=?bYI4&_atdOP;zKGZB$s-~iIxsz5D0X><@roMB2zzjmIM!P)~V^T*p`N0K;s8^D^I zzgQ+VAoQ%mS||X5P8(hm8&sQz*Uf#KJ5!@|l26<&itQ65TmbMqH90J#B+A)80VSmH z6)s*m9`(=?IIIdDP)FnrZYq1C1*vT{+fYL3*O1)Gv^bV9v)=TOY7Tu)p3tPI+Yrcs z28CGfwaTgay9q6#wq zAwcU&nJMJSN1vGHH4Enu){HVM@R38qg}9D;+?!D1Ih+{yGzTwq=aAhfkmFK zin^ub=Xd`)S|=x7kWiS)zuVmeZGw~qRQ{(NVvml`S$H*8ike|`HI=oV&A%=K`KPl) z%N#5JI7kMbTZY%GQ4Udr1lIMP6AItRVP!``i-*6v^5AqYM5||BaxPpv+9JXZu~XXj z5M^aS4LdTWnrvlG#7i~W=!HnL?I8Xc)r~F^QenpPhUdqeg`%LRYa@n8 zMBwijcs3dB?&j&%9y^ElEfb{0+9_p7istS`LI!*3SS1zYZ^=)5n{hasSI)U6mVi@y zUYrxu?P70|32Mtd!TCLuZW^`md?OuUiyRsz7d-&V&vd^Qaq8b#hq}inUo$p2$LW9& z`rC$obIky}VZYa^0qQ)vjt)WuaZUXj{DTjs$B>qf9^4Y2JEyyt)Mh5ha60JX6NxV) z5Hm96T6|xeRoyQ&hFh#unXh5+X=s8g-+1MuBc$jR`MtbSJQ7qfP#eB55QoyX>jaNb zmhGkMdU*mtSC?5t0siqCz~-dcr+v{5%xyt*;)QGJOfY`yI8Ny8!8Gd5I=<)4kqx@c zf7xk%MF~|CSem^Xa-J7R^8J;YBwfqvy8QQoc5?0h{2HG$Aw*1F>D z29l)gOVlIT`k6IgyywtWRaU_(IoOk2Nl87y3tJ4W6YaYDjAQs%)JE+F z>~^H@lk_Fy0LBNrH5(uqM6dj$gC@c8<17uJx4;JHDohF7EAya%D;^G@?-w7gdNPb-rpz|C z?b%gjSbKdYjg^rZkNzE458RQ@RYdhRL3>eFG8AQ%R%Z=j-~?t9({a>)ZnC;JfQ>4& z_{lE2v?WB<)&EdgYK1%vCvPyBNW>a4vrN{rjl#*Ip3@o%D5j?))1%apT@unZy-jmP z2#}ke{#p)IqN4Pz5_FN}!x2o0QXOwe-wO!SgB=8pqIO2L%`F;75i!qMeC(_wxAK8z9|Iw{l#Y;g2Up9>!`NpBB{ z4wB8~jWkgFEjmfJgq$<7*EP*Sw#!d1mQ97zE>Rur?DlmXT*S>I&S zkFYbTvTLrQc`{L;Xt{14MeHzdbu+SHAN$R-bp@s3N~I<-0t5RcMCk9%{C#-^jC3~sA7(1bhziI#6)xa zus#w{eYFB^cu_`iE5@-fboMlwFgIX zBs-(Qvg`;^UJD5dnXk&@)8@ET6~HyGV=~~gnYC3~<3q|GS1~3CF&U10<0w7L*!y?< zPqq`-8=BW&PeoD<2bG79-{SOKsPy4^ZGW(AqYDBVWYHY-AqUS4qTaJV)$oE?<)VH+ zyxC9jJOCd3QTUAu7;|hto3GPu&$hp!gKf^meG|&`!rW0FPPeo#7y@E5=2KAJMl!g* zyUWBES%gr{s~CVrh{|k#$Y^CKQ*^HSrg|G*#Mr{}9s2Hkz1T_nER;XOlG;iv*HirB5o1R) z6qzq}@*5YLMKH6zi&X|HdydBJXh?PV6&T(~>9AEb72s>3T%t@m!fRJ2FlV{_4EoBi z(CJ9zNi0hj03A&;xhTm~YnicL$y|TzWn@kIr9IH8Ido66FK(`&_#5Ze_oC%tb*vP< z5%bqQbA+g_G4bdhlojdUGYL*q`OztqLqx#I63s6DgUw_GEsteM8g0f}FCFsV2JqoUDthbyTw|w@bJ`|T%aPbbiyA6Ig~IZ8IS>$t5!ov;x&*>b=3FPGt3v!0V8tAE>u9M(PoMf=VyPK|W#dVe?IR z`Ppi6tEU}~B`J?`N0B7b_y~Jxbs&tjEY?X8#T~CwimVL(W-+%KkQ1I!GOXHnud;Pp zv)Y!Q2-JC^(e9CcUsJIOCA>vKCb)sbu-e3XhGVk0!|FUSrYk+A4XIqO_+=X6k~Aec zPc$po!602V?FLS)?bTS=4Y2Q2@;kLUba8tcj)hdS(Xw?6j(o_}2E|#|IZ0g{&i5+y zBv?NXYC!@9vb6;NwP9Dl1e$2nAl65-{e=4weu(>|iL?I@BFVg!x`w{h^%pU`0<)Ng zWupvF?sL8cv{n%M)Sip~TJv0)m5)m4bGfz%aFoLv(@2Xx3H3=TQLGXLEFIA~`mv(4 zz+_5YC7ZXRP2$zsWr8;Y!tG}5Slb?O@M|Bd5sF1qf#n(|?N`1ag%YZv{WZd5&xa!J%WOULR35X6+@n{L7466E`5rTuO<~C4B>M$O zVFLxbVF^ZHUmjehiI{p+qWwk>ttb&@R$oRsD-*KYJ_=wRv&dpYc94(xHPPM%D)+p$ zjM(SX2`qT09(;SWE7!0YW}ozPjR5_ulE^~R}~Uwc^&BZylZ#-thQX3tutH2G2NTnC~zJo9;5_znEGnr zkB^%qp;l_mFb&Uc8mV%o19g72?YXzpUTaUxD@WZ|!&=9G&%G7T><5@hPu|2W+g0<} ze_W>ur$K$?BcN#!O;X`ZH{N8k4q~enIRb$^FvVUgq8tUgFX^9M$Cz&W4UsM^!IWNZ z9<07fgw&jCt6M^2oEQdCS^g!)=>p*o1x~TJb|t~{M1TaxLJ{D2)-)B0=JbsomIg^) zgGKFp@1Wv5#6(yb;jyWDBfon(Mw{!-U>abNjY-}IuvH7D$IMO+AvW|3e#nS?%Hd>| zV*LFo#8j^m?&A%Y6qPtdqxim!r%7rP9j32YuhU9*ltZrws>0+cOB?I!U$&aHMLan{ z_E)~#yh5ULugbRZL>ahxpq4v;y~t)pJ)@_TX!E$mz%PP~m_UCT$6n_AQBMzw#o*d2 z%|FX_=c&GOV&9v?KfDyn#-5C+d7-)1+w}D!9R(YKN3AS7?&jYEOCcA9z9pkDkIN7R zZ<78?cGJPbDxkHiM}jU$^_eYbN0nq>V#q!#WhIp(k7GK3Z&Xx50Vj_wixSo*<_zc3gVWR+>v(D%p#eLfhqr8FgVyCy5{K^8 zuw(dxWe6&iyem>gerpTH_O_1pWeLW#RzcHbE#k!icjTgQSCcDrtT9kIjs75|#8TvG zJpB)0aa)xlleqKr&EC}%TJRrXgrd?eyuLL-xL+2|C;-G|CL7*=wt~;zHSd!rWy#t& zHNnTdUC$jax=j%^6N`Io7pRk;NDD|e3oLZ>jOf#q#;N2v-0cL+ zIGKcIa(&!pl?>8M%VQl%xkbwjldD?Ya9)1VMpX0QJ_Rg*mknH1U5+pDO_IScXi=55 z)y7|MWdJ2*#Qsx+Bg%q(Uq50~p;C1HW?8k zaqXwEqzB(rG5-q-z=8|FO>(AmUIbqAHA`A&CXX#ztIa4EUR^s|NWLd8e0EtQq}5M^DNI@Q_w0NnE` z{sEID&9W$UZWOyT7K{9198)-&{$;vt_9}AW;v0|*e#kcDIU#*O(oq2{;I)kt^guxp zk0_|#Tavb9*&QO47eJ0tvaKnMKlPmsx?<1%eyTJG<<{x+ik;=p%m$Wo%@g zx2kDT^6I$_kn(d&)gKAc4AG|w{^&>!wkTmkcV8>+B<%+Y z*C=VP!HprT*sAJ*Tv*?HL8Y);iR6e0u)c4LFmcH=S4Qc4i*W}_et^6zP{(?69>p?# z1>B)au)5kYS<;&|S8U_oi;7)-w#ujhpJ{7s=Iun^Z)TI`gEt0;05-``pm8<{ z;ROslMx3ExsQsH4Fox8;`3cNr1~Zf>_G(zM5_JOd5J#3{URmY(Rl@{{9|= z{r+SSlRVa*Lq@X1i0a)Y&O!4av9zCyl30*SoTPvyXAm!)F1g36Deo!Pb}X3u94g46 zU-Ys=vJvRtBzR}DWtWjjd8bIOzDQrxii+^K^ADkOSO<{_KFI}fPgl96kruC6s5O6` z9C3$!RcjGLL_OJ3wbYKXS2fV!24ib7qF<|m5^NRMth|oT50r)pG7%dhx{@tQvhn}L zh>iGo8>u`3&}5KGNNUlp_20DkZdK5aclaGa55oiBg$E9h+Nj#3F|2KOATG^gSJk1n zz4|4{(eKbK7$TEx*$*R%dcv~~2C7%X6haprn}CgiGh-;&D7x#BATM>-Sd|mo_+S{v z#78aqX-acN6Pwac`pG_J6?|$ZixE0f`#PqL{qA1{?x##*z_LmXG2lmPO*~=I_l&QAM`kniZ~3Sj``s;TT6`EWL8ACfcO_eIY&p zqersQo^pZlX)~@%x4Y_iL=^dVG^{B|`#0#00JV)E3P}g=8;;f=(`Qp?dBQJeHhP`~ z*sTofek1L7}o3cMd5)Mqdj$@$dn-IGrF;Y-Jyol$N3 z8j)~FX|)?#O>kXbmS~O?LAFu0J1s>(4MSUsq^Whaha+mLqTD{lMW~!>$=sgz3u&q{ z+GW02*MRa-!`v47tNHXY-LTFlUS#e27ovtf9!W`TAiIB%Vz_W!k1F|cjDbMq<~AO=oDzm*(~gG6T>M<$7r1MaDsr-?1$UPI@G zY+fszCzzRWEh|pCVG&KrmG9odYIj8DL{W|ZHK6hoP!GSex_7qsq^V}JBc-?1`kq@JVLPKBBAOZ?w2rYJPG0^TXKV7+Sc{nRX*dL^GwZ8k zRk9BCNvO)!==uhk8HCU!b*r05q{xuzf55QK&cXfV$+dP7EZu71iH2uPWO~n+mpE0E zV;0_Vm%89zCG|vBq2>@BaxRyNQ{KAJ{T=q;qOJf$l)Gq>{U$;+*-9^Eu*c<{Q!Mu} zq6Dc+%9%-Njm);c|BP8tcVH{NA{ks|1O47w=jQ1YKXIV>b;cHjuaZGfN zHgJJND7pf31m9HCt;mF(n}{_Al+5J5(G33)GRIbqpx8+!%YK(|A(C+mo(-%=qN*3i z6Q|0xKa-e0Wx)xBj1~K!rPiEYwil=W2|>Y6Bsy8>nYeyZbGJ2>La=gBxsYXM&f^j_ z6l!$~NfpyR{xTi$Ez+9g1Upo-L&#C2aIGko0--^T4qiX4&|2Nz9s((xeTjZTOcT@a z>ahIZrrhzU0)3&EhA-DOS}w@tw{atPK0NIG&dY%`^hZg+D#!7SBt5j1hxqEc=Gq0K zo!xZu?6RXWb_(EFZ|uAg!z9smZ;T{ZU4v>vO0XB;DtsT!O6^$P9id@dZ)i4sdAb30 zH`i(>P7PjLLRIGAURVS*>>!-R8mIeD$@V+VvAf(Vh51_&(*runClK54N;~0$Nkjs@ zaNTQWgzgclkFZ*bLC!{h`wv15?4=~;`GZE>DeezwV`y%ld zr0%u@7+-O5*+wDX#C-IW2DH zOYmT@lJ@H#T6`a$v8-*fUaFX1R#kpm(-Zzve}KmW6aNRE+~~JycHWd=;ELb}8P$3Q z;kq=FBSX4K-9 zMwjGgDAV>b?N~Hb8Y{yx6~&n(N6FM8lT>;>9TH1>%ZdL2DYJ=6bHDpS|Sz+=w8kI}E9THEScvZu(ZFoMYCoYccE zL&}A)kc=mNoS`|R$Io}~)c4t1RnbqHjf|IVTuJsPyH**ZG6N*zMD*Dt`0n=iCsA1! z68x9HP(WWRXJ|})Xjg}u&s~?Bc?wU1l1kdf4DKj6>mh$IWiw?|Gg!D}wDcwW)K= zXsi0h-^?p*6fV-a1``a7-*bU@X*s%K32YV0+U(*5@Ip{O`m3aG@4#VzBU8@lh>1!X zfhIGt6~NVXCipG?pqNN0BtHZC9U8UB#sjN4G?pPxG>-B?owIu~2usDDAgud@2?c<>(?fJp{dG*#Oz(cFex7q8xbytLJwzO5Y( zkB7v5r_je5<1kUQE2t)Db5t>5m1>u}MoGM$q%+6`u-a4LQX^JjTMW6hS(Q-mWXrHM&1t5E_kqha4wGTOt(OX~_| z^17>~pJRKKl6x!nNXRXN1m$WX@0O7amfo!4KYI(P>4TXla2xCnfmhwEimgCDsc1rN zoDTLiS@gcL+;X7$J&V%5==8mWp4<3~jO>h8g-Uz>Mml-5ktna?pK7Wwj9teyJAT4$ zQ9p+vOaDl{>AJ#+BxoK42dk9+0U%}_R%>eNsq>X|C?ere_k#UFK|7U3;mW(gh}rsL zOSd-S@;@i>Xa589blyrxX!U^W*rzY_N4X99P{QSbBTYmTQ8~{ADILGE$Zr@Z>a5PC zeT?=WBMqg1|5*gw2nuKK{*wRG`Qd*yrh)$`1K6m{*XP0Cf(S_7YW&VTUi2s7^m_w& zaok(RcR$NYoR!Gd6?L zhhb2O_S+B=G)4H=az_NdiuOOQcla;&SFVztBn3X>nJj&d_^u?&zej2LJo2Cbri`$a3jsqXUx1ioARn06hmyGh@qncFq_ zX!cd`>}ri=SAZe*1crDt&e`-xD-H?5bR3%UkwBk&8qQq5h!pxZWjU1No#Km$v6f={ zI^1rAG^uhnpPxK6Nb%(~L}HnnpqqRCRWMZh1{=a8;?#d;A|Ha9+Ns}T=)(G&CR(Ef z^g^7gnfv18d;cwz2~P0B+5dFm8GLDtZ*V{P&t?pe>Br$914+N+Gu2fB&KcIhBTQ3g zf#O|@`_;bYchxrql`g<1(D_Dq3@7=!6>pt;XTZPk-by}`FxRN7zVh^iF_B5}18e*? zgg<-XiYJ~nB~bYw8)r+SrE$G@4TS3JD;HqiD94+}cpAv<9ep;Kke-9=me zI%$PLC?&;7cE1(w{6iJ11qdZcDdcybZ5jZ-IY?t7x%#zjauxdS(9o{ok|cgbh0&Cs zz9l#l;&WVW{rOZ#4_uz15x=tmgNU@hf{DRK#gg~Duwh=NsplQi58rjNorr$+Cc7f&>De+ccOfyI70$SAmj^rqr;&yZQE zct2{5TxMkmN;IK{#Awr;a`sgfc)7p;qA3dyO-r*^MAIIWX!`2G&;Go1Ce4F+=x?__ z`+A#CD4}Nmy%FwlEWbHhombI)eDjxOqWh`rc?lc9GMlvDDW8e}D+o$+$WpDIK#l4N zR9woGg(+g?Dz4_?e!2X+RlzvEqiYmp&$XP7GxfBSc5motTheY7jD@q&NN6?Iw)K+H0K(1y91h19qEM#ky&Aa#O+M*I>cm9c1VJOQ@%aE8VAbeU;SE)}aAx}s z3XnjWMkLDeqk4x$$8nVJkzMY3k7fuG&`ec(5hI}w?}qc}vp<=*^IO^u?D^h{;vg#*>UD` z@|jKnp*>9i=Is90W19g4$&0?*cCt)ODk--n0#2>Ru1;=P)iDn|f&bQGrmAn|a0o{@ z@US#LI#6xSVMI%r-`$?yGLJ+GD+Zl# zwB`z}4c;2&l#0|Pb(WIgE~D&=dtGLh4yg5ZXO*R7XX{(__TOO55(3y7cB2hM1m?cA z3|+__1a*1|@gzwHrFZ2S{ZHj^$$y2NLiAKDB^nQ3)C)4QSC5JM7~44Zrr{$Zpe_p) z+{bJS4iQ@4Prxpu5A=+3A4Z+TRI%v=HaAU{Z%l8pz?)I4s;4iijdn10NYJbL`b1Yy znp8Mf1bUj5_?ij1htcsy={0#pNxpOJF=@(g9~<8^sir&coo|6{=y7LV=>^q;8F(dq;2XMdkD=#{&INWoM`TIy;kv!g}2xyITXmxYt_0Axv zF4nGg4(gyn#d%$w7CY*f-A}vdCm5*}nF^pg6D<2qvSlz#bNT?LjF-C-Somq^+f9-W zl@XBQg!6Ck?5fNAfp_D)^NTF*g`#{?1VJ=)Eu|IJJI!XZSE0JWh0d6Tr@N-#_+{=B zL%F8`1p&71m^`{2%0viyf73tbZ5 zsb9k^T0CXg1FW8i+8&3i4>uxx!)_9-6B;)2apDJC3r%x^1OVnf7&mnU*wbJR5-l|C z(AobJu8+los)wi)0hUs^{O-9Fm$uq6hwwJ0X%q`bT*+yoCEVVC6x+%u%gn6=J!s~K zEQOe`%Q7!KT^P9^%Ku?TAw45Wk4dB(0z2qPu!DY(VU8eR8_rKPKX0|WkdMVfWZmyd zSp&SO?I{X{sqs-+S6{MAB(n;%#{hq#aY&m>eX_bcKi2D!z$F9?zAD3VE?hCL5>?jV zphKW-gBS%mXZZz?yPSOZF>@w)YUgZm@^Y6+<6}MRPYkNzO7XaN$~!@yboK0{|J$Mi-kEY3bj*=i0`I(8UYftnq9Bt^;EhC8Sa$e0Ugv z-`>AQ1P%MKqB6YT1PU%Hk51=jr_Vz&Mmcm)Z`56_P&pb*ntASS3vM%3Key-ISo z!!pj~fCAbj*Y6EP3ZGhj%0@5noEK{^P}3S)sn4`^8nTP(-}|Q0KrH}PLJYOlwM{F3 z^u&!&7^-WKY&*y%l;tfYsMF=mp4lO(7Fmcf$xn`C)v@h& z+dL`}aDls(KSDh`yGT6$vG)sd#Q38URf&21>WSN=iZbRxq~sT>bD~;TD;B#gVf7k* zil3pSdJq6aHn0``Y;?E|?p!>p<~6~!2{BF?Cm-GMRCuiAGP_GfOILo0c?oCkGYV3R zEZfNDJoJznLH6W1w-`{yZ3y)>^+-Fbmi@p*-e3cIHo#S&3G@H&Bt*BN5wKTre~`M7Y(DobhG`rf0ZAp(tCelVx|lsVsQ?+0vJS3q143L zmD_NEj9gATKHC?q#y-=NTU!--PcGigwN6)r8;?CHZEktd-2ATj9sOeGcFm^Iaqn%m z#5=mLF7OcJ&}PK;wH1Az_o`YSs3pamkvQK@Jiv8;35=mWcKir;V|z~1df%qUlD5Mm zx=quekU<{_08rau1s(GeFYlThYf5TQYuD6pwRpTGbXfNcniW~B9!7xK6RS)VEi&-iRq*GXxmXrI0#Qb^)hROWUMu;{CWZ%5qE(2G-}SXti)&V0;LiObN4@q zDb~_ze6Wb%_G>?eIqZ7rT>hc%nOCeiq_UJZ z-uYS?9c$d;Xxp|rmXWVy-ZC&5xYul$ksDD%#e3SV!=2q;ly(1|f{05wR<|kT+%yT+ z=RI?E5<=VDOpFiOHf4C6>=j$~wY)B4n)rbNp~6{8xtbJx#D$L?%tcN=VwAm~l-^38w4x@r!pU881m*{9_%fy`9BJ||6 z+?2Io6do?MPFafbyaz^ZCV?8U`XvokYjd56byJtK=Z`Wc^ZJmo_7GJ~RljcPNa!3f zyvN5)THo{*HZhLYQz*6!li~q9avlF!RNLEr3&cfI@47pKeagH|vcly&;A#BY+Pd7y zL*YUxs3+g9CtEq&AD>JyAJO?~;!HQehC5cii)*)ux;A@Yk+yG~D`I50*l(emI`{?h zrjt!ov^oc!P4+-*w^s?pt@xP&{ExOr^h%YQa=G_Mi!9g?P+`}X1`1EuwN%|i>o_gF z*a;@0V$>?Bv@k08UYxXGuSlR>X7Z1MYU?-v{thY<;4p7h=1;^OF1pFS?hTqw*@CzPxhUA!0&B8Fl(;h@-bv1=)bbl$aGRlao&gb12!=bJk zYXpBZ@LJWuWRbum$fU z%do_{8t;alxRzzD><46y;WahqrOg0e`-jbCuc$h{yk-L@*CHg$d zw!V|sI)Bh>=~-MY!I3!i?oz*X0>1`VMy)5CAZ{|Gp+P8};V9*3@v3F!BhPKXF-dkS zOUk&tsE&W_{jpSKcpe8QZLmubzR=r3XudVrv84BfifLDd6=gLLt#I@`Ls+}aMFUP|x_9 z?G_ufDpx}{?$YD<_-$ABm?QMhOJ%for{X%uh)>hVB+zCtRiLXs#U@x&vwVm{K(_gv ztAKv`phzzic~whmif7$?oNhqbpOlAToo=1DJW~=)gHyUcp`@$TuoIABG>CWjxmapT9aw3x7(;3OR`e2>?8nY2xJ zzudIQu0)$PHVWMt)MhPylu~u*_BP+*+e7Oz8*Kx_eG}5hhJ-A`rLnnLk>zE*46Kxy zbGMu1E>_zYqvL(kf=A)XqF?{`{xRFLnk~*yx6JLq<;D=vD%aNw^n+xg@3Y>@F=cXh zd^qgIr%6^{;p1)3&h&@vusDoE`yC(Xk6LUo$o5&c!&7s)OaBm$ess#Chb6XCyOoEJpRJ4U{FY5x69o3eng_8 zm&Y*UF{vm&fwpGs6JrIQIt2UF(;oj8B1#x`w0B>gciC5$(EK>E@wr^}c&%%_fLz4& z872wdIau)3*=J;Xi)eS}4qcypT_E>n$2VE;<$fp@{N9(n^hNQQ8ri$tN0G!@2o^~= zCb>JFGP~`^htGoNp9@!-mKD|%yhu6W#N_xB2P9|s>-%XiEh5H zos|o1tWpaY2!2l@dgODGuZzxL9jPeDS@7E9hZ}EEsT*&K!3!LcaP0Veymj;KGEq~K zF5WaFl8#>1;dha&jgkTBYn+8FdRRK%9YUqE+Uu$#yGYy~;_j!9kMllKtUa&Ui78pyhFug8hAcahNvTy`|Wh@^OI?hDhsn7(xnewCN+M3Pt(GC zR-mu{U5Fp`-M8nDrb*pw5!RC)jLO#Oxezb94Si^?wdq{fekgFO>~j@hbrp6G2^z z!eM9MKt1jx2|cs9&;^Li+Jfka^*%xfA36Lh zH`$PY=0*I`poC2fl-4^33W`s72T;E6rdWx)u8whnc{RDC9lKpHY@Uc7M75pwkr4Ac z1EY+W1SYo8@M%_Y%L(JpQS4R_R?X`zB)?G?XU*TWKMiYa`uIWOrF<{*Cqsn=>c^~0 zPs-3eA5ZY3xWjd1(AH609Kjnqgc0$5!3aX^fi=kqIN;-SNhrlcP4SUmZSybP*;|MY z3j6|d!lm;A2A4{0nCZ`T}S(dYImAQApzkNKO|s9}-t;ye6@aPk(?CXcY|oMlr!7p_MP!pE_)VD7yz z6HlW*o_f|8L3X_3@@$-EiuKLzQU~P9q^r*+A+glFoF0Nm3i{=`CY-ReR)rTaUOdb# zMVOi0FdUs++D6NHmVQqfq1|LEL?!)M>n8O|vP5<{C@?1TpfBv>92jA?k}3_+G`tdt z9D!CUriwLCjkwu6j_}4wPg@Zy*=c+{;Xcdbnli z^9`(_uN^6*sIyl0Pu;4sJ$u?mG@_CpmK8GMos~&xw=&I3N`aA?gtR+JPn8ZR_Q?R> z!<5Di^%>w5NLS6!`pTWg8hQ5y4W$)Ti*}n7a*C(C;@s+imA8tNIP~lIlg}F!5nFPC z9rnBh5w&CG_m6{r1nH!0cPz4GYYlbYcv(gN#CPM}XV1XuG~~kOpgN2fd;F@gQjyHN zUQ)x`C*jFx)Quv_;CvHgY-E`df^cRuIU?KP?+{WKT1i;=FjC}&v8vsPXi4rR` zV!UoM6)>$zRvi#}FFQC50$%OO!AbB%MwgcXcLtM*L!|SFpmeeIn>A%i*FvF{(F-_~`0SysUR09ey%Ms1sf#fz4&Fmo7jh`8dk9f^{4-&K*I3f+ruEV?6#boQ8( zDC&KbpZ_aFwLk)uAcNb8($=wfw_kTO_hKT*p0UEwL*3E)0pWo$yVRic2XV@;~svs%YS6?hUjyR56F%d5OgJ$h>~9 zK{Pdh)`_MfokQ8klRKx#fgRz3i*m}v>^_wwZgr*B<;`7qX@pq1RaNn+NI_bQk8e^4 zZ)fW~YFmL%6ra2O!tlOIjx;jl{uEu6o_bPav1T+xU99+~aL%xkUV5o!SMK)k$}_=P zKbPrkWDZnNl~hA+}2 zI2f6RgcUztqgOxLq4&l5p=k3xAg1EYqB{Jmr=}BgaDG`7IiUg~I9UwSYhEEdZlP%) z%V|{)8K;3aZhCUL^~Wnu1O13L{p4Z%@=6m-rI&n(nJ*rX-d!)Wb6r&qOm4E5kfWcv z^B(@0`TYv}X{Q&FeQzMs&+A)2*&KU5v}~?Bpx?`svU)S|X{|MCoLDfg{E&dHLrgq| zs_z&&OROcqvkGzsnXJQbw^Y7I{~vpA9aLAkb^SKMB@jHgySqbh*Nt0n5AF~`kl-$x z;O-EDOCW*Z7BmD87JL(eOUSo2r0tydJ)Ki^Z{508-#=a5RCg05&t7ZJIeue2$L>f# zzrXmI32A*3KkUc4)QNF!c-}q^4`RB4QC0}h=sW9lCKreB+Ipm))QHnQpA19-Q7}i= zy?e!3IA(`c!ANU)Jhq9WA+qM*#^039ssD0aq@06Bm}hj1um&0DJ@8D6#C{@Y&2HoHwpcWq2y{^!j`^Jjvg|rZKn*h`AWMu+p_2t zq|0hw(2|ks}6ue#qbDo)F&$mKc^zg0W2uq`j{pou>)A!)%HM3hO zEcqzb?O=l{w#`7!MUE1<2n?#X)6@f$mJbbRibNVZllgrO?vE)L$#e;v0%q|d={CBsi>KUY(BqRWAF#Ih4M58~QTNv4v0XB!Z=$Y%out&J>#9mi zdOJj9)U%uq^CUcrd&uk(@_Y;g!5p8(1Nw|&k4R9(HB9%&5uSVHS1#1swm+BmBK_3A zWQp*AagsTz_4viUDM?bBTNZ}yOpuX&$Jt(*f!|<)b(<#5x#&~SmwFT~ZPB>Zinkq4 zA`&`tVhj^J`uShUyzu31tfg!l&UClKUgzA8L-Z0;L9ip0Agd9*xw*=D_HAII`Tkp< zBUzN#0J<_uPv*Sd6HJayQ<^I0o&+!NF46^QFM{6m7rd92o{AAQNp$7#KgP zUxJ01eqn0p$It~P3?J>Kgn#^z z(0mvaO8m!j@eNixaab`s<^Qw70%z&u@ND_Vb&_i8?Xdtx(F;DY%eRVDdf`vz@YE`z~T8i_fi>{UznZE>Gu++F8kjpI*+t{eB;oKIeLkZ07Qe z=7UNiFtld}GRM%ViuzxCn|yZTW-IIy6bgJpM_XbWI@gkpgbY~9;_Sf=w7 zrk3&#Lkyp!%o_eUeDzFA!gm{$%4WK2gt-sj8qXJZJAGi4ynqOXKk^^N9BZbaAW3iz^q-XJUS%3bwDh4rvvtOqn>=_ zFTij{?)`8=9e(66_QAeX9j2H`r-;ugAWb0dxB#C>ufV5rasrL1r>zTHL!-0uT?|T( zQi1KJZw4?gV?$tp;NRv0xZ{c7%(euw5ME6G8PM5%p__B3pFhx>Gl&OOnoT*bi`y&N zfOmo!imQ7!&hu9hI;uGe-XJSfkD+PRG~=&`6`5o;`cM&NRl6nq5V9&<;!CiRNLIsY z{fx8|MWy8fILQ?Q_gPaRG1-b0^1y(lq>v@=SGOEHt6ezKPAU}v*2I%uxyM&n9q&8;y?Bpd z)I_;9y$zNkcCxUG=q*dP@=KN}Ts}e5h0kZ&1S87S&laA}OlDt+d>qJZ)~dF8)5br~ zz*nH}Ub?e+@%Y&VjR$o*HKLdD;yPK)s6y4itx9BuG7%9e@>pf0{oK@sgeN=%wmgEC zI^dneHNR^p>=%rwHQ)jG@eA4)SQCRBrM^EkS^X=YAbB{*XYRuT`ebEA*#c zG~hL(Y!id%AzK<5QcY`8LU0OHcM&C>@G(PhY|9DbU>g0CdKANXXND%NnWH$AiuZ}~ z{3ZsSY$82EoCNZ12dgS6=^LNB2VF4E0j$$a6XkVqeu+U5C`gyo#XPS`_V`Z zqa&`osK?)I^uuVi&II?)kAdGWsCcU*BgbYxRpLO$u`(pJe%Qu^Yw2pd5yB8Grn~Wk z=Epdsx`d9VC49?JZlPR0(=6394$RIEV!rRxsvxdu7rgG(GFFB>3s5jYpiHsgP1LS} z7Ox4@(pU(p!O-o#esA#HrT&H>#W3MIuz~lD&UV-){ww2IA-olue!()Lc0|0xK5am$4iQGT1{*iEWIu% zU&iAFoRmZN5_xHHHEQWy+SsvfAxCwFlsIVh3J58lbJFT_ew1LCdJ$#ur38O^AHpvZ z{RIB)G0wp!q}Ft`?yMbKPI5Pv7{27XHg^36`R57^mc2?1l)6D8!3$htE3C0@MfEM4 zFQ!6d+rYW;E?*QFV5ODfozuXwj!f&iw0lSbn?rT&@L5A^Xwh7%rG18HA!R`a)tn~}v8(6`g+jkuZQ`ps`Z?8c7*wuJu-a@UTG$f11}dxc@cgFs`QyZv*PV1^ z%zV~GRzv+-o@aFBi$ShQU7CbIuJVi>7BR|T`wZ2teSI;4&Y+E~++dno-mKqsjq&N% zk46IxmE{@L=jA#Z`7dJj?09VW!IdB6@Dhpiiq_y^a;Jtt-=JC*F zP3KXpl<`w{or=JK2FmvMbmxWku3Ho;ZBf*l?12(E45=v#Wo5UorF>lHmzcD z4VRuBHFPG>u5~H6Qfrsy?NF$SazF4{HxQC;M9XGNSSXMH0ClAp=casqcJ&?tthvP3$pFLc3{)$eq4m zyHiqLLY*C~drbWj?6gGBFi&3ENh7rAsJ9qa$q{MLhps_ztz@Q|*3gI-d6P}M%CAf) zYudD4C9Ohdd>B+Gm##qgWg|=69gPNJBMf-rKoC+diiG~1qH$^; zd0Sj6FkURSh9P9i;Z96K8^uvv+utsga(zWe;r;Ml$@2Iz7<|d)Hl-4qb?)L0=3;EY z19tRU0pB;k8&pX%3{Uat-oWWe6yUn*A7b_L7C3vWTVHMCd7v{K`{n4M(H6a$YPQ>L zMKIZMTE2#-H$p_njfN?YYHW)#=6@I?sI$XMOKuxhMeVL0kP1wer_m)*=w4ZthKP=w z4j;O=Nl+tQ?{Mtwk49liK}FT zN}iKYQMX5q4fsH5jUW8UhErZ51IJ#O{~gbEY;y~umYe{6&6_4lEjeihVy{;<4=gjw zm;kX0EMDoyBxM#0UNmAnmjZ_i zLI>>B=Cd!T->i!aMpzX$OfIe+uGusIRTYF*^%GsDw&I$ztNJm$mMPgzD%1LPeryO! zUmV{COMB*1&TFR<_$xy?+DpUjtxdnD+(|BVFKo zW*6EHVc~s+z>$ZtNX9;zs8;@1y1uLSieGIDc-_>v6!&CmBsAca9;rO!JJ)1Br-k!U zHgdoNxa?Aivkcm|%7vWH%O~}l0daQ2MB{almzpZJcnUAoAGH%E%}6A-q}uJ+_Z%Cw zWL(2mt~mk=R^UCfp>Ac3IZ_Mf7PmY8ehfvd?yKa8YXPiX(kt6-b=xOxeK2|OgPy*I z{JB5HC9$tuEm=0iv6cFIK?jq3mMRtO<{QVXI(EaCjWjag{=SnR1leD7?AsYYuXi~V72vjV|+{9t? zTYo_-nC|x^HMXrOP68*$E#fFM{pEKbI41rLD;2+YDA5TjK9C+a3=-VqzVv$|12e~k z*qi~xEiR<&&vHp`6?U*Jzvw;LcTqa8yct28QuM0Biv08Nv1sVAMVS0jf6<8IezPCc z(B@cCFFbewAPJ`9@)TA4629#pg{}jo6x3!%f(K7u6RJZLo8OR#|3LQ&l!^LjV?J8fmh6?SZop}Ugpw~r(;Oac_4>$qg&wgy?3{x8MU;(%9c`aQm`J(_i# z(`iaUVfWeI#9i?rBc3X`y3h_^E1hRSv1QWv`%2x-Lq4#4(O0V`&2Hq8RPx_C%~3jXSZTs2XStlJir_;qcSQ)hXAEoVfEn@T zq0K##Cn)oUW7$^bl-O0O3yeB-IH#+`sz$AQ%%ier&CfI26bruI1D{A0*h1Q%^)rzf zjSFpxKJtN{A-K#`bF8BIv48_j<`QL zJ5@(=JVkH*qkCQ(7T2XM@dM7#hlSTvzQec8GqDgIiR|HK6u;f0T0-DufP#Q&C7YtE zHd=Z593_D#-`pu`0*uMc`N7Hk)5>rWv|YXiJ^;WgVAb?(JrQj0qM`-XQx9&P8*OD9 z5ot=rG&yVE56bEUs$Ne6bsq44p$_|~Oq>_twUDpDNXDZTR@|B#wjF2}0ymguGjMFc zJYC_OFf5#-+0?dn%-)E5JpqD^Fz!onvV@!%Ql%|eGPxAdt3^Ix8`YYG&$3Ipmtr>* z9idb&3beZ;8gijlMAj9=%0}E?c$>yb`o8iiSuu~2CJnqgA*mD|p{zE>z&n6ob5oxV zt(i&U9dd5o35 zg*}qMFS9CDzqLuC>kG6#9(y@Ul{Q)C@$Bpz)3G##=(C86{9nM~8#?SL_ zsHMOFZ?eDmC!Xl)i6G2oDo|`yuD#8ow0sCL?jl*;z9XHlxm!R z#|$W`2seL9?M#^q0+F?AwRcxDQ&8r(=89b(dT1W{_r`?lLrC?P(8X7%Yg~#JyDjN= z(+O8#i(JX?a3XM!zQ;GTkR9*i3(6@MTWT*FD9A?MwMTzCJ;DD@J}e|S8f1&v^D&$% zI>^dzCuLq&4rrcn(3N9Vo}9QgDVo~4x3Zlr_4fLoj_|z^`K(zs+q#Tyi3My7njPn) zQVur)(?_SN_olCWLoZolU^n_D@XQ_#Zg{nl-d49N953D8CkiF1AA`lNmV1Fku|bz* z3)uLilpr8idL5!*VSFzde`mwr!HupLb3;D*06BSEbFB>!ar&ipv6}{`nL`~jRTYdN zcF@r!WWU!Z(@%tXpn|`&jcDRz4WNjIO;+|*9!dwMu&m{pZ?Z<21!u~>X{+!tJ=?`v z_O1RxAjFr?^;$d7wt5gtGWv0fhzSEN`!&Vl*RdC6bt|Iz>60p~S~4-}Bb);sZ-YrD?;7?^OLCiL8(L|3YNtp#5O-D)>*0vz0A7Zu*8~*8ZdL zX8mNiky6#^I5*KM4`A&k*_z$YE(oY}9mh8uYci_#Zef%6zsP-eddm1S_=97(SiU4j>M`}PiJ3C1(M}!dcO`n@^&kk~X2|s**eU!qVMaSFBdq7U+g+F| zSD2NaKG7gx((v0zyB_Ys;2DZRFIDa+E|M*3*e68{!K0rauO6dc6aKw*&Z)uGYp9LJ zX!A&$WE|ENXcwT_x3u|h`sP!6%f!84o)1)~+Gku2HK?4Tt`?#FM-^p7kgTRTDt7SM_3mS`r&s%~YgX(% zK4djgHA2EyUx*M|8e~O;R+64wuU&X8v&4u7V={_*UmiC-yCS;8L3sP|ZOH?^+@MhM ze#NzcB#g%$*T*?O3<u%k3TbVaC}7-CsAr}Db|GI z+=JGPuGB1ir>i4F4VZqG>b^~Q`DztyjC~CiV zcKZ4R{8&NP5mBeKM3v?Z!!VClDb>Pih&ZGgsaxj;zCj zmm9$L*`Z_=8_*09UuGngh@Pyb8<+xHXa^P3XpL(B$1$4%f{ZpY*_L%J%UEY5hL$?~ zo1Ga_&0i2JYXi93S=YPB#Df=~BjR3+c-St#7;2L8hx7M2+`(l_pYp-_-yp32xxt=) z<6xl?G#Faacl|jRh}-x{W9>X2;;$A=CpqQz+otZ<$K!3VVku>@Y&O8g+4Dg zkT#W_kc!G_@jzKB6tmeZWp%q1x#}<%f~1u&2HcO};KpeKC56UbD@P-iL`O`>RJ?1r z4|X;15CZFF*@_6QE;EZECSy_=9`7K>$mNcW7uP7G?A!9va*o>(kF?|I zl+0jKG-+Z~lZ)e3K!gaRLt&aOs^J+pvvi@KFReJo-tIlFio zI%4^aN5nR+ZLl4aR@gg{6fR%{T!0kS$3IRVQ<>0D7E!TzRZ#$u+N?-%r4RN#k8Wo&s0AN}#hSV;=kx)kl97h8h-v2>2t$e%6<4y2UqjC^IRbWNNP&Jle+L;;^ED9~0a zBbOT;ks95ghSIP?`O*hNZ4nYX`TmnTaAn~honMN1NH`R}qFEJvAb78tdua@X>bZpM zRX;C-T3O?8TEA4qbDB?F{kL@;*ElO|kX(UQb*GR;bunAb!%QW~&DhH?*A2+5FJ2;^ z5iwoBRnH?`uv}=@`Ici*QtG}HUe;>27ANol7>I>7>{`2uB*Vg06#esGTg_4(4~vj_k59At%&npCZ6Ly6`{4=H!fmbxa4^YmKnpz=yFqPXO?7P_rA z5fCaxszeVBmD=JMyIB~u4{P_?vX@W=eM;p`L(g0HjKUmt4e<=o0uFGQNknU!7P+O! zbaZwNPuU5Mf)JBJ-E-Q5k8h5k%{xJ6C(;Xox_VUoE1+|iRx&Kf<; z;Wh$K1tB8kc5454{xj}b_$da5>*w+PHG2H1W!Oxs!r>_~?^9YsN_q(#GFc=_UwL=c zGv!le^|RKsO(Z*Kt$WIS#LsA(12o-FWg(1h632AVbFFj3R+9{KM_w)DJbMI? zS55z!yc%?6AiMm|R`nplK9O5d6(+*UK|BUCwHJmmIHXBNrAQiQUUN?=((194lk>hT zw4tA2i0_)%a(@hIkBO1z_BQ%oT@`%-Wxp@Nr*|-J)rCJ6;oYu7Za=r+C!dolyu~e? zLk1~D)_2B_sea5a%eLGWy|(+V@zpm^*`5r^_cmlot6#uAF=$F&dx}|>0En1sUQDPj zz5%vZLv(n(TaymkmE$OWK*ZD_XtvB3{;9e#Vc40p*7;#^CMHO+0fxUTjWDbt(<95y zoYT3SCwCtbbYE%2W~xP_i6hUIY%YS?w+J5!3XQL*fa)+7h}SK;HBGofl&I%>)9fSE zdqQwm5Gfd?QUVoAzyGl;2aqhI0aeO^G_}60Z#t-{6YF{V$yoP>WONCgoI?{Hy^OU2 z-}x^RB0hGHh-`&UHN3@YEECdg!y{5o#}ZY^L`;|as)*sDjvP@g(4le*3_*oi%bhCQ zDEOm9>DS^a8;O&FZQQ|rhET=au=!-wo=zL-<{h<{=Vkst6(=2qP5|O$PWgaO9aH(U|hd-HJrV zxOH<~BqAHvv}-Dv-nQ%W0=+*)?wGFvwyBSm1XH12s(ABWHFQ5tn^K~OL1cloun^2! z=sFqc*b~~`VE>xcuq;E(^327P$LGP=y8fw0sAXo~^I9La2tbXPmr&0)JPdKsfwP&b znXW>~*tjiqku}|kozkqibQ80$uTcVU8J;)je*m)_RZf%rq}&^OfbQQmYjuk8*>5gK zZ{SIo{@VJBESEL)whdN0j|THaINvc@gz~v0&i1MT-*g*l;g(CB1~auydj~hw{ZD3^ zSUCO}{@H@X6LXrM86@gFCu_f0zQh|>@gY`i>VmG!E8cc}RP_C4DhSw_8@!J@&cS=8 zZ__6nTS8>kfT7!R`Df4XN(%A^Afvxd0i@8ix$w1QfFhNAVnctG*FS?wzav-+zP}6# z{ojA}{~r|y5jA;w{rybr$9mF#!ABkc-RC0@k%ppde>r8vw8t>J;GAbSos%WE=gj-& z+0Am!%{kpKfnD*pI?cw9t7kW(E-l=N9=}Ary-a6Y`o0HPMww&KlZfdtL8`w>!F?n- zfA9BWl=K|x_Ll)|i()6>7l^o=LsAdCvo9pk0 zKiavog*&@|@9XgII9B8EkL&rz5y%8)s0yK0G}JbiX>#~jN)knQ>1bFNQ_WEACh=m* zKy~ra$@z3#KNqT8&IFmQD#0W$&f@N z^BLhn%(8(HADyZQ&6u8P(MJnl&h*IIvDEZS&Ql|)=~@(WJZ;S_2)fY)56t*{OfvFz1~enx3$yIviu4~s@49?ZVR1S&&3OT3_1Akb~_yM|*JFtMR$ zTa!%v;^SeImss8Ydywoh5Jdxy|K76$Vwj9DNkrE4&IEy--_Zsg_$nAZ?|N+PPVEl6 zA@SIEmOOv1qziWmz@S2?sk<;M7I19~yfhM*WA}Sorl||LPY`|pg#gV+K1WH{9Go-( zO!5;`0uedvhn99nE#Qm58j)DXzF8*Mh%1qmKo3qj0FWR!U>$XfWOdgxx_TPVxbTFp z!_V>9_1hFEpV*TOD)U!^Ahw@LEg>+dkR{W1ynaRwb}uPb0tcs%Bgkq#%doK$|J1P> zc%g2ePrtS9D5sA+%IZq!-j-PrnFn&JOvB=7aS_D3QXA=*YGAi(xtCsG9wWSh;!x;% zP#g+<;p1TGpI&*4ZdO=NGp3kXS6FZsoGK}P4dICOb?45pr8FeWTI&xr_ENnmSTs5V ztU8ppKo4XW$7rimutlM(6e59MPxKhgimfC(2MgfotgL{u#gbXx>mFBo8c6fX*Uvh> z9rxqLR_uc&=O1{)4R!eK&0kOGpHd1A2m-6BhLv`OuqnXNV|k#vhv*p^%S#%g;T*cE zBX$YD=;KvbZPX`PXbkDadr?bOo1YaW&QE4(>US>{0jU+-=6i=m$#VS_jS`&nbBrAH zZk;U5C#O&Ro2NeJRkC_aJd#Foi6|}$fqfoMt+QUyFq~O+j)iWw>x%C!EcwXQHEK^Q zmVy6Cu0DW48ALGk^cHq~nb7JyKsI`)@W*`leAbKFMyr96J$7lF+G_<8+()3GoK?S3 zR$&0Kt#K8VwB5-}3thwR<=?^d8Yft=o5*qZfe05eAY{mAo)zjJ`1Wbhd9p3P6=dNjrgY98gG@x z7wri37N=$2X%p)J#s?V1|EP1QDjfqN!7VFwR7zLarXm;_@}_>)FC%8wj0Ef{E(E4l zT&uC2UDq@OuIzJUi3vEvyRxx-zuMz^@U=S7*_QZ!ciQXF{Ygye!uwIX(MUnK`G9P+h zCQY;*BAXMR$eEvhYZ>%su%Q@S>+gA?!G^4(0-Rk-gN+U*LqYdjnvd~!Sq&jxINimT zFaEdSnZTtzOO^66W5YgV23GgmEQp1!GFK%=?ur5>35HKFQEK1p4x;Otx{9qH{LW7) z+5%5WR#=blE$Uq;EBQh)?7oRKV*DTYl%e1@J?)H|+~6xQTw+t4jQZ{@RC&GlW3=D? zG~(-y&a=xtDI35UZs10*|2Maek3GR#(z1gIezjO;iQ`fm3Uj5r+{A|26OdH*c7{2%W8a~j!hO^ z3&Ft`mIPmU{^0<^g4<-weNA||u^kfk!2cxPMgWG{lFt%ISyf|2*?LVn{>sE^|& z=zg+jDS%7IkZmjo%2Zj4)i6r-r!#922Igs#O2&Z{<+?$Yl)HRec5?$B5(ui7OH zEPLjAgrDn9@-UDSp_FwF!s-ia#SL8cH=h<*NYa=%CXJ}@r$t|{_uno0FqEot6@fLB zZyGfy$`?|`9`(1XjjyKqnAy*n8+7uiidQG(NcEacpyK$*$qvc*t~4*ru&6EEM|q5~ z(rE+_O2?WjL^JDx;qBL~*Vdv41#&gUg{TG;habi;tgLnGHK&K5w~#i6%(>%~wygt0 z%#qu12xgV+K4;6tf|cl6TVDdC@zWG=#;1U>akOBc)LpEjuLp`%iSn2rl%&Oin*bYK zEwD>&h2P(MqQZ$|*BOGdUt<8pmsxJrZ1=_zcr)qL&rbOQYL;Uj%FYzgRhQqYjv%a>x%>5j>ss{j6#Hobz_mV?qoBsbrl5^A~9Ljhw_za zkM}t#UVkRrH?E#Sf$El#%7tdZaEo4{u;m<1QXd$1>I9@^TXn?x8a-Qya;)@}mQ(sv zkxfaw-UCD7u3(RiWf^CfeFmTa1C4=8-Hh~6`Cn~2siY|%8~unEwt7Ng=UNi|63A*! zVXbqrB8Z9(aQLxr`Pq*nRZ%^{0sreSBRSsumnkB)dQh`Y z(anEl)=3#2CR@JxzWiTeRT$Z#MuhT-l|4uA#ULHO)`QC~Bmp*=1NdLF)L3l-eIe>i zlt&x&>J2sGAd5ERFS=F$zN)eNPyapkvL>`2yEz_xmal61)&9F5iuJ+H+v~3%pxGw> z*@KV$%>UpF>h!0fwFqaS$4yU(&)qhsG?HUSPSLmFlv@X_(FQrmS5xK@VMCkr=b{o( z5sLF!i-pgEelsO#2TA%;C~<#?JT{vsxZgn}f-b3&I57#LYt7v6B~W%BlX@0^(ay2x zwpOf!YX?4uy3&t>4q}m+*iaBMn<)23_gCyz&54q3VWW0wTNj=+4$MsfX?(%dP)$P! zPqPbTb~~G{Q<(4X{rVMQV31!&%eV6YDOvatpV1^m{%!VKIZ8y-1nSap=ZGM{N0vih?wWgN4+(}!C9bQQ29ch7s;a$a{N9^IK0Xx4F` zPnT#EV|P+xmwx3hVO0F+7*MGw=&)n z0bTX!=7XOxVzW~4i!v|KRu`F+9d@%2Sn=lwOs|>g3G927W9(DMLSxI5827xUjSyT& zhmBQS?6PwP?@BS_uytwM#Y0w|?3kAjF5LRVat|WpLVf}eSDj>17^WMx7L<=R zP1g*~WV^|a<~eK!4-c{7#@+x@8kY*H%R4qBM7rn-fn1aTC#9N8!6*%trwE6T6sv1y zT%eNA4`22eB}cxtT9|6K!Z0gU%@fat&7|HXpV0A^tT;%!*0X zI9=Fl))s1{_fLs)VC*f6m6N<`@y}GOu*ZGf>y zN;1~Dhru&x!OJ{RwvYn zYABu^6ng`hGN)9ZjE)6<^GYg$`a+8CCo>sda{KweQ-@8Q28ml2Gs~9u+a`gBW+< zr{WNJ^W7dhx74f2Z}b+rK8p`<^jgGPe~VMjVK9Q#W^iIAs9hpB;u~GkOof+J^qn_l z2ql1~(s0*DFdM1ToactBThGMDM@rMhhHJUk{X>j&+4DAVA0VCyjPpJwe2?8bZAkc( z8F;WtxqgRsj8seQQ+l|BTNE=3%0Z;eNHrB?!tdwR+4cwn1_thK}5&cpMGhhcz zdwS@A94nxb1Nh}vK%pZtY}3#8b;|Gm3#(Of%W9z;E@6~{9)<{pb&1b#0Lo3ij;>6> zXi94ga>~`1ip4lnyUpSrfhs-;MDTMn`hvx$Y|vkA^pDn&ZE+uHhsu_bkY&D7HlmdCK}(w}_FAIc0f# zYbTlPt8evu>WVzYAsATJHgQON=Tm(Y4Q>Ttp<9Vix`@s@B=pJg994cvnZYz?OD9>* zdd(a;8FLvBCEI0W__xJXFy3|Tr ze3lAGJa_M&vm0scbP3}^%L*6ck`py$1G>I>Y(8<&hpoO)g$V1yqZWy?zDO7YTF*o&~c44L*uR4U7<=FS-52VCK7sMui`)$Mf`(@TE1p!%rts3r$k|fsBv{Knsf7Cc{u)XY&Y7Li-*oT# z@r|WzQ_ygr?-^IknWZFeBBhrdv2VDvsd(e-UPS@7D=kwJD6ZAuyi$(>$(%zD=94)LJZ z5tpmSOR4F!|SRn$4uK)z~SJ-_Q6lLZxKk` z_}r?(p|4jyyfSB{a^TOj0bzA}UB>WN$>t}m#iv%SR-h+97!s~h3vG#VoYzzQ60v+} zT6x^`LkXC2WQAiW4@7GIYa6{>;?vh(e2z2to<*;19Y}UR4mh{Ik`}M!{71Hl0=hl- z90vRiH|I+SKa76`+Pb*d1l+-`+=nFkWO6>gA?ehGXAX@G4_qF@KBV4M>&koPXuFodE zE;#**>A8`j50!&L(m}jztTF^nnGAajc1v?=Db^!q2+6uAd%8Bru zUk8lRK1>rsrFc3=Qz8M4K*&)Z=nr)3P0+WSkE0M)1=SYWpJMr4lGbn8hrOY(DCtL; zbl?ZK@YYrbDH?R2{+>t7B;`bo)KH?O2=Q2hdd+=Laq=!hOx0|}a#4htCL+({dUf%GgW|WJO}oX1+Dpwrcgi&2uWOmDA+U`^ z{%^E%zk;o~aEUp<5!5H0vEjFcsH>72`)<6`I0R=ysbgf*evCedp*iJIxfm0bA|z|eH49Hf(Q_>UT=dVbI=?V_+rNH)lx08#2?Ox142f6PntvGQoV4LMV!D9e5p`H8CDP>&HY#53>+_??Z(n*q#Mq%G_1T3EG z5+$pPG2tpeXyXoEvNCX+v0c|AAEn<9TiMWZv%8l+AyE>L@$0vsB`SFDkH-Dqh^|-Y zKdL&QM3-e^1JK_Yn;W#=8+o;Sfnv!+px|-eo=S7kub47{(0uMHKGPCz=Z%W|A4Hcy zP#$;XyU|)(G#U0SIff@(`d5A&m|B^6oF=zBc>JeJJWS6jXzQ~n6KIsW$u)f)BYEX* zG}Ipqa@#Kwudl0v{3^tNFk8$*dQ~sRTWhk#O0B|6zvFuF0*iNTZ~dM(M$8d<2bbAsZy=tLD|$<#Q9+d|Tra+J|P~b`nIJq;V*=>CZ2oH!&`2$2?4#xb0D*0(*h2w}W_cliJEsN)4KLolo5(<9L!o6x zO_z@8$*?esc1Ql8B1_&j~H*1uvw%THA^@3sT1a*>X z;@mWT&&sWsCL4?W`q{?Tfyy50a>ixVey*TbU#vNWT1s}`LCV^-U+WD$-#tB%8wz$( z4V@$Q7s1d}G6|ChFUoFY%f|617{{-HJY=f@@V=8@~5P_n;Q5}LH@i8U%>nMieY|^pi=Ufx!wVT!ZXHGjz@ue_HP1O%EUls|$mcc?3dq}fPK$;1@W938 zu1x^Kpdb(Zovja86c(mD{&U<`^I-MZQ;~-?L+i`*z=ONT4`DVJA9G0b-5K%MIq^QwlAol0-?bK`AhQF7w?QfnB~OX(Nzm z7SfPftkU$i4Bm|$40<8j$UPU8d$*kyMC*QCBB7T>@-z3;N6Sz~v zy^lE#3AG~%N{AwBZIS*J^5P=%b!+&<42G@{7^^{m1$uHi`mW9^=_pfFDkrUH{PSf? zQd3l(6U7&HVLxYealHwEVQ{E9z&D)WANzaMGj@nX4$`bp zVN3|%Hj>P3o@0dC^=Yo-o_(*lE1XjH2#FQLuijB za}JtNSiPlr!0s>^O#rN2Mw`yfhk6jCCR;Hc0r5LA014$d(d_*0=j9HZQs?03vHG`6-HF|Xy$p^N}_->*&&f?PIQ!gM3+Y8fWFosJAd~84{*`ISN{&D^sQq$yYXK)9F?VD~D6L4{|;np0* zMD8T?X=Av_W=MCz6pOJO!s0C`6aK-+>1P0{DNS~(D=7s<;}k=tcBVTHIb!$+hkpnzVeq7TboHoX|hy$E5+B*eiP>&B8Xw^+}d zA{;R$_r04_wX)Cu{@gb&5CLnYYrD){2JcTZr>JGr4z9 zo>KyA{lZdDredH~?Oku!fuSy}-?#`=sEaXX8)aRXE`BGc=*MiWjHv)fe1|w^t2T0~ zgBt3*ed)Lhi}sm$DAK4)TJtM5#|s>BD&32~ylSI;IxIspfvTFu*!ZzMD_o&$c;y2_ zVJm9-Eg-hI6(hNWHQo~6thFnoW&2I^(V*>w^pxo};X9=0H{5Sbopz?6Nzl%yCH)W1 z*exiFnV3vAiMlkp{d0}mz7=h%Rhpi{89o=0}gjm+RZVI zgDU$N!?JJ;^UeXozx$@eUl_6cpcqdM{l zfpy518ii!D2Ew*!>DojZF7Z^HwMDU^r#Yo1XDThjQ|?1XWjCj1B5+NZYO4|42;h-0#Wt1lv|v8>a0r(}pU(29LdktYojJo8Z? zwXBNogF9}^iu;<;_upAD%oJKs%~&hCCu{H6Yv1@3O`^9Xr^PCZ@Sb7$roUgpf>44G zmUrd<`Y2{n6|o>ze41&VQk5Q8gKt$ws3()SA*l^#iW0aJ!w_NT0T2ldMBP>0?|Z4D zL7G2BGc{UfJm)LX3i{-PgDgP5-#*go@H!S4Mza(Xi(L9)cm;50etY!XK(S!?&S>QQ zdI9y>yTt#&Wpqrw-JWg$gahz2Ysd(7y_X=aZ_f4LFugh-uYiSDM)uHoyCRzt*17}h zP;X0t>k~ndP?LmDm?=T2>KA9R4rhJ6L#3b`Gb!^b=4dyY)Kf=NK0NfgCUS$U!(ZS$ zV&I@@7EBTHm2x9gHW2BBZX}-B+o7;vYkpq8yPE6&0T*x^$by0=3u#tEwfAN z{Tqt6K#{F|>^?P`5HyaGFqWmmp~8Ji)U*D7*n6w6s@rybS42QSx?8#%q(zYKMx>-e zq#FSxlay?V)XKq@t8}?<~%6|A)tRaTe{gZ-8-W4{+blS)|ck! z@e-lW@LiK?K)?|)rp>20L>H#G+%+v%)_n)|&^I#d3;$Vzp7JqyuU ze^8Mk?i~B@l5xBc|5^Wv;PhPZ{7_ic6{u|N3s|b%(K*HEpH<9U9(LA@)LiW_k#U;j zQ8zaN3;x-z{4EwkgtK!t?vG@lMKQxfa!sIU^sy&_xH6ImUoga$&nf|6^yr!{4|@+O zmK>x$w!&fmd_CqZle4+w03VsK$GdVJ?uimR4+9pC1V5(s6T3D!itDH?`Jh&r} zBE{#1Qs%fklFhaA!)Z{w9i^1bdk5|VPDFvg$F2vqciwk|GUC|17Q#)kq1Z2t=6tr> z)^d7|e)U{qA8Z~T_fAoFdWgdB*b#Yc*kxz( z;aJ~lay}`D3PpNAV`L=#hraBiNrHy0tL;iu{!S+2{2sz*hMBfsl2{{PVV8FxF zenHD_H?B4ObNA#@d~z|?AT5~@hImaXUZpD|Fxo64ve zpQecC7XKcT3N-&C;?Fl`tONxvUgw;jZ1DqVKs5a?_}q06Tl=-b5s?q?-uz{CJ$2oa zJ%VC8@_(Ba0EU+GBf!V<`mR66Hm0Dm$o zmTQUVL@(Qyrh%;{+XE6{7|GRS2Vv*}gm3u+#O1Fjy59TdDaQ=nemQ(O^_^}N;x_eU z6kzZ9^W7(!>l(jod2z~uSjvog74t0<#?#>y){e0;TSQD!`xTA0zYYk!8EJ@!kh1kJ%5V^2dUvZp$D(c!c z=ai{Vy|5LX^|p@G zC~%eDk&~_x5f4cV^TK9QUr~8moH*rOV}wLThyV(ZNu-fCU=9my&&3uo_o}M5&AXZs zW|_ds%&W4)B2u19=!u!Y*P{v#r#10S(|WkkE;Ms&AkUEKN^gj^<}55nOVamlw&Wwl zL}}O0O6V44dnfk<>p|MyicB)&8SrOQ04MQi<>8cv4;~rr-r!vuXFtwL7j{1Rqy||a(P{OpPY@wLx&vRssNnMzR*~)tcSPlB%(t#k-5({2{g!gPvr!@uz88C zRfxVOvV(deO?%;7OnR&3*aohku!9x)vA4~NkJ|9wFALNSgX9ISLr4d>4pchtBT)?Vg@91M*aFUwQlavx!3oOylotP z@zZ@Fd76&$*@Vn;6n&-kDFFwR0Fbxx(>ztQc=7e1_0tosAL0qVp%#2sYO_dP!{VRrx-8?J|gfaH||U1E66 zh_MxX0^N?EpYFZGmFoj|!EzTCle?UaU~&A&qull`ZTY>Zg z1<_`3$!ombXO~^_Eixtt<*4?z97K2Gtpb#ip*>+kN1ib^3UX>>*Ar5+{4iS@Yu5!2 z=3q`ath|?>25FCZDQ9|g>}{@G3U}J(VvVr`wvwn16Q1G?n+q*tysT58brX3g`D>kyC~? z!9Rh9dtxPydkyMRe^~(G+#d6^f2%RJeG2(>xI@-wyqxzKAC7w{%zDd)jxl2r-^NFA z&_Z{4Bk;KyVHjllLzt6P&MVLht?~!#cCFm1aq&&(|J0iJ+IJE?xa{r_q_Ls&wpBDt zLy9iooe!$NC*O2}h_T?-{aQNTo^LG+(K$B(S_Vsl1HYyauQ2_GJkjQCZo^Cwdx#*t zJ`K;r<(bh!qh35o=+V%eioO#>vUt_;ujLds5yZy`JQ}|Lz{g>$P6e&7W2IF3SjFVW z=YiMU5UB`B5d_GaEcV>|7_4-lfQ1pW-uk~_T20plDWMWzSUFP!fLX?wR3PS z1fhmluBb%ws3r`p6mKa^DbS`K)2tLD`A!f0AmI+oD;s_ntHK%eqCdJu-WrIrTW1ZCT#jZy7i#L9n<>yjpruU zWetT|lwA?*hT*_*paQfXS}cYR*>0A7wabIWiiL|riqT!gy7pK}_=bPg7C?9%N&GmJ zZ>!5QU^_G*(8%|2*wtEn@-DC6>*ds1i}C4shV-abKFuQc1wmfs5WRYvFVpG{sA`N| zS%ZRmzDY>b&LHHU9cmaR!P&sJm-JmkF7YFK_+Mwj>~P4+;pB0JZYdZ{9^zb%u?8)#)j>uoJbc z*mVVb7OHE-A|%ypH_h>|e{EH2PN_OJ(o0)RpBV{-AZEqHDg%VXir$z#HL2|Mbt5c# zE=E~R1^9AMXiR|v38f{x5zZ9kAReY)`oV$ik9e5O3WfW@mMZN!caCsmt#dEKiRRx6 z{548~raTa^aE3_`odm39jGQ7xMNUt%3cQ)e;i;#}EMR)!iqvSK!Nfm3-^uMBqj=KW z68(j2*Y52CyOo9%fi;}uX4G~gMbzQU(@hT-d(%E!Y%Yc6jcEF%YsE@*PZrQsTBz)$ z;wup;pw}Uq8zyzysBO?#u8{U3)-)TKLjil%6(!# z(YT^xOZy0%3DKECKMGx^@VSZ4B%-?tXNuyl8DB1+qV^v_L)sivckZ7)85?%yx8=Li zrWhlhc&De-h16}i`Z!;eUb6GxDUJCfuKw&Z564Lqgdj6m3G2wK1 zpVdnv;LHG?AhmPgt)M0CR6JKbF!_)Yjx}4iavVp1`!@W`gK&i;MKY%UC1o1Jj-tn;d73 zwfdIhlV*Sb`=F#=KKI#RP=nNt$(|0tb$3;slc|?iAI#;>=jZ;g|3XZRW*-`yQ!+EX zj9rpW<9>P=TW1GmRuf1{dcF)D*M%(<%6vHdoQN9V9|$v%?M`r2`S`R7ZiX*tq?3!> zilTXEV6eSbAK|~uHoK0_QJ1cj-M_gbG9D!rPr)B>JQoJEU<|xABkYMd3R_apwl5|W zzQ9K6)wghwbU)XMVcBRTXwZ?ES@;+=rAD!Fzm!ZH%aP-RqXli2XI59A$#;t%bI)rY zq{i@O?cQSz``&)Ld1;Z3JWOTCoT@zwlZ}|ExR_L$-mINJV{;mCs~l5H)@d09(oYBI zS7#@iw+qz-sf23y)$$F>^8SC|zp$Rq?dt8(s8bTM01=ftoca{jn&UhZSc=0)-KKlj z5=J?@WNyW-G8ufEcIN}Qc^8dmszdw%|C(0EbL2CBC#}dTOF_k~i~Qf{7w@&MxtZ~h zvwN)s1^LvQKMprWbU(pmB8M0F3TVM{Cm<7EZ5^ec@Bl*^H#j`Jdhd)XqxH5{snmh} zoX@zkf|E~y#D@7z$0{B?j>_sU&>NFf9q;ww5EsWTRsVVR2O`E7VGS|ELg1^sR+#Iv zlIddcPYL67-~%ZzOZCMYV+R%LQVq5}wnhrE$~2~+c$-&j?{;g4Fao+TA>44kK2Am> z;TS09quT81G!YqQVvgPoB((ysbdYa4VAwT7zSr0b+-hyUa_#kia{phc7yAU5YUUF)RIJV3#O??#d62|Jhk}UBs{0ix>}}8?TDrMsq0%d zQ!rirm#Hypi9b?f|63d|XKM!hszwkR^UB%M!kS?lxIL6E{K`7}&hUGM&g@c7iEADh z zGyQ_%)sB4|AiW%7uWbLTl~EFQOEOnQr6HG}s@MhR*XA}6x6$bno=quVs@8>9OT`Gl zpY!&>)QNhJ&lbGR{}=+czaudqSfNlw-Bh4ZK7f7*bMD|CA1kK@q1MA?rnTe>`y}^}}km~n>_j@!aw5aTHH zw24AG)6ah!Qw8?8u6k0OouJ15 zSibULl!DUsV3>Y~V^2TIyHmRT}Rw$dM&JKhS772cVP zk9ETA-i@JBox9^*1>C!e%ym)1;1lwEaNpC+S3)Z?OT!gGg2MpOJg9^N85NJ>-R{*~ zk+XC=)AUsI7-P-1WBWxDRETt@7G2 z-a;tW(L25cpx8c@*f@f3FBgifrTkFZA< zH)cvt;mI$pElZbuNfDZhm>dA1do$up=WDWTOx{O;`^DYp3UJAPhlL$ZxiyhNX58)X z%sv)aZqf^F-**!QNwes!tFdmr#hI=+eVLXEZ<-OR;F}xB>2j(}t)UpXdi$TSFapPa zhlM!{F@s&d(z6>OEMVZEGtmaMkUL4{NMO2n0uonxvJW{u_@e|&4U<)>ITYkOJMl#k%ZT1JNo`z6->usN9}lYQHn z4g4oIO!=&_mnqUS6T;5i=P-Pg(#G$t3+`ta19qF0OdB4&#kp{9s`#3aCg2}dt6-qi zk?S88OHG#`M>M3D#MV@}C-GQW(tiFG%GTR-Inw=X1M>wyX%pJc|MFO`IaBymvVMi&>Xd%N|NcgpyUED(4B=LV`-=&6vdVAim1^Zl{naiQM{?-aHvC?Wk}AD zFqMsdnU6lq05Yn7?gX~b%Z@o}6K+}X)F|m@xq}RQtt4$z!|AMe`r&AB3VoKPLChg` zq8Ggb2Dtv@?{}TN8#!o~>S|OlTcKZ#8GJZp*qfy+Eetvn?5*Gv}t3;Vev4YuB)3vFbgnFTSf>Y|BQ{L;Pyy}T>VZH(b_gt z-nxaBCvVA$2;T-Gr!7pu{IyQaMFOi` zTisgrJYMtM))e7ewc(#bxY`I@B4{#i{+1Nd5=n5_^YMjQIuRt8<~Mf#$?6vMAM~(p z7ny^bGWyYtiEW|5yLt@dW;EZ~Q%?G=SFq zLJEtZfE0`W5*3S20Dnc-}{{S=S8Go!%-^kgY#{a z%Wa6wvNyPw+fDuZ>T6G`yAUGvA9W^3UaSfHT(HK!F5@Hn#ktbOhL%uA5wxv%Uhcj< zEecy9JycEeIJ-PjizE`qAgv}l2ivH6;*$~z_3)DQ&9@%Mh{tGUo7mxSM|7q^1__oD z@@yxzYKb&*6j@24B4xoT1SaFs30fZ>)fch=0}6$cS$PG{b1nzBmL{TTs6w=lwJjL0 zY@|35h_Yvw0BQZ=^Oi154!0EuS_yQ2BZg>Du5(g0qxq3l<9Ugb2OOGX3>^_BEnQx;)+`h5_7P0|H2MCxnhSU za=~Hy##(`pSmQWwI^LC-96rT`bxfOI(8Lvbd$MJEytXuJ$g!fed*>O_f{&^b{jfcl zN6r73NA2D`<=~tBQD#!TJXWgx6-mwU{_~>ppX@N$-t;~Ss!>WzqqL|G1rG_{OQmq&+u2=MiH@P#H9NPN&*NH$#?_kty5Ksxe)j(C4dQ3l9e{zkpT% z(+RWgPsH{t$>GixkZW9aP-paBF7eCRLc=iV;s@iXeiF?eL2N{HVh#n=*;>x^$6BwY1Qqk>Uv%8fd?JianX~A{)wUZ?%~CCyrWni|wI`EC5_l zl==7{znZ>c6(mz(!3c$87A0~EW9-+6W~yJMKSo)zL?e|oC6p$CuGfFqfp6 z-{Oc_*N|)B!xi{9Q{2n%;F*1nh-rbXOaoW3`B>H~lO(`ZH~9^iIPtY;Gl>@UNO$<> z;PIgr$0BoWYf)9xj=iMyL<=+-mZ(xTTBdBoZ59G_cc{A4kE7_Jx0v`!hZ(|iwt76r z;7E%Sm~xauJ}h_&UYyN9mQZ^Twu9R-jGW2wj@SABGyTS z^@z@Fylb^3^A-G{x54^VmOutC>il-ObLCjT{+M~z0%93aQEOGt}bgbF|Bf;ED|j~V%t zhGKOh6(NKrK=$#{fG^ltxx&7{j1kNI3d(I3G7Rg%KABach6rn5(KY>#36tNLhQ@AE zvR3$Kb1AeqYAEDW35~sUroRlc!maphunn?(@Lg{~agX4lYllK9UztjEmA0SF)f-@n zhZv-z$5bVtk~4D>@l!_)PCWdIw`3Gct#C zrITsPBsj_YY5hti3(ULK0uJ79H5naaA=RcV?)T0yns)Z5LZ8CxO=v&#ZYGj*~tf0qoGrkuk>9@P+Kfa{5qd#?rHo9KDlxr3@W z<_)6~jf+wJ`C27w)#q1_`M!KTJTN`79vz5uw6t88jU*{iTXotOWd2y-l058Wa|&HA z3~@L5T)P`H{wsGQC_(+j-8e7&7kA^(OQk>UGco^SpQ+$o6`sj>KtOX9CWAuk3}{<+ z`M4l%JTX5M=!_RRu53Ftb_=#NSxM+dZ6(LMFa!#5Sx%yUrgYf=Jt{)fcvDU}gnY{( zUy^+deE49tfh17Mr0H)FVZ_duYr|VErB12iC}beI)pS^a)Nl~bU7Epr%r`T-%0x6r zI5T*y4W{Q^y0c@aNlp0QI0pkljw3yx#YviZShokqPf}^7)Jmv`orKj zQ@=a^kXOY}mA1kwJfa#y!Wz$!2bGP?pR6WXGwDms=88m&n4fOqH=v^8efGF4z9OY zX<;#q^)1yQbmQ7Xc^)x?aoLHMN!p~xYEA9N;doRkdSO>WM}2RtjX!jb4N1c&^h&cb;pD1`>eN68*T(;(7za{kzoM%HWfB|H}CIE1Hb*|HEjq9whxk!SAQ)U)AzF?-e49?$3M9n?q;{ zBO+Zz68C56Kilw0+J5W30q7hu7JPXON}+0?{b1Q?jsasc($_v_XhXSD;M)PND2H@M z?{hjZ`}p6(U)M?V0E`1vNgsYoQ#o{#C&l$Xyc=fEm(PT^T7&S*6B-^`0Lq$Im*nDAJ#IeX{a z=VDZ*=l+#YLlt|_gGtY2<#}ahmudbu19QT}TKRvzO$Y@@f>4st$SA;H#Fh=hBD zJ<{*pUgrHU+h1?0M7*=JT94xDd>A-d*%TOfWd+Jz;L}2Ugmq|Ll=Ib$V_bx=m8av5 zn-QOD;KL1cHI(o2!#ibbfeML?hzNUJ7*hlHneChYZZmar*(+9A{yjU85FbS7W+h6G zeb<)2H;sdiTvPnDdLiq5n?T5)-h1T*OZX5v04uX!W-uU-E`H7RQ$s^@@e@_H9C2lZ zOfwV&sIuG=5A7GoSivLg&Aaoh`9eZavJ840Vq)`k9~1o$$gSH84%^<&{<@w@(0g~V zp*E<3{P8gS;dMq0@Ru)e``ofY^j#P^b)LXpu;)JRYx;ATXN zF(!0>mk85_sd9;R%qdt+B?JS(AwCdVdO0t5X`@3BU+I1S^Q~Prc;TKGDmA@B6zPZ$=LF8l=Q_o0 z@VcQuwko|^i4hVcB+g}lv*!g#Y~ipHy? z(%b8TZD5KZuEiCM6x{~$EqJPT)Ts5}&|bUy{@Dz3&N`nuoZ zDy~*Y@@)yiw1&|)A5oM4(-ifhk=3taR+crff~Dw zf-E!=O7=z=&xHm$dosqRSYP55`@ryq-Pr=%E(Hqf*!^7m=x;03^$#ZwN(A~BAWxNK ziVB8qECvq)e4B^l#4nmF6sXVn^TU^L%v*C%;$D%`2xFW(i=8e7`wAqP#oiznBE)V(@R;S~9Cyp}?PG3E8 zk4ojfZeR+R7AoQe?^~lZWVGb#c;%g{TVEmm$ior7*Asx&OG7drF5AwHDfVa@X1b1- z$rr$cS?(t$A=nb+5*GYnqe=SO+tA_uuOQ*wbfpG%JP5d8wY_(~#N}Ir^#e#*WYXmc z2FFd|%Oe%yl_ni)+BXsofugf*7y_2mCAHSIuIXk(>TNY_Y+vOLV_q5?lHpvfF{*+9 zz@TP7pyJN(n~X6PLRTv}Uh@KW_6YQQAstblY?c^$9?&B9=q+ii9=R{yL36ScZ&OD5 z_}NlI8j}^Xr%yfmF;!$Cd7(Vcm^KF{FiZAylWfZQ8NyH8BlV_-0JbnY%(G+Mtk}nc z67eSti;q%7GJ@FEd8I6QUMU>546%NYD(bsBijMUXIxU~cdABGtN4Sl{Ky%;d9d*dh ziHrH+kR<$dz=D9aPO;V}fIxE5*N2+xPjneClXQhcYzLHNkC#ttZFhYd@llj$J!|vs z6`kM+avLRn@4rEpy_fqpbQuG}+^{Lx2qGrvhQxLx5-w23XA7)^v@` z?l=r8lh+)~gsIiU>-sA8icrvY^?Dg9nlBXZHT$dm!6FbaWchyq&4IE;ZNT)8lSRUA7VZ-`m)WeKG6-VT2(v>cjn*6x`^CS zL6s2ou*{0}$*9n+mthvTov(RZW5g#atIp019_LWpbq`6}-6nXTcMYeV`WrncGS%X#FYC{`_!U%-t_wC#Wq9Y=g%?5I)`Q1<*h(CZIW{d%5v z#ZdZNQKSjkQ~cTAPxOx)k!lj4$#|>NILgYPoWi2yG`ylyH7H9n&=_2&k%w;uYYZ%z zcTn42?|FP%dPm;8Mq-1vOaPS;^~k!6p2{eCkyS*z{<1q>Pnf~5;3-Wi?=WKb@IeN)_daxY+(uTa zuP|orM-}`}iZXi?3CMV3my0ngfU}CD*A^?61CcT!tSCAAu>)cOp`BgXj$Cbk@{In^ z?=hd%4kzD*`zH$Yz0C_s!9NyAdi>LOqOQ@xsE;ogU)U_Za4cTY0mwb~R)e@_YB396 zk7cjYXy^P(NcqndkRJWtINclSzHuS`w$@Lw%>Csn>Om~YCC?$>%N1-H+zUa%%k}Z> z1LqaTN3Vm9%)zn}PEhX&eRor@Y=Yb~FRh$!2Mc?h-4FU7ZU9C5P8?VxeFKNo@83S9 z-hyk=p_GK76}e;p7x@u|5!5nQ9g|2fZ~trA#1n|phb1Bm8B@Nee&_mbSLF7e7g>k@ z2beQj3e;8R_nrzG75R=JrS=&hw0;y(J{BKp*R)H?E=?Q!XTn+DB(yNjlvXH{!tY@PkXLar5T@_Dd)c=p z5VSqqh;%iSe1=>gAMDBfciFjrn@FIt|CmV1yF!>cXR$5FT%-)0k77{a9qPy42NT<* zDbbmt&O;v$#n7L;|3(0a$+zbAS9KEW7D6`7=R<-!@Vs)N9Gv)6P%^KmYttC8%%4`R zkBZ1!v$Y53iJ@NUW7DmcdIAVrxN}U(ibp6IK>VeXw&3ArDL*fy5J)YbLSB*kGx=sy zsp2e&$`=dWp)4xg7CeL$LF=`(nOf1Hxc?OYibEGS<5CX^1JID>ed4E*u4x~eH_j^| zKvSAbh0wGWm`Emn5oy*d&u8bwQK3i1GUq&Vno#Kw$|xk(K8*764U$}JSwm2?l-+U( zcwA4}_rGq^eC@lcV2qBjg&x4B&|@-|9;}x=XaE3gJl#6dW{3TUb}tATlqB%kF|l?v zo!@@P%{+$-I>7s2xogR(2@S+;d!@FlkC_&I-(f zUIvLe7PqR~y?WF#cqV*gS|LoPJea86^QF2%Z`~+C+6AmBI-dkQy@LLUiR7+6ZedPT zp^+H%d3hN3gI1NOob;G5OND#HnMIKYBG)`Hvyf@bq29Go=V?6aY~zg7wEKqgs0Y0` z%dbH!O4BdkKElP;Cz4On%Gx!WR2qdYxC@+B+4O@KU_+*$7MZRxQF2dstigm1J)bqF z0i|0*EN!4L5%k?@z{8fxuBOD5dXKQfkl69$mUl?h@V(5+CL9dKc09Tt()*!x?efEF zbKz}Tbd6S#JHpxPIi`_5V3(0DQ$F4=BuC5%$txBBkAznaiRdF=wnGJ2sZz#et@f}x zCQ0DaY6Lvx(p2C;2`}cDx(j+Yb2c2VKE)T|(cyTqCqgy1HzUmn!B4WhNZ) zr-US!*Zk^w)VSBpL4+d(S|~jR|H-j=L+lsFCg3Y$o-HSI7u8;VY|i*}`b3!ahJ%uL z=*!dK*AvKYM}reSDLEyK&sd0ryLYk49~F>RkiXg5OOS$*9&LYflXg4b_}HgNvM5(! zT0=1biNhs$8EgH23&(af&lFLc-txIv&*$&*^a}E64N$3m)aK4V6xtd>j{HUAwz#$U zcT#9uvb4HhZdZvN8inLOb$3*zdU{#-%?)7829UuZLijr7*;)FlH@6<|7Sm#8d+o5< zk;Zbx%u5)h=QSZ`ezNK5*R5g;G+`6!AVp#{rhrFQLcIdQcWAv0np$5Cm!KC%UL>$a zWE;qy`B_6Wr?`oa{|;vRL}L-{*2Y=g3T7FXq}DqKr+Jcl;l&Z;Kb4yc8dPgGA1VKq zVWj_#^XMJPr{h)879S&_VYk|Fwz+Ob_a@vX*r^z7dlQ{`MT%<81QiGNJJWmMTdQG1 z3E||FeDz_|_iQXO8`%isP1?IdL5z8>(2=_dw|2W3p*3=wF34F?G4 zb_ay45)R(EST`#J19T)E9P5Hni#e;;Z1AAZsKxaxqvx~j1KD+R^ivzTf*;j*y~;1p zK%#=}YiY(2z6ld>w>(OESr&Pchs7AKgv>@Yj!YAsYjaLXs(K|+MQ=^Wdm|@$ zY0$>$ypEDC6eqeFbsoft=ia!Qh@6wSAIf(g)*^&dL3_tGbhx$U6hPMn3kjgezI^m!alc_WA#?0@L^{;$KGWe#M%2ZN~aEg{|;|eXzTB&H~%e7H}c=4 z>Grea$})0P9(Hl#K5X9&$>KbL>^)K+;bvM*W|xc6@5J8%ool1qEv#cv7c;_n9Mwg} z-2V+Wo4;DFmZ>JDDYU3FmdP{CK~mJvbw}^FU16ygEdIR(%OM^d1sUR?orC-A_}2M{ zy+`=3sgxCjG%!v|o|rqrKO5fS*wzCV^B=z5%krKrOv3l!SZds~t_%b2P0^=rP)(pQy^jbzPG>`U-T;WG_ie1B<>$UK)yrH4+ky}D zcOzkVu81qD%aHpc>~_Z|v=usO6Nnz~bf?{A2UA44wDMSKm%*iNF9w!uZpa3UT>M8> z2o#Tk_bujsX0)9Y`1~K|=}Mh#EAP_3Q^RD6AtoxGo@*T=L>-F9=&b{l9Xx?8^eeLS z@eLx5UT;hof^wze5?T&04sGawPZx_Y$&Fh11~yg8$3u+j8zCA6im*Z@SJAmu*5#eG z@_HhfJn>gPO!q!hLIx&H8mXv5A1KjGezG)eDhll9+r>~taUQiwpkBuuuO}RxIp=#t zZL?1Q3VcDacInChj5pv|z=d|=m(0==-|1-1r*KLR8d#m}2aIegd z{?!A^&UIMh{GoE%-ylkn(?BmnqurZ|c)Sta@{v7_YL?}{AN zBKo9LqJP^``nw4)Nj7E|wcnH4{6FzvH=zHt0YQvBfHl7U@nYjU({IJ$AVc}DfjbD0 z;ur3NWQ2??_yNp<-!A|}AyzDc;_2_@_%6uzz;mfR*^_|vyk{$Vwf4i?nwsnh*kQ99 z&X*1Atq>%h{l<-#=K?0T?os~ld-b4%poLFF)%^kR7b1R-DDlDpF2(%_iS!$!1$J8Z z99$4seLv;8llJ^D)KhEwV=Oq;K}f#z*T}yc!1bgCo;V+M!yrQ#`jIWwQdG>6>7xKBxEs`s)%7btTahrG4t* zsEC~SkqJ4{+nDwKIz_4ntZ#+Qlu)>`7CJt*2kX%9w70ca_JBX|n`IO>i-<2j*ORK5 zb2ETgt-bf`s>gz?UOPuGxJF ze?&MD-jrFUvbN-@+Y1ct-9_Ovj!KvgQh&IB0uy0%5eLtQ3 zQw6k2U}B;fDyqjwm+Ne?@t#C|iyr)cfS+?^ctFlJVyj2S$MG%;zJbzWrFWf=z{UU~ zQhiR}Cv$oD0Yb-H(V3ck4m~*w*z8icBu8|F!FJ49sJiZ4uEoG3ApL<3{DFmgsM2qR zy+*BFxpj9Q!Jqha1M33R?j*Hy>kyqbC9-Gv_NHqcJ0e#eiwL_1KI}m9KxBW6TZy{W z9<6iF@AmYR=Ah2m6f$0op{N`>vR|JE&mRo8emLGTGL?|~9Gd-LSazEHycOuTW-eE2 zFyBf(nX0@ea?lrwm>xWTS5dQ%2u7$C3hivaR}v8)rIP+nvnSx}OYu95xe=6R%Pq>R zWQVnCCew?=KSUyxrMu(h!>CPcxbvWnA-G!aRs(ZE=1^jwcf?Sb-B>z&5ftIuElbe^ z%QNgh)j_s$=E1K-5*qyTSZ=DniP$er9K=W23Q@p<*OqOTV-Ly=w1klbHcFw+Z_gTr zX^ZMxyHu;yFqBA8htR}1LkHwA(zgtg@7)#NLO;D5$gq6}Jwhgj?}#abE7X&2BlGU! zjS1Qy6YWEt|lgz0DOfK`?44?0%zXBGLl62g*fnBK8QFgmgIbql6&mc_yL!Wp?M2HR7K$0~m5l$)P%ba*l%#O%VZjYK_-`U3Bjq^Oi$^6T=p| z`ML_VmAr&gvZ-U^j4!gLk;Qj?TD)Rp=@O*- zGx3bvdv$V|q>Mhi$V_0%{FhfHFQh|p?(n6N1D8kdqw-nuuFf7br#2P2qBS%l6S-Op z#asi5BYE_KVwV}a4+4Gk9GFo*ueO>w~Dk9#{B;meA2Lm{l0>DFmcvs{(i12&A{!Mu7Cx*9+3!Ue z3A3t19pvggQgj{6^%4@s85%Kk>Y9T#xm4e4+Zm{XyS0PWreq8^+TBPAJkd@{g*wud zUq#U_74Cyy2c}?XpgY=C)L0eUH5!q*mgYsZpvQxbv?p%mwYJ5{E&drJ{f*F1untMXQ>xC;0~rKe#S>oFvqvhAyV z>$fyDoHJJh3pR@-v_8A~-M`9aRwgK0-XHYs$zUZkOq0x>CFg+aVTsFL%3Dj2L+T)u zQM6IP_7giNNy^(Wy z+OPX!*x|{I9>>;zs`@~kwEq#2?o?zIlXNwgoX*GBuZV#V6~#kjBsp0tL+4Z#U8*&@ zZdC^n&(BN8NJfL80G)UghvkKqHva25J-%$8kZ3)vcr@ne4ON4xNmDjS;Y7@*vfOEG z-PuU_w~MbD-)^jC52wqeYKG{2mlt9m`5ET=RQs`O#i3iC2}$6gzCUimR888{tPS<4 zSMi!5^$=0PxCa08NQtmJcRgSp5WtP^%-A|^jnk3P?3oZhF80p2;!=Y3(_B{r zuQ^^RE#;l}fbo6MCukM===jM6nWBCUx)Okbb-T4eJLm$NQXiUnkY~Hv&6C?;U#6;; zYJGz-_`RNp-VCZ97GJIwDRanZc*&}BA_GKl8rl3EvHAW4so#Bd-a0#|+@nDCw^HYm zH++=Tsw(E(Bwh!+q4QN^Q95)aE40q`7_^-Qjw)Y=@Eqi_zhKeBqrJ+Okrim(ba4t7 z(AbP2wj{JdnGz%2=QUz=@5lv9$pK3u)zP`(SB0w)rmq>J^VNlo8hv05V!kRG#H0XT zxuw|ysdIseJOJ3Vfp|})w(|p#*qS?3SlunE9z>t^ep6Zkzcy0B`T7~G(m+9web^eT zL%Sn8s_$(FaCcZ8wQ5Ov7OJaJ`bq`0bK0cTJhsJS4EW_7V=?}PE?dRPiqqXM#v2j} zTh|&Wzy|qFiJq#A?p~~8-KnNHmP0IlyX>6O&BM|Q`1GK1Ulr)b`8y1A_Ba(gnwx=i znUCMZ-no~Kz%GW}4vY81pdiiz+${iNfUB^hJR=i) zvI{rB9il$QbNHyTY|_k4e60e3T}^J^HoM~unCrFBpYsm8n`wm-Ak|}8mJ$*3XEA|P zV-3Y<^=&hz5ypt`*cR;rkrMwx;`pzDdbX0EQi(2^N3(4NEID@6YT6y0KZVs9CaD`# zJ*0{?qyI2lH0grAIZD-KG);oC=aF=0lpj>%DA|vOsy})>zR=5AQMm%_op6_oGaL`s z5Od+5hIfY*KkPW)nkCDsmAQ&I%E&=x=3j=h94Y|aj(zPJ=xRnGS%&?4!g7@DKUSG6SYN1%>2e?8s}Yi_YvzpkKo9ccKcC3NE<4D4pZZDs zNbvH6`RgDaiUi7TY*$HfPTQrLsYVGziNpZg_rJ* z67mc=+nPJi<|bi~r#xr`{gktYT0g%P@&YoF-A*6yN1ZA~8*qGSf=m5**p%3TaaUw- zl*{&rVq9xnwmG8&J6f6l-tl56{YkVhG#QYm+me<>`)nMEk?HZ@71-##t>P@6&Xmsg zrf_o!znPDWAV8}2A*2UJ7y5W|e=>*L0No~x5Eb1~(-%tUthBAQ>)j|E`LB=6CdqeO zg=N52gn9g3zVd{|uH(8Tg~J@H8#tDhzuQwo{Wvf|#x}u-jF%7|=EavYBXHl?nv2wa z)qMVT4mbY#LckN2`R*rtA+E$WNW=<-iUil#KVLa>4k4Ar+Uh6TYOrw81Hk7 zhz>v33%wO!0&J61I2GYWF#B3hC8-89ndijIO~a!M-oSe#%pS+M`0Jj>+8JaJyw48_HR z5#Rc0%%2cHV}4PXgbjc~cP!uKxx44VbH5r_*CDowuZDgYK|bMj|Jc$FFIJGqW|0z&f{g!0KHOpH9F3a&0xx z3qO)6nAAp=MckDbhAYHocxN3=aM3NH+IlO(0B)KgyY=1cM2C>dToHb%j;jOb)x&qn zs0pikpby7ji-1rE6Ky+Y&zek^?IzNZ^b(wH)}Jy4NMDZtSFk^h*iM1hIc{IX$NfT9 zy|a88gVMrz9uDcT|IoeyU`_)dO+q{U5ue9 zn5o;UA72_vez;LMxwiE;HxyQq!Dne0MA~B`^J2`Y{b9Qvy1l7Z5!aKQMMo!f!*?>_ z{%V1C+baA6HLtt~Rg%+P=Q>$gAs-G7u+$87Y@fF;tyq$wt`RGgUJ2n3VQ-64WMvSI z>=KiV1o4=0ovgsVFLvo_<7^4ZLgr zx^2az6$N@kKO&Z7eT{UwO{TW?w$`4X4~@=W;cJk0l)_2ExZ??RW``M?5q6kFDhpXI zM?F;iN%V;6oUwgtL-MZ`E4MrqL0sIcgr$&?)g*s01tCC)KuhK34f21Lv`bgGckH*} zS3?CGcEh!kt!J-{(UdyBOMwENHwpe-{_%iMsj)wmLnbxMvw)Nw`^P($JeWjtS4%PU zs)B}Uo3X4fbF{E_(kIwvVzXTK41-2yTs3oZSa{4ML;eq7m1(IPM3Z_C3Zwkm@Q%mj z((8@w>q8BchC-$=&}D+(SC);~!^R=2Z=t8)1JDiSQi6}=^R6UJCB=|U$IwuJFVQ}> z&JD_Qm2T#x1YG$CbgRSUc!aauWcR;LbEmp=?K4A|@wu!0Yc_o6N z#rb9}pskI&Ak#*rsZzPnB`bY&_O)`cyf3u^E4Zs$u5EKmTj4`!Z*l;HsqP^5)!@P26B@eVc7+gPlBF?`?d7JJ2rTf zPszKn1$|z`-j>xzyDEd6SyuXt=u)*E1WKfO4k)In5X^erTS8%nb?OAVq0aBOPNb%{&t$xFD1I1-q9e4g24ae83cL5y9}Q1lq# zG0pGUDrH+2%sWae&gx?|ChWQ11qV2_aU$GAk#%F}vQOlHvwczfNc%Az-G~fi!3$MQ z;4@2uGv8CM#C5`r3pZHZb}U|m<5lI^kG~}2GIEsV*L`Nc)No?kUC33YmHV^3cn6~; z19RDn(^bZ2(GQQF;(UBDo$-mc_;K-lyVXUmd7Nk~S20o0(hcCwR=0}kZy`{>9Myqe zSD#>%H^=MzGKRilVUHvKYLd<6m!-!uXvBPiW(WFC;1GE;Mak$8bX*Uc7iVDqfzi5Z zgvH-Jt0Vd@oT-douBfI);Ii{2KXgh#4G5;ZqZNEjGpHZV#G)=7eD25F&=BV>mzN&~ z$F4rl!OqgA#aHXQq-+^yOsvw>G&{zn;zY~u;Y#wUff0oybt*4+=`YbG-3Jkr$5p}G z%QNIS8l;jmwczl>l9jUF+H$iL<;&6HO53dICu2Ip$C#eP>cP_Y6N+zok!rCzB`{3j z#!eL0x2>c_^aPW!o+*iK+cm-1w%wmo!5>$E#_0R zdc2!B!0oM^n8^ePC9Qnh{Z`lAZ0jQ|_6JwlD+*uTs{^}%)V)gCm!y*}4t5Q1Q3(id z7YJr8Ft=POQKRio6VDq7v?;h=>Et+T!l|O)L_O(JNprQWKt{sG_-K2&qJ@a@S#;kc zQ+!*bpH_Wxs~KU%=a6%KnypfKoleP~jb%asnNTwd>aoL`pm zCfV?5gyJS|+!$eNU0#|D3k}+ju}Hh?75GY0`qt1ou270jm3y`hjw8iI4AWQkGr@RF zQ$bay9m||;7(8`$S91tM#qnvqoNt@98atjO!jkk#wLmJgUx%0bBL}H#Br*yZ@l0GB zJ*f2FK3{)4aLA><*fVzi#VEgTGvW@>J<>p`$1=0Z_UwKW+wle^&dNQ}BNW%m`^v0lCEg zD&E=iE_4ONa;7DbOURdZq*q;jJ&1kBNXcRS5AATrq!h*1Ufu}_28Kedap@1(ri1HQ z9xS{7jB<{RD$z}#Iiq&|o6*^xk&p9d|Ds6r{M)6TkdNN3=N2kitIS54+Nqh%j3<|v z^VHWGEGF6XAI1nyxV4s{JgHE&jH)*}0)&kf;##j3P+;bU4~k#Tu0E5#?_N_$c%XUU zc*V}>Q^|$V()2cOmA%5r(VkA|!V(N(wUlhQbbXw$t-MO(WIOB0M;cSt!j$z8e3Vmt zv6tNVaN~ecqo|kM@Bvm&)^f~{;0DOFZHkS!cM?H0kBFOAP!xC3j&C0rItvc+I4@8# zP6>V1%P$23Jn1G=cslXQ>#n!z#7)=RfR}SSrBUPn7W>n=ARY>LP)N~saQ#e%D5@$- z5B(-R1wc&h0>a8&Q|-3uLgaZ%SOsJJGf?5>Kc{QQ@9U3#vj0g;gKSIhL^FX?D_42h zhCe$Gjp+H%2NUeK&Uhj@FZ{Lz88M*F-7x${7k7aWs`Pk1s;DWPj+31!sz2aUehFD& z_A=V8{dK!jLej$Nw$Lw_!pSV6i)FEv$yR|)i(3k*aO5zBREUWU11$%J&Rjy(b6vv5VpdIQY&`C`9g}@InV9@? zX895QR?ZvDLZ_UBhAetw;I=b`Bu`GgnSn)Oj&H#a%I0OPE;!J=)|4s0 zxL#kX^$MqrlwB+b-aiYR)0X&_ZZ*|d-!aB{vTZi~?7R_2{?y#XNA%4dM|~a{HGP`_ z;z#$e+;S&5WfTJ?^I!>C`A8 zM-ppVz)ARUB!#H)cx{gr+aEjzd$(p8rOfQ_tP6iJ=y;Z=Ht+E4-+>RlUw&b)d;tV4 zoEZ<4eXmz~LNdm{rU5YnqCN$!bPZpa9?IS-QvbL71)I-1_4Dl2JDL4cgF zzr5OA)X;l{A=N~2-NEL1;VgYB6E`5va=s!GqBnv^yijC)m(pmkT%Uza?M#} zr+5V0blwaI&`BIR<7(R5zR#uRw)L(Rw7wYm$<<`ohmWb2Q4Ml>s9a2_rffwPaXSb` z64eOlT%UWyVous?p7os90Z{?lW3dE6_l%GI!tZf$dhl~c>wm;7Cn;d-z=xnvnh@SQ z2XoQsr_ME_$6Ce|o+%c|QRPF+DohmA5}CzgmlFyTmo*~eKyTtGy$3pX*x^Buq~pLx~j-3g0~!=Ir$pkH`CuN6#pUz5k(K{}cw zE}0+g3LHM}-d<1V<&18looiMP@B zmy#71lJDD;B#Xd2FdXn`7B|IZO$m3=ImZX&O-OmzG3|1G?r9$NWzhNdTXv>B^N8%D z;*-+)vr)Sr*{I&(Iz{now^VX;AZSbx!mfbojpwx)w&xT_-7!*SW}k?G3Uo+t!-til zbO{uj66g>Z86O2t>ldT;a~*ti%RCR$Ngy+FPwOtX6@4-L z%*rL+#Z*6g)-q9M8Fn{=mN#%_Ri*bkX}>CQoPgiKZHBY zIPcg}la7=()Z6QvQvFs^PNFhiwjc3?@Q>MFjW|4~z3y5|0as-ws*_FPxLiGQ;R5a? zKrf(Xw3my}$2dpbUVr02t1$2{HH`)5Sv~QOw{riL3+<-{FS}`)zH*1ultmS#slH6F ziLX3^{!TTcqng1;LAEGDym8{sp~jPHx|!9=>D3eOI*h zUFzgp-1&DsX5YPy+K-_tYx-@S4i?myPZ5BBcoZ5UTzc$`2SiP=g^s zqy&7Y(q*-x3^{a?l%adeMydVJ^lP{n$5|>WrZhcGHFGcp(DZ;w2!%sW%+I+RpcMe^ zncx}tiS+wjm_PF)u#()lbG5b3bDitThsQeL^Jg!g zJ$CHac}-cq=L+F~S)rmJUDwqGbCX{fpSzGSg7Sq1Bd6^(Va4I`P z$XNZSu zwdA$h186Nd_>)2vTlkDJ9b7vRYZl@59&X>N#<(-oH7ux~P6gENY(Joi(9WSyfP=~@ z1^8njKx_M_)d6Y<0g6t|^S5Q^!_YVK0=6J>Bh%UQ=hh-il1QRc^ffvToa`q>qmE#Gw7W^d1`qsc^R2SM8RwUqi@343@!Ula9*{0sn6e!gxyn1aX zn#7itRQRWRR+pY5ti;WN?3Wa!6IksGW`tqG(e0!7g7NeNwkL=OF8Z-S{T6W?DE@CX z*I0fij=Uea`}kAPQ^s|#6eogWx&Pv>tC&uTpipkW{Anj}*HDinS>-6j&S9U-S5B_1 zkf*FN*3UX>7mX~nx##q~IUlu|I$GZZP87gjkd8-(4f*4xG;!4?-7fAwdZEMIoTHlBZDXK3?IH!If5Et<|f{Epv zfU!el$)?YP#oLJOUkx`viYDb5+PD-s>7+NXBE4!x+02aG5cmcUg!arP$eH?cpu>Yd zV_a+oA{#k)^Sk#_-!vAK)Su#h+*a0`?8p6ho^~=$YW+LX%Q*A6fB$i(W7p%6MSX>% zdbf&rr)!pK_j83}bl|Zr3K+VGoS#(N0+4(X7mH4Wg~sizlfmJ5G!pxB=o7p_#3|`Z z;k5Ppg9>kT$;NAs_cu^(tKV~$KnuALThlvi(R#V#_mw@b@GUSOi08fM+tNIUp4$!6 z_J6o^wupD3UfaA!=Jm~lOgZG@3NQ71f5wD12v5umU*dw16^B~Sxamu+w@QzsY$j-b ztVCnK-SisRw0FRm<50;uKOrbx#+0d(PB^p!B;LO1d}F7EBb(0>XPu5+NLs>X7#{94s7SqxHXU(e>OAC^#+He76zhM!L^m{3q0 z%~c)00|6K4A;7l9wSv_&b8~B)A~hAWcQ^XO;NH+mW6r2J0_@pfcO#!1Wy;R*Rq#12 z;v%bJm}p|jnf*#C}k5>0!t?Q6(!a0UjZ6BWjXdw=^3pRf^ƞC=%K^sf%uBtQ6=CyMh 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 E4U6(9RR910 From 83f5b2ddc14548252b9d72f021c0aa5495048de6 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 30 Mar 2023 19:45:49 +0200 Subject: [PATCH 181/305] All benchmarks enabled by default --- .../RecyclableCollectionsBenchmarks.Array.cs | 10 +++--- .../RecyclableCollectionsBenchmarks.List.cs | 30 ++++++++-------- ...yclableCollectionsBenchmarks.PooledList.cs | 34 +++++++++---------- ...llectionsBenchmarks.RecyclableArrayList.cs | 14 ++++---- ...bleCollectionsBenchmarks.RecyclableList.cs | 28 +++++++-------- 5 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index 2615e22..0625abc 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -5,14 +5,14 @@ namespace Recyclable.Collections.Benchmarks [MemoryDiagnoser] public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = true)] public void Array_Create_WithCapacity() { var data = new object[TestObjectCount]; DoNothing(data); } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void Array_SetItem() { var data = TestObjects; @@ -23,7 +23,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void Array_GetItem() { var data = TestObjects; @@ -34,14 +34,14 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void Array_Count() { var data = _testArray; DoNothing(data.Length); } - //[Benchmark] + [Benchmark(Baseline = false)] public void Array_LongCount() { var data = _testArray; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 8426065..de65dc4 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_Create() { var list = new List(); DoNothing(list); } - //[Benchmark] + [Benchmark(Baseline = false)] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark(Baseline = false)] public void List_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + [Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + [Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + [Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -83,7 +83,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void List_SetItem() { var data = TestObjectsAsList; @@ -94,7 +94,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void List_GetItem() { var data = TestObjectsAsList; @@ -105,14 +105,14 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void List_Count() { var data = TestObjectsAsList; DoNothing(data.Count); } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_Contains_FirstItems() { var data = TestObjects; @@ -124,7 +124,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void List_Contains_LastItems() { var data = TestObjects; @@ -136,7 +136,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_IndexOf_FirstItems() { var data = TestObjects; @@ -148,7 +148,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void List_IndexOf_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs index f3e1c67..d97de14 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs @@ -5,21 +5,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - //[Benchmark] + [Benchmark] public void PooledList_Create() { using var list = new PooledList(); DoNothing(list); } - //[Benchmark] + [Benchmark] public void PooledList_Create_WithCapacity() { using var list = new PooledList(capacity: TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark] public void PooledList_Add() { var data = TestObjects; @@ -31,7 +31,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void PooledList_Add_WithCapacity() { var data = TestObjects; @@ -43,7 +43,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void PooledList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -51,7 +51,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void PooledList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -59,7 +59,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void PooledList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsPooledList; @@ -67,7 +67,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void PooledList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -75,7 +75,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void PooledList_AddRangeWhenSourceIsPooledList() { var data = TestObjectsAsPooledList; @@ -83,7 +83,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void PooledList_AddRange_WithCapacity() { var data = TestObjects; @@ -92,7 +92,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void PooledList_GetItem() { var data = _testPooledList; @@ -103,7 +103,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void PooledList_SetItem() { var data = _testPooledList; @@ -114,14 +114,14 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void PooledList_Count() { var data = _testPooledList; DoNothing(data.Count); } - //[Benchmark] + [Benchmark] public void PooledList_Contains_FirstItems() { var data = TestObjects; @@ -133,7 +133,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void PooledList_Contains_LastItems() { var data = TestObjects; @@ -145,7 +145,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void PooledList_IndexOf_FirstItems() { var data = TestObjects; @@ -157,7 +157,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void PooledList_IndexOf_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 779a6ad..a6cbad8 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; @@ -91,7 +91,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_SetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -102,7 +102,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_GetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -113,7 +113,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Count() { var data = TestObjectsAsRecyclableArrayList; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index f9451d6..3d4feaf 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: BlockSize, 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; @@ -91,7 +91,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] public void RecyclableList_GetItem() { var data = _testRecyclableList; @@ -214,7 +214,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_SetItem() { var data = _testRecyclableList; @@ -225,21 +225,21 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_Count() { var data = _testRecyclableList; DoNothing(data.Count); } - //[Benchmark] + [Benchmark] public void RecyclableList_LongCount() { var data = _testRecyclableList; DoNothing(data.LongCount); } - //[Benchmark] + [Benchmark] public void RecyclableList_Contains_FirstItems() { var data = TestObjects; @@ -251,7 +251,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_Contains_LastItems() { var data = TestObjects; @@ -263,7 +263,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_IndexOf_FirstItems() { var data = TestObjects; @@ -275,7 +275,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_IndexOf_LastItems() { var data = TestObjects; @@ -287,7 +287,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_LongIndexOf_FirstItems() { var data = TestObjects; @@ -299,7 +299,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_LongIndexOf_LastItems() { var data = TestObjects; From d79dc4fb863b7f8b6f7f95e58b8376396f8d243d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 30 Mar 2023 20:29:36 +0200 Subject: [PATCH 182/305] Tiny cleanup --- Recyclable.Collections/RecyclableList.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 5149d10..b774002 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -8,13 +8,11 @@ namespace Recyclable.Collections public class RecyclableList : IDisposable, IList { private const int ItemNotFoundIndex = -1; - private const int BatchSize = 8; 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; private int _blockSize; private int _blockSizePow2Shift; From 4500484831306cbb3c90233a74372d1cee94bd3c Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 1 Apr 2023 13:54:53 +0200 Subject: [PATCH 183/305] RecyclableArrayList.IndexOf performance improvements. --- Recyclable.Collections/RecyclableArrayList.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 0d61193..348cfb1 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -388,22 +388,7 @@ namespace Recyclable.Collections } } - public int IndexOf(T itemToFind) - { - int itemCount = _count; - Span memorySpan = new(_memoryBlock, 0, itemCount); - var equalityComparer = _equalityComparer; - for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) - { - var item = memorySpan[itemIdx]; - if (equalityComparer.Equals(item, itemToFind)) - { - return itemIdx; - } - } - - return -1; - } + public int IndexOf(T itemToFind) => Array.IndexOf(_memoryBlock, itemToFind, 0, _count); public void Insert(int index, T item) { From 239d45fc8c83c6e7f341ad8937ebd88b120495fd Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 1 Apr 2023 16:38:53 +0200 Subject: [PATCH 184/305] RecyclableArrayList.IndexOf improved to not raise exception when the list is empty. --- Recyclable.Collections/RecyclableArrayList.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 348cfb1..1d4bce4 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -6,6 +6,7 @@ namespace Recyclable.Collections { public class RecyclableArrayList : IEnumerable, IList, IDisposable { + private const int ItemNotFound = -1; private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; @@ -388,7 +389,7 @@ namespace Recyclable.Collections } } - public int IndexOf(T itemToFind) => Array.IndexOf(_memoryBlock, itemToFind, 0, _count); + public int IndexOf(T itemToFind) => _count > 0 ? Array.IndexOf(_memoryBlock, itemToFind, 0, _count) : ItemNotFound; public void Insert(int index, T item) { From 80756aff442519fc39f79e6a950f3c2acfccc517 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 1 Apr 2023 20:00:32 +0200 Subject: [PATCH 185/305] RecyclableList.RemoveAt fix to release the rented block correctly. --- 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 b774002..3967338 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -69,7 +69,7 @@ namespace Recyclable.Collections list._nextItemIndex = blockSize; } - if ((list._capacity * blockSize) - oldCountMinus1 == blockSize) + if (list._capacity - oldCountMinus1 == blockSize) { T[][] memoryBlocks = list._memoryBlocks; if (blockSize >= RecyclableDefaults.MinPooledArrayLength) From d6b351b1ebc0ef9637ef6278cda3052a548e9a26 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 1 Apr 2023 20:01:34 +0200 Subject: [PATCH 186/305] RecyclableList.DoIndexOf multiplication changed to bit shifting for better performance. --- 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 3967338..217ea6c 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -209,7 +209,7 @@ namespace Recyclable.Collections itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); if (itemIndex >= 0) { - return itemIndex + (blockIndex * blockSize); + return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); } } @@ -224,7 +224,7 @@ namespace Recyclable.Collections return ItemNotFoundIndex; } - return itemIndex >= 0 ? itemIndex + (lastBlockIndex * blockSize) : ItemNotFoundIndex; + return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 1ce91c00621ea90c63121b4e4790795f6b2f578e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 7 Apr 2023 01:09:25 +0200 Subject: [PATCH 187/305] RecyclableArrayList.Remove, RecyclableArrayList.RemoveAt, RecyclableList.Remove & RecyclableList.RemoveAt performance improvements. Support for removal of any item implemented in RecyclableList.Remove & .RemoveAt. --- Recyclable.Collections/RecyclableArrayList.cs | 25 +++-- Recyclable.Collections/RecyclableList.cs | 98 ++++++++++++++----- .../RecyclableListTests.cs | 63 +++++++++--- 3 files changed, 135 insertions(+), 51 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 1d4bce4..8333cd7 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -411,6 +411,7 @@ namespace Recyclable.Collections _count++; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Remove(T itemToRemove) { var itemIdx = IndexOf(itemToRemove); @@ -423,24 +424,28 @@ namespace Recyclable.Collections return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveAt(int index) { - int oldCount = _count; - int oldCountMinus1 = oldCount - 1; - if (index == oldCountMinus1) + if (index == _count - 1) { _count--; - return; } - - if (index >= oldCount) + else if (index > _count - 1) { - ThrowArgumentOutOfRangeException($"Argument \"{index}\" is out of range. The current version supports removing the last element, only "); - return; + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range"); + } + else if (index < --_count) + { + Array.Copy(_memoryBlock, index + 1, _memoryBlock, index, _count - index); } - _memoryBlock.CopyItems(index, oldCountMinus1, ref _memoryBlock); - _count--; + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(_memoryBlock, _count, 1)[0] = default; +#pragma warning restore CS8601 + } } public IEnumerator GetEnumerator() => Enumerate(this).GetEnumerator(); diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 217ea6c..a2e4420 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -49,42 +49,73 @@ namespace Recyclable.Collections public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; - private static void RemoveAt(RecyclableList list, long index, ArrayPool blockArrayPool) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void DoRemoveAt(long index) { - long oldCountMinus1 = list._longCount - 1; - if (index != oldCountMinus1) + int blockSize = _blockSize; + int nextItemIndex = _nextItemIndex; + T[][] memoryBlocks = _memoryBlocks; + _longCount--; + if (index < _longCount) { - ThrowArgumentOutOfRangeException(); + int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); + int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); + + int targetBlockIndex = (int)(index >> _blockSizePow2Shift); + int targetItemIndex = (int)(index & (blockSize - 1)); + + int lastTakenBlockIndex = LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } } - list._longCount--; - int blockSize = list._blockSize; - if (list._nextItemIndex > 0) + if (nextItemIndex > 0) { - list._nextItemIndex--; + _nextItemIndex--; } else { - list._nextItemBlockIndex--; - list._nextItemIndex = blockSize; + _nextItemIndex = blockSize - 1; + _nextItemBlockIndex--; } - if (list._capacity - oldCountMinus1 == blockSize) + if (NeedsClearing) { - T[][] memoryBlocks = list._memoryBlocks; - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - blockArrayPool.Return(memoryBlocks[list.ReservedBlockCount - 1], NeedsClearing); - } - - list._capacity -= blockSize; +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; +#pragma warning restore CS8601 } } - private static void ThrowArgumentOutOfRangeException() - { - throw new ArgumentOutOfRangeException("index"); - } + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); protected void AddRangeEnumerated(IEnumerable source, int growByCount) { @@ -765,7 +796,7 @@ namespace Recyclable.Collections var itemIndex = LongIndexOf(item); if (itemIndex >= 0) { - RemoveAt(this, itemIndex, _defaultBlockArrayPool); + DoRemoveAt(itemIndex); return true; } @@ -794,8 +825,25 @@ namespace Recyclable.Collections } } - public void RemoveAt(int index) => RemoveAt(this, index, _blockArrayPool); - public void RemoveAt(long index) => RemoveAt(this, index, _blockArrayPool); + public void RemoveAt(int index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + public void RemoveAt(long index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index e229d6d..3f41035 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -545,34 +545,65 @@ namespace Recyclable.CollectionsTests [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void RemoveAtShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { - if (itemsCount is 0 or 1) - { - return; - } - // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - // Act - _ = Assert.Throws(() => list.RemoveAt(0)); + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + list.RemoveAt(0); + + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); } [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] - public void RemoveShouldRaiseNotSupportedExceptionWhenNotLastItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { - if (itemsCount is 0 or 1) - { - return; - } - // Prepare using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); - // Act - _ = Assert.Throws(() => list.Remove(testData.First())); + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + list.RemoveAt(list.Count - 1); + + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + } + + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + _ = list.Remove(deleted).Should().BeTrue(); + + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); + } } [Theory] From 064b3f0cb0f598cd6db46db09f462c7ec857b16d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 7 Apr 2023 23:34:43 +0200 Subject: [PATCH 188/305] .Remove & .RemoveAt benchmarks added. --- .../RecyclableCollectionsBenchmarks.List.cs | 80 ++++++++++++---- ...yclableCollectionsBenchmarks.PooledList.cs | 82 ++++++++++++---- ...llectionsBenchmarks.RecyclableArrayList.cs | 86 ++++++++++++++--- ...bleCollectionsBenchmarks.RecyclableList.cs | 96 ++++++++++++++----- 4 files changed, 274 insertions(+), 70 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index de65dc4..91ca294 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Create() { var list = new List(); DoNothing(list); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); DoNothing(list); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -74,7 +74,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -83,7 +83,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_SetItem() { var data = TestObjectsAsList; @@ -94,7 +94,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_GetItem() { var data = TestObjectsAsList; @@ -105,14 +105,14 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Count() { var data = TestObjectsAsList; DoNothing(data.Count); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Contains_FirstItems() { var data = TestObjects; @@ -124,7 +124,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_Contains_LastItems() { var data = TestObjects; @@ -136,7 +136,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_IndexOf_FirstItems() { var data = TestObjects; @@ -148,7 +148,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_IndexOf_LastItems() { var data = TestObjects; @@ -159,5 +159,53 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.IndexOf(data[^(i + 1)])); } } + + //[Benchmark(Baseline = true)] + public void List_RemoveAt_FirstItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + //[Benchmark(Baseline = false)] + public void List_RemoveAt_LastItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + + //[Benchmark(Baseline = false)] + public void List_Remove_FirstItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void List_Remove_LastItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs index d97de14..04243ae 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs @@ -5,21 +5,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - [Benchmark] + //[Benchmark] public void PooledList_Create() { using var list = new PooledList(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void PooledList_Create_WithCapacity() { using var list = new PooledList(capacity: TestObjectCount); DoNothing(list); } - [Benchmark] + //[Benchmark] public void PooledList_Add() { var data = TestObjects; @@ -31,7 +31,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void PooledList_Add_WithCapacity() { var data = TestObjects; @@ -43,7 +43,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void PooledList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -51,7 +51,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void PooledList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -59,7 +59,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void PooledList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsPooledList; @@ -67,7 +67,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void PooledList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -75,7 +75,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void PooledList_AddRangeWhenSourceIsPooledList() { var data = TestObjectsAsPooledList; @@ -83,7 +83,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void PooledList_AddRange_WithCapacity() { var data = TestObjects; @@ -92,7 +92,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void PooledList_GetItem() { var data = _testPooledList; @@ -103,7 +103,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void PooledList_SetItem() { var data = _testPooledList; @@ -114,14 +114,14 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void PooledList_Count() { var data = _testPooledList; DoNothing(data.Count); } - [Benchmark] + //[Benchmark] public void PooledList_Contains_FirstItems() { var data = TestObjects; @@ -133,7 +133,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void PooledList_Contains_LastItems() { var data = TestObjects; @@ -145,7 +145,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void PooledList_IndexOf_FirstItems() { var data = TestObjects; @@ -157,7 +157,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void PooledList_IndexOf_LastItems() { var data = TestObjects; @@ -168,5 +168,53 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.IndexOf(data[^(i + 1)])); } } + + //[Benchmark(Baseline = true)] + public void PooledList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new PooledList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_Remove_LastItems() + { + var data = TestObjects; + using var list = new PooledList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new PooledList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + [Benchmark(Baseline = true)] + public void PooledList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new PooledList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index a6cbad8..b5bc74a 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; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -74,7 +74,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; @@ -82,7 +82,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRange_WithCapacity() { var data = TestObjects; @@ -91,7 +91,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_SetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -102,7 +102,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_GetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -113,11 +113,71 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Count() { var data = TestObjectsAsRecyclableArrayList; DoNothing(data.Count); } + + //[Benchmark] + public void RecyclableArrayList_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableArrayList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Remove_LastItems() + { + var data = TestObjects; + using var list = new RecyclableArrayList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableArrayList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableArrayList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new RecyclableArrayList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 3d4feaf..2dc5e42 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: BlockSize, 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; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -74,7 +74,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; @@ -82,7 +82,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableList_AddRange_WithCapacity() { var data = TestObjects; @@ -91,7 +91,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableList_GetItem() { var data = _testRecyclableList; @@ -102,7 +102,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_GetItem_AsSpan() { var data = _testRecyclableList; @@ -131,7 +131,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_GetItem_AsArray() { var data = _testRecyclableList; @@ -160,7 +160,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_GetItem_AsForEach() { var data = _testRecyclableList; @@ -172,7 +172,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_GetItem_AsWhile() { var data = _testRecyclableList; @@ -214,7 +214,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_SetItem() { var data = _testRecyclableList; @@ -225,21 +225,21 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_Count() { var data = _testRecyclableList; DoNothing(data.Count); } - [Benchmark] + //[Benchmark] public void RecyclableList_LongCount() { var data = _testRecyclableList; DoNothing(data.LongCount); } - [Benchmark] + //[Benchmark] public void RecyclableList_Contains_FirstItems() { var data = TestObjects; @@ -251,7 +251,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_Contains_LastItems() { var data = TestObjects; @@ -263,7 +263,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_IndexOf_FirstItems() { var data = TestObjects; @@ -275,7 +275,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_IndexOf_LastItems() { var data = TestObjects; @@ -287,7 +287,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_LongIndexOf_FirstItems() { var data = TestObjects; @@ -299,7 +299,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableList_LongIndexOf_LastItems() { var data = TestObjects; @@ -310,5 +310,53 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.LongIndexOf(data[^(i + 1)])); } } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Remove_LastItems() + { + var data = TestObjects; + using var list = new RecyclableList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new RecyclableList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } } } From 3369bcc6cb04356eda121c5cb4123e115ec14cc3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 8 Apr 2023 00:34:23 +0200 Subject: [PATCH 189/305] RecyclableList.Remove & .RemoveAt performance improvements. --- Recyclable.Collections/RecyclableList.cs | 154 +++++++++++++++-------- 1 file changed, 102 insertions(+), 52 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index a2e4420..f001895 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -50,71 +50,77 @@ namespace Recyclable.Collections public int NextItemIndex => _nextItemIndex; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private void DoRemoveAt(long index) + private static void DoRemoveAt(RecyclableList list, long index) { - int blockSize = _blockSize; - int nextItemIndex = _nextItemIndex; - T[][] memoryBlocks = _memoryBlocks; - _longCount--; - if (index < _longCount) + list._longCount--; + if (index < list._longCount) { - int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); - int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); - - int targetBlockIndex = (int)(index >> _blockSizePow2Shift); - int targetItemIndex = (int)(index & (blockSize - 1)); - - int lastTakenBlockIndex = LastTakenBlockIndex; - while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) - { - int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 - ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) - : Math.Min(nextItemIndex, blockSize - targetItemIndex); - - Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); - // We didn't have enough room in the target array block. There are still items in the source array block to copy. - if (sourceItemIndex + toCopy < blockSize) - { - sourceItemIndex += toCopy; - targetBlockIndex++; - targetItemIndex = 0; - } - // We copied all the source items in the current array block. But have we filled the target? - else - { - sourceItemIndex = 0; - sourceBlockIndex++; - if (targetItemIndex + toCopy < blockSize) - { - targetItemIndex += toCopy; - } - else - { - targetBlockIndex++; - targetItemIndex = 0; - } - } - } + CopyItems(list, index); } - if (nextItemIndex > 0) + if (list._nextItemIndex > 0) { - _nextItemIndex--; + list._nextItemIndex--; } else { - _nextItemIndex = blockSize - 1; - _nextItemBlockIndex--; + list._nextItemIndex = list._blockSize - 1; + list._nextItemBlockIndex--; } if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it - new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; + new Span(list._memoryBlocks[list._nextItemBlockIndex], list._nextItemIndex, 1)[0] = default; #pragma warning restore CS8601 } } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableList list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2Shift); + int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); + + int targetBlockIndex = (int)(index >> list._blockSizePow2Shift); + int targetItemIndex = (int)(index & (blockSize - 1)); + + int lastTakenBlockIndex = list.LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); protected void AddRangeEnumerated(IEnumerable source, int growByCount) @@ -231,6 +237,7 @@ namespace Recyclable.Collections return; } + [MethodImpl(MethodImplOptions.AggressiveOptimization)] protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) { Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); @@ -258,7 +265,7 @@ namespace Recyclable.Collections return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); @@ -796,7 +803,7 @@ namespace Recyclable.Collections var itemIndex = LongIndexOf(item); if (itemIndex >= 0) { - DoRemoveAt(itemIndex); + DoRemoveAt(this, itemIndex); return true; } @@ -825,6 +832,7 @@ namespace Recyclable.Collections } } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void RemoveAt(int index) { if (index > _longCount - 1 || index < 0) @@ -832,7 +840,28 @@ namespace Recyclable.Collections ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); } - DoRemoveAt(index); + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(_memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; +#pragma warning restore CS8601 + } } public void RemoveAt(long index) @@ -842,7 +871,28 @@ namespace Recyclable.Collections ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); } - DoRemoveAt(index); + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(_memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; +#pragma warning restore CS8601 + } } IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); From fd9c2dab828eb141267eaaff9a38f9eadd4b24e1 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 8 Apr 2023 01:01:55 +0200 Subject: [PATCH 190/305] RecyclableList.Remove & .RemoveAt performance improvements. --- Recyclable.Collections/RecyclableList.cs | 67 ++++++++++++------------ 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index f001895..2d8442c 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -49,33 +49,6 @@ namespace Recyclable.Collections public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static void DoRemoveAt(RecyclableList list, long index) - { - list._longCount--; - if (index < list._longCount) - { - CopyItems(list, index); - } - - if (list._nextItemIndex > 0) - { - list._nextItemIndex--; - } - else - { - list._nextItemIndex = list._blockSize - 1; - list._nextItemBlockIndex--; - } - - if (NeedsClearing) - { -#pragma warning disable CS8601 // In real use cases we'll never access it - new Span(list._memoryBlocks[list._nextItemBlockIndex], list._nextItemIndex, 1)[0] = default; -#pragma warning restore CS8601 - } - } - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] private static void CopyItems(RecyclableList list, long index) { @@ -792,18 +765,43 @@ namespace Recyclable.Collections : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public long LongIndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool Remove(T item) { - var itemIndex = LongIndexOf(item); - if (itemIndex >= 0) + var index = LongIndexOf(item); + if (index >= 0) { - DoRemoveAt(this, itemIndex); + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + return true; } @@ -835,7 +833,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void RemoveAt(int index) { - if (index > _longCount - 1 || index < 0) + if (index >= _longCount || index < 0) { ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); } @@ -859,14 +857,15 @@ namespace Recyclable.Collections if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it - new Span(_memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; #pragma warning restore CS8601 } } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void RemoveAt(long index) { - if (index > _longCount - 1 || index < 0) + if (index >= _longCount || index < 0) { ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); } @@ -890,7 +889,7 @@ namespace Recyclable.Collections if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it - new Span(_memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; #pragma warning restore CS8601 } } From dead3235028b754c557c0660d2db7121a8f4fd23 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 8 Apr 2023 01:13:37 +0200 Subject: [PATCH 191/305] RecyclableArrayList.Remove & .RemoveAt performance improvements. --- Recyclable.Collections/RecyclableArrayList.cs | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 8333cd7..4c620ff 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -411,31 +411,41 @@ namespace Recyclable.Collections _count++; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool Remove(T itemToRemove) { - var itemIdx = IndexOf(itemToRemove); - if (itemIdx >= 0) + var index = IndexOf(itemToRemove); + if (index >= 0) { - RemoveAt(itemIdx); + _count--; + if (index < _count) + { + Array.Copy(_memoryBlock, index + 1, _memoryBlock, index, _count - index); + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlock[_count] = default; +#pragma warning restore CS8601 + } + return true; } return false; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void RemoveAt(int index) { - if (index == _count - 1) - { - _count--; - } - else if (index > _count - 1) + if (index > _count - 1) { ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range"); } - else if (index < --_count) + + _count--; + if (index < _count) { Array.Copy(_memoryBlock, index + 1, _memoryBlock, index, _count - index); } @@ -443,7 +453,7 @@ namespace Recyclable.Collections if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it - new Span(_memoryBlock, _count, 1)[0] = default; + _memoryBlock[_count] = default; #pragma warning restore CS8601 } } From b7af797dd73b1433c9c19e23123a7c7669869593 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 8 Apr 2023 01:15:27 +0200 Subject: [PATCH 192/305] Tiny cleanup --- Recyclable.Collections/RecyclableList.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 2d8442c..f66a73d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -69,6 +69,7 @@ namespace Recyclable.Collections : Math.Min(nextItemIndex, blockSize - targetItemIndex); Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + // We didn't have enough room in the target array block. There are still items in the source array block to copy. if (sourceItemIndex + toCopy < blockSize) { From 298af4e71843c110608fad88aa062c2835ee5a35 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 9 Apr 2023 00:50:12 +0200 Subject: [PATCH 193/305] Arithmetic overflow protections added. --- Recyclable.Collections/RecyclableList.cs | 44 ++++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index f66a73d..1cff9a0 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -44,7 +44,7 @@ namespace Recyclable.Collections set => _longCount = value; } - public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); + public int ReservedBlockCount => checked((int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0)); public int BlockSize => _blockSize; public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -55,11 +55,11 @@ namespace Recyclable.Collections T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; int blockSize = list._blockSize; - int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2Shift); - int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2Shift)); + int sourceItemIndex = checked((int)((index + 1) & (blockSize - 1))); - int targetBlockIndex = (int)(index >> list._blockSizePow2Shift); - int targetItemIndex = (int)(index & (blockSize - 1)); + int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); + int targetItemIndex = checked((int)(index & (blockSize - 1))); int lastTakenBlockIndex = list.LastTakenBlockIndex; while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) @@ -221,7 +221,7 @@ namespace Recyclable.Collections itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); if (itemIndex >= 0) { - return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); } } @@ -236,15 +236,15 @@ namespace Recyclable.Collections return ItemNotFoundIndex; } - return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) { int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); - int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); - int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); + int sourceBlockCount = checked((int)(oldCapacity >> minBlockSizePowerToShift)); + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; T[][]? newMemoryBlocks; Span memoryBlocksSpan; @@ -310,13 +310,13 @@ namespace Recyclable.Collections } } - return newMemoryBlocks.Length << minBlockSizePowerToShift; + return (long)newMemoryBlocks.Length << minBlockSizePowerToShift; } protected long EnsureCapacity(long requestedCapacity) { long newCapacity = _capacity > 0 - ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + ? checked((long)(BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity) - 1)) : requestedCapacity; newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); @@ -339,7 +339,7 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _capacity = Resize(ref _memoryBlocks!, checked((int)(BitOperations.RoundUpToPowerOf2((uint)minBlockSize) - 1)), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -350,7 +350,7 @@ namespace Recyclable.Collections } else { - _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSize = checked((int)(BitOperations.RoundUpToPowerOf2((uint)minBlockSize) - 1)); _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -365,7 +365,7 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _capacity = Resize(ref _memoryBlocks!, checked((int)(BitOperations.RoundUpToPowerOf2((uint)minBlockSize) - 1)), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -376,7 +376,7 @@ namespace Recyclable.Collections } else { - _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSize = checked((int)(BitOperations.RoundUpToPowerOf2((uint)minBlockSize) - 1)); _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -387,7 +387,7 @@ namespace Recyclable.Collections public T this[long index] { get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; - set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & (_blockSize - 1)))] = value; } public T this[int index] @@ -525,7 +525,7 @@ namespace Recyclable.Collections long copiedCount = 0; while (copiedCount < itemsCount) { - toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); if (targetBlockArraySpan.Length < toCopy) { toCopy = targetBlockArraySpan.Length; @@ -562,7 +562,7 @@ namespace Recyclable.Collections } else { - _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); + _nextItemIndex = checked((int)(targetCapacity & (blockSize - 1))); _nextItemBlockIndex = targetBlockIndex; } @@ -738,7 +738,7 @@ namespace Recyclable.Collections if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) { - return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; } Span memoryBlocksSpan = new(_memoryBlocks); @@ -762,8 +762,8 @@ namespace Recyclable.Collections public int IndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) - ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex)); public void Insert(int index, T item) => throw new NotSupportedException(); @@ -771,7 +771,7 @@ namespace Recyclable.Collections public long LongIndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) - ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] From ed76bd99ba290553c745f43346f290d771d62e55 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 9 Apr 2023 01:51:58 +0200 Subject: [PATCH 194/305] Failing tests fixed. --- Recyclable.Collections/RecyclableList.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 1cff9a0..1546379 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -316,7 +316,7 @@ namespace Recyclable.Collections protected long EnsureCapacity(long requestedCapacity) { long newCapacity = _capacity > 0 - ? checked((long)(BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity) - 1)) + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) : requestedCapacity; newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); @@ -339,7 +339,7 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _capacity = Resize(ref _memoryBlocks!, checked((int)(BitOperations.RoundUpToPowerOf2((uint)minBlockSize) - 1)), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _capacity = Resize(ref _memoryBlocks!, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -350,7 +350,7 @@ namespace Recyclable.Collections } else { - _blockSize = checked((int)(BitOperations.RoundUpToPowerOf2((uint)minBlockSize) - 1)); + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -365,7 +365,7 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _capacity = Resize(ref _memoryBlocks!, checked((int)(BitOperations.RoundUpToPowerOf2((uint)minBlockSize) - 1)), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _capacity = Resize(ref _memoryBlocks!, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); if (_memoryBlocks.Length > 0) { minBlockSize = _memoryBlocks[0].Length; @@ -376,7 +376,7 @@ namespace Recyclable.Collections } else { - _blockSize = checked((int)(BitOperations.RoundUpToPowerOf2((uint)minBlockSize) - 1)); + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); _memoryBlocks = _emptyMemoryBlocksArray; } From a132fdc775f1d491e063683d9556bf326573fd36 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 9 Apr 2023 01:54:52 +0200 Subject: [PATCH 195/305] RecyclableList.EnsureCapacity performance improvements. --- Recyclable.Collections/RecyclableList.cs | 31 ++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 1546379..9e8f632 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -119,7 +119,7 @@ namespace Recyclable.Collections { if (copied + growByCount > capacity) { - capacity = EnsureCapacity(capacity + growByCount); + capacity = EnsureCapacity(this, capacity + growByCount); memoryBlocksCount = _memoryBlocks.Length; if (blockSize != _blockSize) { @@ -182,7 +182,7 @@ namespace Recyclable.Collections long requiredCapacity = copied + requiredAdditionalCapacity; if (_capacity < requiredCapacity) { - _ = EnsureCapacity(requiredCapacity); + _ = EnsureCapacity(this, requiredCapacity); blockSize = _blockSize; } @@ -313,20 +313,21 @@ namespace Recyclable.Collections return (long)newMemoryBlocks.Length << minBlockSizePowerToShift; } - protected long EnsureCapacity(long requestedCapacity) + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long EnsureCapacity(RecyclableList list, long requestedCapacity) { - long newCapacity = _capacity > 0 + long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) : requestedCapacity; - newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); - if (newCapacity > 0 && _capacity == 0) + newCapacity = Resize(ref list._memoryBlocks!, list._blockSize, list._capacity, newCapacity, list._memoryBlocksPool, list._blockArrayPool); + if (newCapacity > 0 && list._capacity == 0) { - _blockSize = _memoryBlocks[0].Length; - _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); + list._blockSize = list._memoryBlocks[0].Length; + list._blockSizePow2Shift = MathUtils.GetPow2Shift(list._blockSize); } - _capacity = newCapacity; + list._capacity = newCapacity; return newCapacity; } @@ -403,7 +404,7 @@ namespace Recyclable.Collections { if (_capacity < _longCount + 1) { - _ = EnsureCapacity(_longCount + 1); + _ = EnsureCapacity(this, _longCount + 1); } _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; @@ -426,7 +427,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + items.LongLength; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } int blockSize = _blockSize, @@ -469,7 +470,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } int blockSize = _blockSize, @@ -510,7 +511,7 @@ namespace Recyclable.Collections if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } int blockSize = _blockSize, @@ -580,7 +581,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } int blockSize = _blockSize, @@ -626,7 +627,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } int blockSize = _blockSize, From 7f84cdc636080ee96db93c89df1e2bcea69262a5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 9 Apr 2023 03:10:46 +0200 Subject: [PATCH 196/305] RecyclableList.Resize performance improvements. --- Recyclable.Collections/RecyclableList.cs | 45 ++++++++++++------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 9e8f632..15b268a 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -240,19 +240,20 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + protected static long Resize(RecyclableList list, long newCapacity) { + ArrayPool blockArrayPool = list._blockArrayPool; + int minBlockSize = list._blockSize; int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); - int sourceBlockCount = checked((int)(oldCapacity >> minBlockSizePowerToShift)); + int sourceBlockCount = checked((int)(list._capacity >> minBlockSizePowerToShift)); int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; - T[][]? newMemoryBlocks; Span memoryBlocksSpan; // Release excesive blocks if we're downsizing if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) { - memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); + memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, sourceBlockCount - requiredBlockCount); blockIndex = 0; while (blockIndex < memoryBlocksSpan.Length) { @@ -260,23 +261,25 @@ namespace Recyclable.Collections } } + ArrayPool memoryBlocksPool = list._memoryBlocksPool; + // Allocate new memory block for all arrays - newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + T[][]? newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength ? (new T[requiredBlockCount][]) : memoryBlocksPool.Rent(requiredBlockCount); // Copy arrays from the old memory block for all arrays if (sourceBlockCount > 0) { - Array.Copy(source!, newMemoryBlocks, sourceBlockCount); + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); // We can now return the old memory block for all arrays itself if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) { - memoryBlocksPool.Return(source!, true); + memoryBlocksPool.Return(list._memoryBlocks!, true); } } - source = newMemoryBlocks; + list._memoryBlocks = newMemoryBlocks; // Allocate arrays for any new blocks if (requiredBlockCount > sourceBlockCount) @@ -320,7 +323,7 @@ namespace Recyclable.Collections ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) : requestedCapacity; - newCapacity = Resize(ref list._memoryBlocks!, list._blockSize, list._capacity, newCapacity, list._memoryBlocksPool, list._blockArrayPool); + newCapacity = Resize(list, newCapacity); if (newCapacity > 0 && list._capacity == 0) { list._blockSize = list._memoryBlocks[0].Length; @@ -333,21 +336,19 @@ namespace Recyclable.Collections public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) { - memoryBlocksPool ??= _defaultMemoryBlocksPool; - blockArrayPool ??= _defaultBlockArrayPool; - _memoryBlocksPool = memoryBlocksPool; - _blockArrayPool = blockArrayPool; + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + _blockArrayPool = blockArrayPool ?? _defaultBlockArrayPool; if (expectedItemsCount > 0) { - _capacity = Resize(ref _memoryBlocks!, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); - if (_memoryBlocks.Length > 0) + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _capacity = Resize(this, expectedItemsCount.Value); + if (_memoryBlocks!.Length > 0) { - minBlockSize = _memoryBlocks[0].Length; + _blockSize = _memoryBlocks[0].Length; } - _blockSize = minBlockSize; - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); } else { @@ -366,14 +367,14 @@ namespace Recyclable.Collections if (expectedItemsCount > 0) { - _capacity = Resize(ref _memoryBlocks!, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _capacity = Resize(this, expectedItemsCount.Value); if (_memoryBlocks.Length > 0) { - minBlockSize = _memoryBlocks[0].Length; + _blockSize = _memoryBlocks[0].Length; } - _blockSize = minBlockSize; - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); } else { From 4165c9c6d91d7855af207b47604115c68b2eeba3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 10 Apr 2023 22:35:00 +0200 Subject: [PATCH 197/305] RecyclableList.EnsureCapacity & .Resize performance improvements. Custom ArrayPool added. --- Recyclable.Collections/OneSizeArrayPool.cs | 179 +++++++++++++++++++++ Recyclable.Collections/RecyclableList.cs | 98 +++++++---- 2 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 Recyclable.Collections/OneSizeArrayPool.cs diff --git a/Recyclable.Collections/OneSizeArrayPool.cs b/Recyclable.Collections/OneSizeArrayPool.cs new file mode 100644 index 0000000..3f54b31 --- /dev/null +++ b/Recyclable.Collections/OneSizeArrayPool.cs @@ -0,0 +1,179 @@ +using System.Buffers; +using System.Collections.Concurrent; + +namespace Recyclable.Collections +{ + public class MemoryBucket + { + public MemoryBucket(T[] memory, MemoryBucket nextBucket) + { + _memory = memory; + _nextBucket = nextBucket; + } + + public MemoryBucket(T[] memory) + { + _memory = memory; + } + + private T[] _memory; + public T[] Memory { get => _memory; set => _memory = value; } + + private MemoryBucket? _nextBucket; + public MemoryBucket? NextBucket { get => _nextBucket; set => _nextBucket = value; } + } + + public static class RecyclableArrayPool + { + public static readonly OneSizeArrayPool Null = new(0, 0); + + private static readonly ConcurrentDictionary> _pools = new(); + + public static ArrayPool Shared(int blockSize) + { + //lock (_lock) + { + if (!_pools.TryGetValue(blockSize, out ArrayPool? pool)) + { + pool = new OneSizeArrayPool(blockSize, 100); + pool = _pools.TryAdd(blockSize, pool) ? pool : _pools[blockSize]; + } + + return pool; + } + + //_lock.Wait(); + //try + //{ + //} + //finally + //{ + // _lock.Release(); + //} + } + } + + public class OneSizeArrayPool : ArrayPool + { + private readonly int _blockSize; + private readonly int _minBlockCount; + private int _blockCount; + private MemoryBucket? _currentBucket; + //SemaphoreSlim _lock = new(1); + //ManualResetEventSlim _lock = new(true, 1); + private readonly object _updateLock = new(); + //private Task? _bufferFillUpTask; + + //private Task CreateAdditionalBucket(int minimumLength, int blockCount) + //{ + // return Task.Factory.StartNew(() => + // { + // MemoryBucket firstBucket = new(new T[minimumLength]); + // MemoryBucket currentBucket = firstBucket; + // for (var i = 0; i < blockCount; i++) + // { + // currentBucket = new(new T[minimumLength], currentBucket); + // } + + // lock (_updateLock) + // //_lock.Wait(); + // //try + // { + // currentBucket.NextBucket = _currentBucket; + // _currentBucket = firstBucket; + // _blockCount += blockCount; + // //_bufferFillUpTask = null; + // } + // //finally + // //{ + // // _ = _lock.Release(); + // // //_lock.Set(); + // //} + // }); + //} + + public OneSizeArrayPool(int blockSize, int minBlockCount) + { + _blockSize = blockSize; + _minBlockCount = minBlockCount; + } + + public override T[] Rent(int minimumLength) + { + if (minimumLength != _blockSize) + { + ThrowWrongRentedArraySizeException(minimumLength, _blockSize); + } + + MemoryBucket? bucket; + lock (_updateLock) + //_lock.Wait(); + //try + { + if (_currentBucket != null) + { + _blockCount--; + bucket = _currentBucket; + _currentBucket = _currentBucket.NextBucket; + } + else + { + bucket = null; + //if (_bufferFillUpTask == null) + //{ + // _bufferFillUpTask = CreateAdditionalBucket(minimumLength, _minBlockCount); + //} + } + } + //finally + //{ + // _ = _lock.Release(); + // //_lock.Set(); + //} + + return bucket != null ? bucket.Memory : new T[_blockSize]; + } + + public override void Return(T[] array, bool clearArray = false) + { + if (array.Length != _blockSize) + { + ThrowWrongReturnedArraySizeException(array.Length, _blockSize); + } + + MemoryBucket bucket = new(array); + if (clearArray) + { + Array.Clear(array, 0, array.Length); + } + + lock (_updateLock) + //lock (_lock) + //_lock.Wait(); + //try + { + if (_blockCount < _minBlockCount) + { + bucket.NextBucket = _currentBucket; + _currentBucket = bucket; + _blockCount++; + } + } + //finally + //{ + // _ = _lock.Release(); + // //_lock.Set(); + //} + } + + private static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize) + { + throw new InvalidOperationException($"The array size {returnedLength} doesn't match the size {blockSize} of the pool buffer and cannot be returned to this pool."); + } + + private static void ThrowWrongRentedArraySizeException(int requestedSize, int blockSize) + { + throw new InvalidOperationException($"The requested size {requestedSize} doesn't match the size {blockSize} of the pool buffer and cannot be taken from this pool."); + } + } +} diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 15b268a..43b5212 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -15,7 +15,7 @@ namespace Recyclable.Collections private static readonly T[] _emptyBlockArray = new T[0]; private int _blockSize; - private int _blockSizePow2Shift; + private byte _blockSizePow2Shift; private int _nextItemBlockIndex; private int _nextItemIndex; @@ -65,7 +65,7 @@ namespace Recyclable.Collections while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) { int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 - ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) : Math.Min(nextItemIndex, blockSize - targetItemIndex); Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); @@ -100,9 +100,8 @@ namespace Recyclable.Collections protected void AddRangeEnumerated(IEnumerable source, int growByCount) { long i, copied = _longCount; - int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; - + using IEnumerator enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) { @@ -240,13 +239,11 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableList list, long newCapacity) + protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizeBitShift, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; - int minBlockSize = list._blockSize; - int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); - int sourceBlockCount = checked((int)(list._capacity >> minBlockSizePowerToShift)); - int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int sourceBlockCount = checked((int)(list._capacity >> minBlockSizeBitShift)); + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizeBitShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; Span memoryBlocksSpan; @@ -313,7 +310,7 @@ namespace Recyclable.Collections } } - return (long)newMemoryBlocks.Length << minBlockSizePowerToShift; + return (long)newMemoryBlocks.Length << minBlockSizeBitShift; } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -323,37 +320,60 @@ namespace Recyclable.Collections ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) : requestedCapacity; - newCapacity = Resize(list, newCapacity); - if (newCapacity > 0 && list._capacity == 0) + int blockSize = list._blockSize; + newCapacity = RecyclableList.Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + if (newCapacity > 0 && blockSize != list._memoryBlocks[0].Length) { - list._blockSize = list._memoryBlocks[0].Length; - list._blockSizePow2Shift = MathUtils.GetPow2Shift(list._blockSize); + blockSize = list._memoryBlocks[0].Length; + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockArrayPool = RecyclableArrayPool.Shared(blockSize); + } + + list._blockSizePow2Shift = (byte)MathUtils.GetPow2Shift(blockSize); + list._blockSize = blockSize; } list._capacity = newCapacity; return newCapacity; } + [MethodImpl(MethodImplOptions.AggressiveOptimization)] public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; - _blockArrayPool = blockArrayPool ?? _defaultBlockArrayPool; if (expectedItemsCount > 0) { - _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _capacity = Resize(this, expectedItemsCount.Value); - if (_memoryBlocks!.Length > 0) + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_memoryBlocks!.Length > 0 && _blockSize != _memoryBlocks[0].Length) { _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); + } + } + else + { + _blockSize = minBlockSize; } - - _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); } else { _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) + : RecyclableArrayPool.Null; + _memoryBlocks = _emptyMemoryBlocksArray; } } @@ -361,25 +381,39 @@ namespace Recyclable.Collections 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) { - _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _capacity = Resize(this, expectedItemsCount.Value); - if (_memoryBlocks.Length > 0) + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_memoryBlocks?.Length > 0 && minBlockSize != _memoryBlocks[0].Length) { _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); + } + } + else + { + _blockSize = minBlockSize; } - - _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); } else { _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -635,7 +669,9 @@ namespace Recyclable.Collections targetBlockIndex = _nextItemBlockIndex, memoryBlockCount = _memoryBlocks.Length; - T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; try { items.CopyTo(itemsBuffer, 0); @@ -665,7 +701,7 @@ namespace Recyclable.Collections { if (items.Count >= RecyclableDefaults.MinPooledArrayLength) { - _blockArrayPool.Return(itemsBuffer, NeedsClearing); + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); } } } From fdea416dc1e90ce9bf1dc31826c62c4790738622 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 10 Apr 2023 22:37:15 +0200 Subject: [PATCH 198/305] RecyclableList[index] tests added for `get`. --- Recyclable.CollectionsTests/RecyclableListTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 3f41035..af4a0aa 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -184,6 +184,20 @@ namespace Recyclable.CollectionsTests _ = list.Should().ContainInConsecutiveOrder(testData); } + [Theory] + [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + public void GetByIndexShouldReturnCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + foreach (var item in testData) + { + _ = list[item - 1].Should().Be(item); + } + } + [Theory] [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] //[InlineData("Debugging case", new long[1] { 1 }, 1, 2)] From 6b2db42d0d8a07d823b1c584dba09f2376b453b5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 10 Apr 2023 22:56:02 +0200 Subject: [PATCH 199/305] Partial support for items counts exceeding int.MaxValue --- .../BenchmarkBase.cs | 17 +++++++++++++---- .../RecyclableCollectionsBenchmarks.Array.cs | 6 ++++-- .../RecyclableCollectionsBenchmarks.List.cs | 6 +++--- ...ecyclableCollectionsBenchmarks.PooledList.cs | 16 ++++++++-------- ...CollectionsBenchmarks.RecyclableArrayList.cs | 6 +++--- ...lableCollectionsBenchmarks.RecyclableList.cs | 10 +++++----- .../RecyclableCollectionsBenchmarks.cs | 14 +++++++++++--- 7 files changed, 47 insertions(+), 28 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index c1244b2..1c2dbe0 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -6,7 +6,7 @@ namespace Recyclable.Collections.Benchmarks { //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public int TestObjectCount = 1_000_000; + public long TestObjectCount = 1_000_000; //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] //[Params(10, 20, 50, 100)] @@ -15,8 +15,8 @@ namespace Recyclable.Collections.Benchmarks public int BlockSize => TestObjectCount switch { 0 => 1, - > 0 and <= 10 => TestObjectCount, - _ => TestObjectCount / Divider > 0 ? TestObjectCount / Divider : TestObjectCount + > 0 and <= 10 => (int)TestObjectCount, + _ => TestObjectCount / Divider > 0 ? checked((int)(TestObjectCount / Divider)) : checked((int)TestObjectCount) }; protected object[]? _testObjects; @@ -29,7 +29,16 @@ namespace Recyclable.Collections.Benchmarks 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 IEnumerable TestObjectsAsIEnumerable + { + get + { + for (long i = 0; i < TestObjectCount; i++) + { + yield return new object(); + } + } + } protected static void DoNothing(T obj) { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index 0625abc..4dc11c4 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Attributes; +using MoreLinq.Extensions; namespace Recyclable.Collections.Benchmarks { @@ -17,9 +18,10 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) + long index = 0; + for(var i = 0; i < dataCount; i++) { - data[i] = TestObjects[i]; + data[index] = TestObjects[index++]; } } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 91ca294..e7d4615 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -14,7 +14,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = false)] public void List_Create_WithCapacity() { - var list = new List(TestObjectCount); + var list = new List(checked((int)TestObjectCount)); DoNothing(list); } @@ -35,7 +35,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - var list = new List(dataCount); + var list = new List(checked((int)dataCount)); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -79,7 +79,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - var list = new List(dataCount); + var list = new List(checked((int)dataCount)); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs index 04243ae..0829c89 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs @@ -15,7 +15,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void PooledList_Create_WithCapacity() { - using var list = new PooledList(capacity: TestObjectCount); + using var list = new PooledList(capacity: checked((int)TestObjectCount)); DoNothing(list); } @@ -36,7 +36,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(capacity: dataCount); + using var list = new PooledList(capacity: checked((int)dataCount)); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -47,7 +47,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsArray() { var data = TestObjects; - using var list = new PooledList(capacity: TestObjectCount); + using var list = new PooledList(capacity: checked((int)TestObjectCount)); list.AddRange(data); } @@ -55,7 +55,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; - using var list = new PooledList(capacity: TestObjectCount); + using var list = new PooledList(capacity: checked((int)TestObjectCount)); list.AddRange(data); } @@ -63,7 +63,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsPooledList; - using var list = new PooledList(capacity: TestObjectCount); + using var list = new PooledList(capacity: checked((int)TestObjectCount)); list.AddRange(data); } @@ -71,7 +71,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; - using var list = new PooledList(capacity: TestObjectCount); + using var list = new PooledList(capacity: checked((int)TestObjectCount)); list.AddRange(data); } @@ -79,7 +79,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsPooledList() { var data = TestObjectsAsPooledList; - using var list = new PooledList(capacity: TestObjectCount); + using var list = new PooledList(capacity: checked((int)TestObjectCount)); list.AddRange(data); } @@ -88,7 +88,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(capacity: TestObjectCount); + using var list = new PooledList(capacity: checked((int)TestObjectCount)); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index b5bc74a..02eee54 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -14,7 +14,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void RecyclableArrayList_Create_WithCapacity() { - using var list = new RecyclableArrayList(TestObjectCount); + using var list = new RecyclableArrayList(checked((int)TestObjectCount)); DoNothing(list); } @@ -35,7 +35,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(dataCount); + using var list = new RecyclableArrayList(checked((int)dataCount)); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -87,7 +87,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(dataCount); + using var list = new RecyclableArrayList(checked((int)dataCount)); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 2dc5e42..c750122 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -176,7 +176,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_GetItem_AsWhile() { var data = _testRecyclableList; - int dataCount = TestObjectCount; + long dataCount = TestObjectCount; if (dataCount == 0) { return; @@ -315,7 +315,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_Remove_FirstItems() { var data = TestObjects; - using var list = new RecyclableList(data); + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -327,7 +327,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_Remove_LastItems() { var data = TestObjects; - using var list = new RecyclableList(data); + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { @@ -339,7 +339,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_RemoveAt_FirstItems() { var data = TestObjects; - using var list = new RecyclableList(data); + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -351,7 +351,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_RemoveAt_LastItems() { var data = TestObjects; - using var list = new RecyclableList(data); + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs index 4bce805..5a6e8f4 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs @@ -16,9 +16,9 @@ namespace Recyclable.Collections.Benchmarks public void Setup() { Console.WriteLine("******* GLOBAL SETUP RAISED *******"); - _testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); - _testRecyclableArrayList = new(TestObjects, initialCapacity: TestObjectCount); - _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testObjects = EnumerateTestObjects().ToArray(); + _testRecyclableArrayList = new(TestObjects, initialCapacity: checked((int)TestObjectCount)); + _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: checked((int)TestObjectCount)); _testArray = TestObjects.ToArray(); _testObjectsAsList = TestObjects.ToList(); _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); @@ -26,6 +26,14 @@ namespace Recyclable.Collections.Benchmarks _testPooledList = new PooledList(TestObjects); } + private IEnumerable EnumerateTestObjects() + { + for (long i = 0; i < TestObjectCount; i++) + { + yield return new object(); + } + } + [GlobalCleanup] public void Cleanup() { From 767892a9eb9ddc95c652b55b74334d3c447df58c Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 10 Apr 2023 23:07:51 +0200 Subject: [PATCH 200/305] Array_IndexOf_FirstItems benchmark added. --- .../RecyclableCollectionsBenchmarks.Array.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index 4dc11c4..82e6eda 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -49,5 +49,17 @@ namespace Recyclable.Collections.Benchmarks var data = _testArray; DoNothing(data.LongLength); } + + [Benchmark(Baseline = false)] + public void Array_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[i])); + } + } } } From 0063f11ff52022e20c872a4993e7f8817a139a28 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 00:12:53 +0200 Subject: [PATCH 201/305] Clear mode set to ClearMode.Always in PooledList benchmarks to have fair comparison on worst case. --- ...yclableCollectionsBenchmarks.PooledList.cs | 28 +++++++++---------- .../RecyclableCollectionsBenchmarks.cs | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs index 0829c89..4c8b91c 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs @@ -8,14 +8,14 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark] public void PooledList_Create() { - using var list = new PooledList(); + using var list = new PooledList(ClearMode.Always); DoNothing(list); } //[Benchmark] public void PooledList_Create_WithCapacity() { - using var list = new PooledList(capacity: checked((int)TestObjectCount)); + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); DoNothing(list); } @@ -24,7 +24,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(); + using var list = new PooledList(ClearMode.Always); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -36,7 +36,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(capacity: checked((int)dataCount)); + using var list = new PooledList(capacity: checked((int)dataCount), ClearMode.Always); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -47,7 +47,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsArray() { var data = TestObjects; - using var list = new PooledList(capacity: checked((int)TestObjectCount)); + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); list.AddRange(data); } @@ -55,7 +55,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; - using var list = new PooledList(capacity: checked((int)TestObjectCount)); + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); list.AddRange(data); } @@ -63,7 +63,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsPooledList; - using var list = new PooledList(capacity: checked((int)TestObjectCount)); + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); list.AddRange(data); } @@ -71,7 +71,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; - using var list = new PooledList(capacity: checked((int)TestObjectCount)); + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); list.AddRange(data); } @@ -79,7 +79,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsPooledList() { var data = TestObjectsAsPooledList; - using var list = new PooledList(capacity: checked((int)TestObjectCount)); + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); list.AddRange(data); } @@ -88,7 +88,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(capacity: checked((int)TestObjectCount)); + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); list.AddRange(data); } @@ -173,7 +173,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_Remove_FirstItems() { var data = TestObjects; - using var list = new PooledList(data); + using var list = new PooledList(data, ClearMode.Always); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -185,7 +185,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_Remove_LastItems() { var data = TestObjects; - using var list = new PooledList(data); + using var list = new PooledList(data, ClearMode.Always); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { @@ -197,7 +197,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_RemoveAt_FirstItems() { var data = TestObjects; - using var list = new PooledList(data); + using var list = new PooledList(data, ClearMode.Always); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -209,7 +209,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_RemoveAt_LastItems() { var data = TestObjects; - using var list = new PooledList(data); + using var list = new PooledList(data, ClearMode.Always); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs index 5a6e8f4..422fe09 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs @@ -23,7 +23,7 @@ namespace Recyclable.Collections.Benchmarks _testObjectsAsList = TestObjects.ToList(); _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); _testObjectsAsRecyclableList = TestObjects.ToRecyclableList(); - _testPooledList = new PooledList(TestObjects); + _testPooledList = new PooledList(TestObjects, ClearMode.Always); } private IEnumerable EnumerateTestObjects() From 91515cb76b598c603f4271b6b9c41bf1850a7298 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 00:13:59 +0200 Subject: [PATCH 202/305] Typo fixed. Basic RecyclableList.Resize documentation added. --- Recyclable.Collections/RecyclableList.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 43b5212..20723c2 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -238,6 +238,19 @@ namespace Recyclable.Collections return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; } + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizeBitShift, long newCapacity) { @@ -247,7 +260,7 @@ namespace Recyclable.Collections int blockIndex; Span memoryBlocksSpan; - // Release excesive blocks if we're downsizing + // Release excessive blocks if we're downsizing if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) { memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, sourceBlockCount - requiredBlockCount); From 24a14f810bcd4ad9f3432aa099133dcaefe4b2af Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 00:16:05 +0200 Subject: [PATCH 203/305] Additional benchmarks added. One-off quick smoke test added. --- Recyclable.Collections.Benchmarks/Program.cs | 48 ++++++++++++++------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 82b9a7a..8ff8914 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -29,20 +29,25 @@ internal class Program { var benchmark = new RecyclableCollectionsBenchmarks(); benchmark.Setup(); - foreach (var _ in Enumerable.Range(1, 1000)) - { - //benchmark.RecyclableList_Contains(); - //benchmark.RecyclableArrayList_AddRange_WithCapacity(); - //benchmark.RecyclableArrayList_Add_WithCapacity(); + //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.RecyclableList_Contains_LastItems(); - //benchmark.RecyclableList_IndexOf_FirstItems(); - benchmark.RecyclableList_AddRangeWhenSourceIsList(); - } + //benchmark.RecyclableList_Create(); + //benchmark.RecyclableList_Create_WithCapacity(); + //benchmark.RecyclableList_Add(); + //benchmark.RecyclableList_Add_WithCapacity(); + //benchmark.RecyclableList_Contains_LastItems(); + //benchmark.RecyclableList_IndexOf_FirstItems(); + //benchmark.RecyclableList_AddRangeWhenSourceIsList(); + //benchmark.RecyclableList_Remove_FirstItems(); + //benchmark.RecyclableList_RemoveAt_LastItems(); + //benchmark.RecyclableList_EnsureCapacity_New_ByBlockSize(); + benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); + //benchmark.List_RemoveAt_LastItems(); + //} benchmark.Cleanup(); } @@ -53,6 +58,23 @@ internal class Program ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary) ); + //var pool = ArrayPool.Shared; + //for (long bufferSize = int.MaxValue / 2; bufferSize <= int.MaxValue; bufferSize += 100_000_000) + //{ + // try + // { + // Console.WriteLine($"Allocating {bufferSize}"); + // var arr = pool.Rent((int)bufferSize); + // pool.Return(arr); + // GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + // } + // catch (Exception) + // { + // Console.WriteLine($"Error during array creation. {nameof(bufferSize)} = {bufferSize}"); + // throw; + // } + //} + //RunBenchmark(); From eae5cb44529f108a8504fc9a981914b1732e458a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 00:46:22 +0200 Subject: [PATCH 204/305] Changed all benchmarks to be non-baseline by default. Missing parameters added everywhere. --- .../RecyclableCollectionsBenchmarks.Array.cs | 2 +- .../RecyclableCollectionsBenchmarks.List.cs | 40 +++++++------- ...yclableCollectionsBenchmarks.PooledList.cs | 42 +++++++-------- ...llectionsBenchmarks.RecyclableArrayList.cs | 34 ++++++------ ...bleCollectionsBenchmarks.RecyclableList.cs | 54 +++++++++---------- 5 files changed, 86 insertions(+), 86 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index 82e6eda..10da984 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -6,7 +6,7 @@ namespace Recyclable.Collections.Benchmarks [MemoryDiagnoser] public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - [Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void Array_Create_WithCapacity() { var data = new object[TestObjectCount]; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index e7d4615..a0bafdc 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Create() { var list = new List(); DoNothing(list); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Create_WithCapacity() { var list = new List(checked((int)TestObjectCount)); DoNothing(list); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -74,7 +74,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -83,7 +83,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_SetItem() { var data = TestObjectsAsList; @@ -94,7 +94,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_GetItem() { var data = TestObjectsAsList; @@ -105,14 +105,14 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Count() { var data = TestObjectsAsList; DoNothing(data.Count); } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Contains_FirstItems() { var data = TestObjects; @@ -124,7 +124,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Contains_LastItems() { var data = TestObjects; @@ -136,7 +136,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_IndexOf_FirstItems() { var data = TestObjects; @@ -148,7 +148,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_IndexOf_LastItems() { var data = TestObjects; @@ -160,7 +160,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void List_RemoveAt_FirstItems() { var data = TestObjects; @@ -172,7 +172,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_RemoveAt_LastItems() { var data = TestObjects; @@ -184,7 +184,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Remove_FirstItems() { var data = TestObjects; @@ -196,7 +196,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void List_Remove_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs index 4c8b91c..f20abf9 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs @@ -5,21 +5,21 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_Create() { using var list = new PooledList(ClearMode.Always); DoNothing(list); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_Create_WithCapacity() { using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); DoNothing(list); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_Add() { var data = TestObjects; @@ -31,7 +31,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_Add_WithCapacity() { var data = TestObjects; @@ -43,7 +43,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -51,7 +51,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -59,7 +59,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsPooledList; @@ -67,7 +67,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -75,7 +75,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_AddRangeWhenSourceIsPooledList() { var data = TestObjectsAsPooledList; @@ -83,7 +83,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_AddRange_WithCapacity() { var data = TestObjects; @@ -92,7 +92,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_GetItem() { var data = _testPooledList; @@ -103,7 +103,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_SetItem() { var data = _testPooledList; @@ -114,14 +114,14 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_Count() { var data = _testPooledList; DoNothing(data.Count); } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_Contains_FirstItems() { var data = TestObjects; @@ -133,7 +133,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_Contains_LastItems() { var data = TestObjects; @@ -145,7 +145,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_IndexOf_FirstItems() { var data = TestObjects; @@ -157,7 +157,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void PooledList_IndexOf_LastItems() { var data = TestObjects; @@ -169,7 +169,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void PooledList_Remove_FirstItems() { var data = TestObjects; @@ -181,7 +181,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void PooledList_Remove_LastItems() { var data = TestObjects; @@ -193,7 +193,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void PooledList_RemoveAt_FirstItems() { var data = TestObjects; @@ -205,7 +205,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void PooledList_RemoveAt_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 02eee54..d561175 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(Baseline = false)] public void RecyclableArrayList_Create() { using var list = new RecyclableArrayList(); DoNothing(list); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_Create_WithCapacity() { using var list = new RecyclableArrayList(checked((int)TestObjectCount)); DoNothing(list); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -74,7 +74,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; @@ -82,7 +82,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_AddRange_WithCapacity() { var data = TestObjects; @@ -91,7 +91,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_SetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -102,7 +102,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_GetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -113,14 +113,14 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_Count() { var data = TestObjectsAsRecyclableArrayList; DoNothing(data.Count); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableArrayList_IndexOf_FirstItems() { var data = TestObjects; @@ -132,7 +132,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void RecyclableArrayList_Remove_FirstItems() { var data = TestObjects; @@ -144,7 +144,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void RecyclableArrayList_Remove_LastItems() { var data = TestObjects; @@ -156,7 +156,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void RecyclableArrayList_RemoveAt_FirstItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index c750122..801fad9 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(Baseline = false)] public void RecyclableList_Create() { using var list = new RecyclableList(); DoNothing(list); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_Create_WithCapacity() { using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_Add() { var data = TestObjects; @@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_Add_WithCapacity() { var data = TestObjects; @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -58,7 +58,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -74,7 +74,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; @@ -82,7 +82,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_AddRange_WithCapacity() { var data = TestObjects; @@ -91,7 +91,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_GetItem() { var data = _testRecyclableList; @@ -102,7 +102,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsSpan() { var data = _testRecyclableList; @@ -131,7 +131,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsArray() { var data = _testRecyclableList; @@ -160,7 +160,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsForEach() { var data = _testRecyclableList; @@ -172,7 +172,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsWhile() { var data = _testRecyclableList; @@ -214,7 +214,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_SetItem() { var data = _testRecyclableList; @@ -225,21 +225,21 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_Count() { var data = _testRecyclableList; DoNothing(data.Count); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_LongCount() { var data = _testRecyclableList; DoNothing(data.LongCount); } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_Contains_FirstItems() { var data = TestObjects; @@ -251,7 +251,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_Contains_LastItems() { var data = TestObjects; @@ -263,7 +263,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_IndexOf_FirstItems() { var data = TestObjects; @@ -275,7 +275,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_IndexOf_LastItems() { var data = TestObjects; @@ -287,7 +287,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_LongIndexOf_FirstItems() { var data = TestObjects; @@ -299,7 +299,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = false)] public void RecyclableList_LongIndexOf_LastItems() { var data = TestObjects; @@ -311,7 +311,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void RecyclableList_Remove_FirstItems() { var data = TestObjects; @@ -323,7 +323,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void RecyclableList_Remove_LastItems() { var data = TestObjects; @@ -335,7 +335,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] + [Benchmark(Baseline = false)] public void RecyclableList_RemoveAt_FirstItems() { var data = TestObjects; From 2d7e62df7294e64d61ae13e870081fde8b44f3c5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 16:42:06 +0200 Subject: [PATCH 205/305] Benchmarks mostly reorganized & cleaned up. Simplified for re-runs. --- .../BenchmarkBase.cs | 19 +- .../DelegateVsComparerBenchmark.cs | 252 ----- .../ModuloBenchmarks.cs | 355 ------- .../POC/ArraySizeLimitPoc.Benchmarks.cs | 55 ++ .../POC/DelegateVsComparerPoc.Benchmarks.cs | 274 ++++++ .../POC/ModuloPoc.Benchmarks.cs | 374 ++++++++ .../POC/PocBenchmarkBase.cs | 25 + ...clableListPoc.Benchmarks.EnsureCapacity.cs | 908 ++++++++++++++++++ .../POC/RecyclableListPoc.Benchmarks.cs | 14 + .../POC/RefVsInstanceMemberPoc.Benchmarks.cs | 362 +++++++ Recyclable.Collections.Benchmarks/Program.cs | 108 +-- .../RefVsInstanceMemberBenchmarks.cs | 339 ------- 12 files changed, 2045 insertions(+), 1040 deletions(-) delete mode 100644 Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs delete mode 100644 Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs delete mode 100644 Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index 1c2dbe0..874f783 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -1,24 +1,9 @@ -using BenchmarkDotNet.Attributes; +using Recyclable.Collections.Benchmarks.POC; namespace Recyclable.Collections.Benchmarks { - public class BenchmarkBase + public class BenchmarkBase : PocBenchmarkBase { - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public long TestObjectCount = 1_000_000; - - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] - //[Params(10, 20, 50, 100)] - //[Params(1, 10)] - public int Divider = 10; - public int BlockSize => TestObjectCount switch - { - 0 => 1, - > 0 and <= 10 => (int)TestObjectCount, - _ => TestObjectCount / Divider > 0 ? checked((int)(TestObjectCount / Divider)) : checked((int)TestObjectCount) - }; - protected object[]? _testObjects; protected object[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); diff --git a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs deleted file mode 100644 index 2eb4204..0000000 --- a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs +++ /dev/null @@ -1,252 +0,0 @@ -using BenchmarkDotNet.Attributes; -using MiscUtil; - -namespace Recyclable.Collections.Benchmarks -{ - public delegate T Compare(T x, T y); - - public readonly struct MyComparerReadOnlyStruct : IComparer - { - public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - public struct MyComparerStruct : IComparer - { - public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - public class MyComparer : IComparer - { - public int Compare(long x, long y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - [MemoryDiagnoser] - public class DelegateVsComparerBenchmark - { - private static readonly Comparer _comparer = Comparer.Default; - - private const int _objectCount = 1_000_000; - private static readonly IEnumerable _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x); - private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static x => new MyComparer().Compare); - private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(x => (IComparer)new MyComparer()); - private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(x => new Compare((x, y) => new MyComparer().Compare(x, y))); - private static readonly IEnumerable> _testListOfLocalFunctions = GetLocalFunctions(_testObjects); - private static readonly IEnumerable _testListOfClasses = _testObjects.Select(x => new MyComparer()); - private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(x => new MyComparerStruct()); - private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(x => (IComparer)new MyComparerStruct()); - private static IEnumerable _testListOfUnboxedStructs() where TComparer : IComparer, new() - { - foreach (var item in _testObjects) - { - yield return new TComparer(); - } - } - - private static IEnumerable> GetLocalFunctions(IEnumerable testObjects) - { - static int compareFunc(long x, long y) - { - return (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }; - } - - foreach (var item in testObjects) - { - yield return compareFunc; - } - } - - private static void TestWithComparer(IComparer comparer) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = comparer.Compare(testData[itemIdx], testData[itemIdx + 1]); - } - } - - //[Benchmark(Baseline = true)] - public void Comparer() - { - TestWithComparer(_comparer); - } - - private static void TestWithFunc(Func compareFunc) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); - } - } - - //[Benchmark] - public void Func() - { - TestWithFunc((x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - //[Benchmark] - public void StaticFunc() - { - TestWithFunc(static (x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - private static void TestWithDelegate(Compare compareFunc) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); - } - } - - //[Benchmark] - public void Delegate() - { - TestWithDelegate((x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - //[Benchmark] - public void ListOfFuncs() - { - const long x = 2, y = 5; - var testData = _testListOfFunctions.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareFunc = testData[itemIdx]; - _ = compareFunc(x, y); - } - } - - //[Benchmark] - public void ListOfLocalFuncs() - { - const long x = 2, y = 5; - var testData = _testListOfLocalFunctions.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareFunc = testData[itemIdx]; - _ = compareFunc(x, y); - } - } - - //[Benchmark] - public void ListOfComparers() - { - const long x = 2, y = 5; - var testData = _testListOfComparers.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfClasses() - { - const long x = 2, y = 5; - var testData = _testListOfClasses.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfStructs() - { - const long x = 2, y = 5; - var testData = _testListOfStructs.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfBoxedStructs() - { - const long x = 2, y = 5; - var testData = _testListOfBoxedStructs.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfUnboxedStructs() - { - const long x = 2, y = 5; - var testData = _testListOfUnboxedStructs, long>().ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfUnboxedReadOnlyStructs() - { - const long x = 2, y = 5; - var testData = _testListOfUnboxedStructs, long>().ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfDelegates() - { - const long x = 2, y = 5; - var testData = _testListOfDelegates.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareDelegate = testData[itemIdx]; - _ = compareDelegate(x, y); - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs b/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs deleted file mode 100644 index dfa9d3f..0000000 --- a/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs +++ /dev/null @@ -1,355 +0,0 @@ -using BenchmarkDotNet.Attributes; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace Recyclable.Collections.Benchmarks -{ - [MemoryDiagnoser] - public class ModuloBenchmarks - { - private long _cutoff; - private Vector128 _increment; - private long _lastCaseIndex; - - [GlobalSetup] - public void Setup() - { - _cutoff = BlockSize << 4; - _increment = Vector128.Create(-BlockSize, 1L); - _lastCaseIndex = 0; - } - - private static void DoNothing(T result, T remainder, [CallerMemberName] string? callerName = null) - { - //Console.WriteLine($"{callerName}: result = {result}, remainder = {remainder}"); - } - - //[Params(123, 10, 2, int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 10, int.MaxValue / 15, int.MaxValue / 20)] - [Params(int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 8, int.MaxValue / 10, int.MaxValue / 100)] - //[Params(int.MaxValue / 10, int.MaxValue / 20, int.MaxValue / 100)] - public int BlockSize { get; set; } = int.MaxValue; - - [Params(int.MaxValue, int.MaxValue / 1000, int.MaxValue / 10000)] - //[Params(int.MaxValue)] - //[Params(int.MaxValue * 100L)] - public long TestNumber { get; set; } = int.MaxValue; - - private void CalculateDividerAndModulusWithOperator(long i) - { - DoNothing(i / BlockSize, i % BlockSize); - } - - private void CalculateWithMathDivRem(long i) - { - DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); - } - - private void CalculateWithAddWithFor(long i) - { - int result = 0; - int remainder = 0; - int blockSize = BlockSize; - for (long iteration = 0; iteration < i; iteration++) - { - if (remainder > blockSize - 1) - { - remainder = 0; - result++; - } - - remainder++; - } - - DoNothing(result, remainder); - } - - private void CalculateWithAddWithForOptimized(long i) - { - long total = 0; - int result = 0; - int blockSize = BlockSize; - while (total + blockSize <= i) - { - total += blockSize; - result++; - } - - DoNothing(result, i - total); - } - - private void CalculateWithXDivYMulX(long i) - { - DoNothing(i / BlockSize, i - (i / BlockSize * BlockSize)); - } - - private void CalculateWithXDivYMulXWithLocalVar(long i) - { - int blockSize = BlockSize; - int result = (int)(i / blockSize); - DoNothing(result, i - (result * blockSize)); - } - - private void CalculateWithSubtractWithWhile(long i) - { - int result = 0; - long remainder = i; - int blockSize = BlockSize; - while (remainder >= blockSize) - { - remainder -= blockSize; - result++; - } - - DoNothing(result, remainder); - } - - private void CalculateWithSubractWithWhileWithSse2(long i) - { - var state = Vector128.Create(i, 0); - int blockSize = BlockSize; - var inc = _increment; - while (state.GetElement(0) >= blockSize) - { - state = Sse2.Add(state, inc); - } - - DoNothing(state.GetElement(1), state.GetElement(0)); - } - - private void CalculateWithMixedApproachWithSubractWithWhile(long i) - { - long remainder; - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out remainder), remainder); - } - else - { - int result = 0; - int blockSize = BlockSize; - remainder = i; - while (remainder >= blockSize) - { - remainder -= blockSize; - result++; - } - - DoNothing(result, remainder); - } - } - - private void CalculateWithMixedApproachWithSubtractWithWhileAsVector(long i) - { - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); - } - else - { - var state = new Vector(new[] { 0L, i, 0, 0 }); - var increment = new Vector(new[] { 1L, -BlockSize, 0, 0 }); - int blockSize = BlockSize; - while (state[1] >= blockSize) - { - state += increment; - } - - DoNothing(state[0], state[1]); - } - } - - private void CalculateWithMixedApproachWithSubtractWithWhileWithSse2(long i) - { - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); - } - else - { - var increment = _increment; - var state = Vector128.Create(i, 0); - int blockSize = BlockSize; - while (state.GetElement(0) >= blockSize) - { - state = Sse2.Add(state, increment); - } - - DoNothing(state.GetElement(1), state.GetElement(0)); - } - } - - private void CalculateWithMixedApproachWithSubtractWithStackalloc(long i) - { - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); - } - else - { - Span state = stackalloc long[2] { i, 0 };// Vector128.Create(BlockSize, 0); - int blockSize = BlockSize; - while (state[0] >= blockSize) - { - state[0] -= blockSize; - state[1]++; - } - - DoNothing(state[1], state[0]); - } - } - - private void CalculateWithMixedApproachWithForLoopOptimized(long i) - { - long total; - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out total), total); - } - else - { - total = 0; - int result = 0; - int blockSize = BlockSize; - while (total + blockSize <= i) - { - total += blockSize; - result++; - } - - DoNothing(result, i - total); - } - } - - [Benchmark(Baseline = true)] - public void DividerAndModulusWithOperator() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateDividerAndModulusWithOperator(i); - } - } - - [Benchmark] - public void MathDivRem() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMathDivRem(i); - } - } - - [Benchmark] - public void AddWithFor() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithAddWithFor(i); - } - } - - [Benchmark(Baseline = true)] - public void AddWithForOptimized() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithAddWithForOptimized(i); - } - } - - [Benchmark] - public void XDivYMulX() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithXDivYMulX(i); - } - } - - [Benchmark] - public void XDivYMulXWithLocalVar() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithXDivYMulXWithLocalVar(i); - } - } - - [Benchmark] - public void SubtractWithWhile() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithSubtractWithWhile(i); - } - } - - [Benchmark] - public void SubtractWithWhileWithSse2() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithSubractWithWhileWithSse2(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithSubtractWithWhile() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithSubractWithWhile(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithSubtractWithWhileAsVector() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithSubtractWithWhileAsVector(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithSubtractWithWhileWithSse2() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithSubtractWithWhileWithSse2(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithSubtractWithStackalloc() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithSubtractWithStackalloc(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithForLoopOptimized() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithForLoopOptimized(i); - } - } - } -} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs new file mode 100644 index 0000000..42c5783 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs @@ -0,0 +1,55 @@ +using BenchmarkDotNet.Attributes; +using System.Buffers; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class ArraySizeLimitPocBenchmarks : PocBenchmarkBase + { + public static void Run() + { + var benchmark = new ArraySizeLimitPocBenchmarks(); + benchmark.FindArraySizeLimit(); + } + + [Benchmark] + public void FindArraySizeLimit() + { + var pool = ArrayPool.Shared; + var bufferSize = int.MaxValue; + var oldValue = 0; + while (true) + { + try + { + Console.WriteLine($"Allocating {bufferSize}"); + var arr = pool.Rent((int)bufferSize); + var nextValue = (oldValue - bufferSize) >> 1; + if (nextValue == 0) + { + break; + } + + oldValue = bufferSize; + bufferSize += nextValue; + + pool.Return(arr); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + } + catch (Exception) + { + Console.WriteLine($"Error during array creation when {nameof(bufferSize)} = {bufferSize}"); + var nextValue = (oldValue - bufferSize) >> 1; + if (nextValue == 0) + { + break; + } + + oldValue = bufferSize; + bufferSize -= nextValue; + } + } + + Console.WriteLine("All done"); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs new file mode 100644 index 0000000..4cd9e75 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs @@ -0,0 +1,274 @@ +using BenchmarkDotNet.Attributes; +using MiscUtil; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class DelegateVsComparerPocBenchmarks : PocBenchmarkBase + { + #region Dummy classes + + private delegate T Compare(T x, T y); + + public readonly struct MyComparerReadOnlyStruct : IComparer + { + public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + private struct MyComparerStruct : IComparer + { + public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + private class MyComparer : IComparer + { + public int Compare(long x, long y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + #endregion + + public static void Run() + { + var benchmark = new DelegateVsComparerPocBenchmarks(); + benchmark.Comparer(); + benchmark.Delegate(); + benchmark.Func(); + benchmark.ListOfBoxedStructs(); + benchmark.ListOfClasses(); + benchmark.ListOfComparers(); + benchmark.ListOfDelegates(); + benchmark.ListOfFuncs(); + benchmark.ListOfLocalFuncs(); + benchmark.ListOfStructs(); + benchmark.ListOfUnboxedReadOnlyStructs(); + benchmark.ListOfUnboxedStructs(); + benchmark.StaticFunc(); + } + + private static readonly Comparer _comparer = Comparer.Default; + + private const int _objectCount = 1_000_000; + private static readonly IEnumerable _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x); + private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static x => new MyComparer().Compare); + private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(x => (IComparer)new MyComparer()); + private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(x => new Compare((x, y) => new MyComparer().Compare(x, y))); + private static readonly IEnumerable> _testListOfLocalFunctions = GetLocalFunctions(_testObjects); + private static readonly IEnumerable _testListOfClasses = _testObjects.Select(x => new MyComparer()); + private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(x => new MyComparerStruct()); + private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(x => (IComparer)new MyComparerStruct()); + private static IEnumerable _testListOfUnboxedStructs() where TComparer : IComparer, new() + { + foreach (var item in _testObjects) + { + yield return new TComparer(); + } + } + + private static IEnumerable> GetLocalFunctions(IEnumerable testObjects) + { + static int compareFunc(long x, long y) + { + return (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }; + } + + foreach (var item in testObjects) + { + yield return compareFunc; + } + } + + private static void TestWithComparer(IComparer comparer) + { + var testData = _testObjects.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = comparer.Compare(testData[itemIdx], testData[itemIdx + 1]); + } + } + + [Benchmark(Baseline = true)] + public void Comparer() + { + TestWithComparer(_comparer); + } + + private static void TestWithFunc(Func compareFunc) + { + var testData = _testObjects.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); + } + } + + [Benchmark] + public void Func() + { + TestWithFunc((x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + [Benchmark] + public void StaticFunc() + { + TestWithFunc(static (x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + private static void TestWithDelegate(Compare compareFunc) + { + var testData = _testObjects.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); + } + } + + [Benchmark] + public void Delegate() + { + TestWithDelegate((x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + [Benchmark] + public void ListOfFuncs() + { + const long x = 2, y = 5; + var testData = _testListOfFunctions.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareFunc = testData[itemIdx]; + _ = compareFunc(x, y); + } + } + + [Benchmark] + public void ListOfLocalFuncs() + { + const long x = 2, y = 5; + var testData = _testListOfLocalFunctions.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareFunc = testData[itemIdx]; + _ = compareFunc(x, y); + } + } + + [Benchmark] + public void ListOfComparers() + { + const long x = 2, y = 5; + var testData = _testListOfComparers.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfClasses() + { + const long x = 2, y = 5; + var testData = _testListOfClasses.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfStructs() + { + const long x = 2, y = 5; + var testData = _testListOfStructs.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfBoxedStructs() + { + const long x = 2, y = 5; + var testData = _testListOfBoxedStructs.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfUnboxedStructs() + { + const long x = 2, y = 5; + var testData = _testListOfUnboxedStructs, long>().ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfUnboxedReadOnlyStructs() + { + const long x = 2, y = 5; + var testData = _testListOfUnboxedStructs, long>().ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfDelegates() + { + const long x = 2, y = 5; + var testData = _testListOfDelegates.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareDelegate = testData[itemIdx]; + _ = compareDelegate(x, y); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs new file mode 100644 index 0000000..d5f4ddb --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs @@ -0,0 +1,374 @@ +using BenchmarkDotNet.Attributes; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class ModuloPocBenchmarks + { + public static void Run() + { + var benchmark = new ModuloPocBenchmarks(); + benchmark.Setup(); + benchmark.AddWithFor(); + benchmark.AddWithForOptimized(); + benchmark.DividerAndModulusWithOperator(); + benchmark.MathDivRem(); + benchmark.MixedApproachWithForLoopOptimized(); + benchmark.MixedApproachWithSubtractWithStackalloc(); + benchmark.MixedApproachWithSubtractWithWhile(); + benchmark.MixedApproachWithSubtractWithWhileAsVector(); + benchmark.MixedApproachWithSubtractWithWhileWithSse2(); + benchmark.SubtractWithWhile(); + benchmark.SubtractWithWhileWithSse2(); + benchmark.XDivYMulX(); + benchmark.XDivYMulXWithLocalVar(); + } + + private long _cutoff; + private Vector128 _increment; + private long _lastCaseIndex; + + [GlobalSetup] + public void Setup() + { + _cutoff = BlockSize << 4; + _increment = Vector128.Create(-BlockSize, 1L); + _lastCaseIndex = 0; + } + + private static void DoNothing(T result, T remainder, [CallerMemberName] string? callerName = null) + { + //Console.WriteLine($"{callerName}: result = {result}, remainder = {remainder}"); + } + + //[Params(123, 10, 2, int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 10, int.MaxValue / 15, int.MaxValue / 20)] + [Params(int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 8, int.MaxValue / 10, int.MaxValue / 100)] + //[Params(int.MaxValue / 10, int.MaxValue / 20, int.MaxValue / 100)] + public int BlockSize { get; set; } = int.MaxValue; + + [Params(int.MaxValue, int.MaxValue / 1000, int.MaxValue / 10000)] + //[Params(int.MaxValue)] + //[Params(int.MaxValue * 100L)] + public long TestNumber { get; set; } = int.MaxValue; + + private void CalculateDividerAndModulusWithOperator(long i) + { + DoNothing(i / BlockSize, i % BlockSize); + } + + private void CalculateWithMathDivRem(long i) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + + private void CalculateWithAddWithFor(long i) + { + int result = 0; + int remainder = 0; + int blockSize = BlockSize; + for (long iteration = 0; iteration < i; iteration++) + { + if (remainder > blockSize - 1) + { + remainder = 0; + result++; + } + + remainder++; + } + + DoNothing(result, remainder); + } + + private void CalculateWithAddWithForOptimized(long i) + { + long total = 0; + int result = 0; + int blockSize = BlockSize; + while (total + blockSize <= i) + { + total += blockSize; + result++; + } + + DoNothing(result, i - total); + } + + private void CalculateWithXDivYMulX(long i) + { + DoNothing(i / BlockSize, i - (i / BlockSize * BlockSize)); + } + + private void CalculateWithXDivYMulXWithLocalVar(long i) + { + int blockSize = BlockSize; + int result = (int)(i / blockSize); + DoNothing(result, i - (result * blockSize)); + } + + private void CalculateWithSubtractWithWhile(long i) + { + int result = 0; + long remainder = i; + int blockSize = BlockSize; + while (remainder >= blockSize) + { + remainder -= blockSize; + result++; + } + + DoNothing(result, remainder); + } + + private void CalculateWithSubractWithWhileWithSse2(long i) + { + var state = Vector128.Create(i, 0); + int blockSize = BlockSize; + var inc = _increment; + while (state.GetElement(0) >= blockSize) + { + state = Sse2.Add(state, inc); + } + + DoNothing(state.GetElement(1), state.GetElement(0)); + } + + private void CalculateWithMixedApproachWithSubractWithWhile(long i) + { + long remainder; + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out remainder), remainder); + } + else + { + int result = 0; + int blockSize = BlockSize; + remainder = i; + while (remainder >= blockSize) + { + remainder -= blockSize; + result++; + } + + DoNothing(result, remainder); + } + } + + private void CalculateWithMixedApproachWithSubtractWithWhileAsVector(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + var state = new Vector(new[] { 0L, i, 0, 0 }); + var increment = new Vector(new[] { 1L, -BlockSize, 0, 0 }); + int blockSize = BlockSize; + while (state[1] >= blockSize) + { + state += increment; + } + + DoNothing(state[0], state[1]); + } + } + + private void CalculateWithMixedApproachWithSubtractWithWhileWithSse2(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + var increment = _increment; + var state = Vector128.Create(i, 0); + int blockSize = BlockSize; + while (state.GetElement(0) >= blockSize) + { + state = Sse2.Add(state, increment); + } + + DoNothing(state.GetElement(1), state.GetElement(0)); + } + } + + private void CalculateWithMixedApproachWithSubtractWithStackalloc(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + Span state = stackalloc long[2] { i, 0 };// Vector128.Create(BlockSize, 0); + int blockSize = BlockSize; + while (state[0] >= blockSize) + { + state[0] -= blockSize; + state[1]++; + } + + DoNothing(state[1], state[0]); + } + } + + private void CalculateWithMixedApproachWithForLoopOptimized(long i) + { + long total; + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out total), total); + } + else + { + total = 0; + int result = 0; + int blockSize = BlockSize; + while (total + blockSize <= i) + { + total += blockSize; + result++; + } + + DoNothing(result, i - total); + } + } + + [Benchmark(Baseline = true)] + public void DividerAndModulusWithOperator() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateDividerAndModulusWithOperator(i); + } + } + + [Benchmark] + public void MathDivRem() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMathDivRem(i); + } + } + + [Benchmark] + public void AddWithFor() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithAddWithFor(i); + } + } + + [Benchmark(Baseline = true)] + public void AddWithForOptimized() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithAddWithForOptimized(i); + } + } + + [Benchmark] + public void XDivYMulX() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithXDivYMulX(i); + } + } + + [Benchmark] + public void XDivYMulXWithLocalVar() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithXDivYMulXWithLocalVar(i); + } + } + + [Benchmark] + public void SubtractWithWhile() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithSubtractWithWhile(i); + } + } + + [Benchmark] + public void SubtractWithWhileWithSse2() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithSubractWithWhileWithSse2(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhile() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubractWithWhile(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhileAsVector() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithWhileAsVector(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhileWithSse2() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithWhileWithSse2(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithStackalloc() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithStackalloc(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithForLoopOptimized() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithForLoopOptimized(i); + } + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs new file mode 100644 index 0000000..552979a --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -0,0 +1,25 @@ +namespace Recyclable.Collections.Benchmarks.POC +{ + public class PocBenchmarkBase + { + // 0 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 + // 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 + // 1073741824 2147483648 + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + public long TestObjectCount = 1_000_000; + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] + //[Params(10, 20, 50, 100)] + //[Params(1, 10)] + public int Divider = 10; + //public int BlockSize = 1_048_576;//int.MaxValue - 1; + public int BlockSize => TestObjectCount switch + { + 0 => 1, + > 0 and <= 10 => (int)TestObjectCount, + _ => TestObjectCount / Divider > 0 ? checked((int)(TestObjectCount / Divider)) : checked((int)TestObjectCount) + }; + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs new file mode 100644 index 0000000..c0efe8b --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -0,0 +1,908 @@ +using BenchmarkDotNet.Attributes; +using System.Buffers; +using System.Collections; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public partial class RecyclableListPocBenchmarks + { + #region Old class + + private class OldRecyclableList : 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][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private int _blockSizePow2Shift; + private int _nextItemBlockIndex; + private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void DoRemoveAt(long index) + { + int blockSize = _blockSize; + int nextItemIndex = _nextItemIndex; + T[][] memoryBlocks = _memoryBlocks; + _longCount--; + if (index < _longCount) + { + int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); + int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); + + int targetBlockIndex = (int)(index >> _blockSizePow2Shift); + int targetItemIndex = (int)(index & (blockSize - 1)); + + int lastTakenBlockIndex = LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + if (nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; +#pragma warning restore CS8601 + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + { + int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); + int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); + int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); + int blockIndex; + T[][]? newMemoryBlocks; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + { + memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + } + } + + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(requiredBlockCount); + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(source!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(source!, true); + } + } + + source = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + return newMemoryBlocks.Length << minBlockSizePowerToShift; + } + + internal long EnsureCapacity(long requestedCapacity) + { + long newCapacity = _capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); + if (newCapacity > 0 && _capacity == 0) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); + } + + _capacity = newCapacity; + return newCapacity; + } + + public OldRecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + + if (expectedItemsCount > 0) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + + public OldRecyclableList(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) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(_longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(OldRecyclableList items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & (blockSize - 1)) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); + _nextItemBlockIndex = targetBlockIndex; + + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is OldRecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + } + } + + _capacity = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public void Insert(int index, T item) => throw new NotSupportedException(); + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public bool Remove(T item) + { + var itemIndex = LongIndexOf(item); + if (itemIndex >= 0) + { + DoRemoveAt(itemIndex); + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + public void RemoveAt(int index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + public void RemoveAt(long index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~OldRecyclableList() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + + #endregion + + [Benchmark(Baseline = true)] + public void RecyclableList_EnsureCapacity_Old_ByPowOf2() + { + using var list = new OldRecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = list.EnsureCapacity(TestObjectCount); + } + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacity_ByPowOf2() + { + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = RecyclableList.EnsureCapacity(list, TestObjectCount); + } + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacity_Old_ByBlockSize() + { + using var list = new OldRecyclableList(); + _ = list.EnsureCapacity(TestObjectCount); + } + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacity_ByBlockSize() + { + using var list = new RecyclableList(); + _ = RecyclableList.EnsureCapacity(list, TestObjectCount); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs new file mode 100644 index 0000000..738c0ea --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -0,0 +1,14 @@ +namespace Recyclable.Collections.Benchmarks.POC +{ + public partial class RecyclableListPocBenchmarks : PocBenchmarkBase + { + public static void Run() + { + var benchmark = new RecyclableListPocBenchmarks(); + benchmark.RecyclableList_EnsureCapacity_Old_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacity_Old_ByBlockSize(); + benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs new file mode 100644 index 0000000..3c9c429 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs @@ -0,0 +1,362 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class RefVsInstanceMemberPocBenchmarks + { + public static void Run() + { + var benchmark = new RefVsInstanceMemberPocBenchmarks(); + benchmark.ArrayInstanceMember2(); + benchmark.ArrayInstanceMember3(); + benchmark.ArrayInstanceMember4(); + benchmark.ArrayInstanceMember5(); + benchmark.ArrayInstanceMember6(); + benchmark.ArrayInstanceMemberLocalVar5(); + benchmark.ArrayInstanceMemberLocalVar6(); + benchmark.IntInstanceMember2(); + benchmark.IntInstanceMember3(); + benchmark.IntInstanceMember4(); + benchmark.IntInstanceMember5(); + benchmark.RefToArray2(); + benchmark.RefToArray3(); + benchmark.RefToArray4(); + benchmark.RefToArray5(); + benchmark.RefToArray6(); + benchmark.RefToInt2(); + benchmark.RefToInt3(); + } + + private readonly TestCase _testCase = new(); + private static void DoNothing(int count) + { + _ = count; + } + + private class TestCase + { + private int[] _items = new int[123]; + private int _count = 123; + private static int Add(int count) + { + return count; + } + + public int Count + { + get => _count; + set => _count = value; + } + + public ref int GetCountAsRefCount2() + { + ref int count = ref _count; + count = 234; + return ref count; + } + + public ref int GetCountAsRefCount3() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + return ref count; + } + + public ref int GetCountAsRefCount4() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + _ = TestCase.Add(count); + return ref count; + } + + public ref int GetCountAsRefCount5() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + _ = TestCase.Add(count); + _ = TestCase.Add(count); + return ref count; + } + + public int GetCountAsInstanceMember2() + { + _count = 234; + return _count; + } + + public int GetCountAsInstanceMember3() + { + _count = 234; + _ = TestCase.Add(_count); + return _count; + } + + public int GetCountAsInstanceMember4() + { + _count = 234; + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + return _count; + } + + public int GetCountAsInstanceMember5() + { + _count = 234; + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + return _count; + } + + public int GetLengthAsArray2() + { + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray3() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray4() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray5() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray6() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArrayLocalVar5() + { + var items = _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsArrayLocalVar6() + { + var items = _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray2() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray3() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray4() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray5() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray6() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + } + + [Benchmark] + public void RefToInt2() + { + var count = _testCase.GetCountAsRefCount2(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt3() + { + var count = _testCase.GetCountAsRefCount3(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt4() + { + var count = _testCase.GetCountAsRefCount4(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt5() + { + var count = _testCase.GetCountAsRefCount5(); + DoNothing(count); + } + + [Benchmark(Baseline = true)] + public void IntInstanceMember2() + { + var count = _testCase.GetCountAsInstanceMember2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember3() + { + var count = _testCase.GetCountAsInstanceMember3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember4() + { + var count = _testCase.GetCountAsInstanceMember4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember5() + { + var count = _testCase.GetCountAsInstanceMember5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray2() + { + var count = _testCase.GetLengthAsRefArray2(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray3() + { + var count = _testCase.GetLengthAsRefArray3(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray4() + { + var count = _testCase.GetLengthAsRefArray4(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray5() + { + var count = _testCase.GetLengthAsRefArray5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray6() + { + var count = _testCase.GetLengthAsRefArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember2() + { + var count = _testCase.GetLengthAsArray2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember3() + { + var count = _testCase.GetLengthAsArray3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember4() + { + var count = _testCase.GetLengthAsArray4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember5() + { + var count = _testCase.GetLengthAsArray5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember6() + { + var count = _testCase.GetLengthAsArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar5() + { + var count = _testCase.GetLengthAsArrayLocalVar5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar6() + { + var count = _testCase.GetLengthAsArrayLocalVar6(); + DoNothing(count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 8ff8914..d7a156f 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -1,100 +1,54 @@ using BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; using Recyclable.Collections.Benchmarks; - -//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); - -//BenchmarkRunner.Run(); - - +using Recyclable.Collections.Benchmarks.POC; internal class Program { - static void RunModuloBenchmarks() + static IConfig BenchmarkConfig { get; } = ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary); + + static void RunAssemblyBenchmarks() { - var benchmark = new ModuloBenchmarks(); - //benchmark.AddWithFor(); - //benchmark.DividerAndModulusOperator(); - //benchmark.MathDivRem(); - //benchmark.SubtracWithWhile(); - //benchmark.XDivYMulX(); - //benchmark.XDivYMulXWithLocalVar(); - //benchmark.AddWithForOptimized(); - //benchmark.MixedApproachWithForLoopOptimized(); - //benchmark.MixedApproachWithSubtractWithWhile(); - benchmark.MixedApproachWithSubtractWithWhileAsVector(); + _ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); } - static void RunBenchmark() + private static void RunRecyclableCollectionsBenchmarks() { - var benchmark = new RecyclableCollectionsBenchmarks(); - benchmark.Setup(); + _ = BenchmarkRunner.Run(BenchmarkConfig); + } + + static void RunSelectedBenchmarks() + { + // **************** + // *** Template *** + // **************** + //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.RecyclableList_Contains_LastItems(); - //benchmark.RecyclableList_IndexOf_FirstItems(); - //benchmark.RecyclableList_AddRangeWhenSourceIsList(); //benchmark.RecyclableList_Remove_FirstItems(); //benchmark.RecyclableList_RemoveAt_LastItems(); - //benchmark.RecyclableList_EnsureCapacity_New_ByBlockSize(); - benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); //benchmark.List_RemoveAt_LastItems(); //} - benchmark.Cleanup(); + //benchmark.Cleanup(); + } + + static void RunPocBenchmarks() + { + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); } private static void Main(string[] args) { - BenchmarkRunner.Run( - ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary) - ); - - //var pool = ArrayPool.Shared; - //for (long bufferSize = int.MaxValue / 2; bufferSize <= int.MaxValue; bufferSize += 100_000_000) - //{ - // try - // { - // Console.WriteLine($"Allocating {bufferSize}"); - // var arr = pool.Rent((int)bufferSize); - // pool.Return(arr); - // GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - // } - // catch (Exception) - // { - // Console.WriteLine($"Error during array creation. {nameof(bufferSize)} = {bufferSize}"); - // throw; - // } - //} - - //RunBenchmark(); - - - //BenchmarkRunner.Run( - // ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary) - //); - - //RunModuloBenchmarks(); + RunRecyclableCollectionsBenchmarks(); + //RunPocBenchmarks(); + //RunSelectedBenchmarks(); + //RunAssemblyBenchmarks(); } } -// benchmark.Setup(); -// benchmark.Cleanup(); - -//benchmark.Setup(); -//benchmark.Cleanup(); - -//} - - -//BenchmarkRunner.Run(); - -//var tests = new ListVsLinkedListBenchmark(); -//tests.RecyclableList(); diff --git a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs deleted file mode 100644 index cd21368..0000000 --- a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs +++ /dev/null @@ -1,339 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks -{ - public class RefVsInstanceMemberBenchmarks - { - private readonly TestCase _testCase = new(); - private void DoNothing(int count) - { - _ = count; - } - - private class TestCase - { - private int[] _items = new int[123]; - private int _count = 123; - private int Add(int count) - { - return count; - } - - public int Count - { - get => _count; - set => _count = value; - } - - public ref int GetCountAsRefCount2() - { - ref int count = ref _count; - count = 234; - return ref count; - } - - public ref int GetCountAsRefCount3() - { - ref int count = ref _count; - count = 234; - _ = Add(count); - return ref count; - } - - public ref int GetCountAsRefCount4() - { - ref int count = ref _count; - count = 234; - _ = Add(count); - _ = Add(count); - return ref count; - } - - public ref int GetCountAsRefCount5() - { - ref int count = ref _count; - count = 234; - _ = Add(count); - _ = Add(count); - _ = Add(count); - return ref count; - } - - public int GetCountAsInstanceMember2() - { - _count = 234; - return _count; - } - - public int GetCountAsInstanceMember3() - { - _count = 234; - _ = Add(_count); - return _count; - } - - public int GetCountAsInstanceMember4() - { - _count = 234; - _ = Add(_count); - _ = Add(_count); - return _count; - } - - public int GetCountAsInstanceMember5() - { - _count = 234; - _ = Add(_count); - _ = Add(_count); - _ = Add(_count); - return _count; - } - - public int GetLengthAsArray2() - { - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArray3() - { - _ = Add(_items.Length); - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArray4() - { - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArray5() - { - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArray6() - { - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArrayLocalVar5() - { - var items = _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsArrayLocalVar6() - { - var items = _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray2() - { - ref int[] items = ref _items; - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray3() - { - ref int[] items = ref _items; - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray4() - { - ref int[] items = ref _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray5() - { - ref int[] items = ref _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray6() - { - ref int[] items = ref _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - } - - [Benchmark] - public void RefToInt2() - { - var count = _testCase.GetCountAsRefCount2(); - DoNothing(count); - } - - [Benchmark] - public void RefToInt3() - { - var count = _testCase.GetCountAsRefCount3(); - DoNothing(count); - } - - [Benchmark] - public void RefToInt4() - { - var count = _testCase.GetCountAsRefCount4(); - DoNothing(count); - } - - [Benchmark] - public void RefToInt5() - { - var count = _testCase.GetCountAsRefCount5(); - DoNothing(count); - } - - [Benchmark(Baseline = true)] - public void IntInstanceMember2() - { - var count = _testCase.GetCountAsInstanceMember2(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void IntInstanceMember3() - { - var count = _testCase.GetCountAsInstanceMember3(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void IntInstanceMember4() - { - var count = _testCase.GetCountAsInstanceMember4(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void IntInstanceMember5() - { - var count = _testCase.GetCountAsInstanceMember5(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray2() - { - var count = _testCase.GetLengthAsRefArray2(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray3() - { - var count = _testCase.GetLengthAsRefArray3(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray4() - { - var count = _testCase.GetLengthAsRefArray4(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray5() - { - var count = _testCase.GetLengthAsRefArray5(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray6() - { - var count = _testCase.GetLengthAsRefArray6(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember2() - { - var count = _testCase.GetLengthAsArray2(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember3() - { - var count = _testCase.GetLengthAsArray3(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember4() - { - var count = _testCase.GetLengthAsArray4(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember5() - { - var count = _testCase.GetLengthAsArray5(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember6() - { - var count = _testCase.GetLengthAsArray6(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMemberLocalVar5() - { - var count = _testCase.GetLengthAsArrayLocalVar5(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMemberLocalVar6() - { - var count = _testCase.GetLengthAsArrayLocalVar6(); - DoNothing(count); - } - } -} From abbd568c6b19b3db3765220d90c409c5168d07e0 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 20:24:26 +0200 Subject: [PATCH 206/305] RecyclableList.LastTakenBlockIndex renamed to LastBlockWithData. Tiny cleanup. --- .../RecyclableCollectionsBenchmarks.RecyclableList.cs | 8 ++++---- Recyclable.Collections/RecyclableList.cs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 801fad9..71c4242 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -110,7 +110,7 @@ namespace Recyclable.Collections.Benchmarks int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; - var memoryBlocks = new Span(data.MemoryBlocks, 0, data.LastTakenBlockIndex + 1); + var memoryBlocks = new Span(data.MemoryBlocks, 0, data.LastBlockWithData + 1); for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) { var memoryBlock = new Span(memoryBlocks[blockIndex], 0, blockSize); @@ -120,7 +120,7 @@ namespace Recyclable.Collections.Benchmarks } } - if (nextItemBlockIndex == data.LastTakenBlockIndex) + if (nextItemBlockIndex == data.LastBlockWithData) { var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); @@ -149,7 +149,7 @@ namespace Recyclable.Collections.Benchmarks } } - if (nextItemBlockIndex == data.LastTakenBlockIndex) + if (nextItemBlockIndex == data.LastBlockWithData) { var memoryBlock = memoryBlocks[nextItemBlockIndex]; blockSize = data.NextItemIndex; @@ -203,7 +203,7 @@ namespace Recyclable.Collections.Benchmarks } } - if (nextItemBlockIndex == data.LastTakenBlockIndex) + if (nextItemBlockIndex == data.LastBlockWithData) { memoryBlock = memoryBlocks[nextItemBlockIndex]; blockSize = data.NextItemIndex; diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 20723c2..092f2e3 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -34,7 +34,7 @@ namespace Recyclable.Collections public int Count => checked((int)_longCount); public bool IsReadOnly { get; } - public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); protected long _longCount; @@ -61,7 +61,7 @@ namespace Recyclable.Collections int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); int targetItemIndex = checked((int)(index & (blockSize - 1))); - int lastTakenBlockIndex = list.LastTakenBlockIndex; + int lastTakenBlockIndex = list.LastBlockWithData; while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) { int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 @@ -334,7 +334,7 @@ namespace Recyclable.Collections : requestedCapacity; int blockSize = list._blockSize; - newCapacity = RecyclableList.Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + newCapacity = Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); if (newCapacity > 0 && blockSize != list._memoryBlocks[0].Length) { blockSize = list._memoryBlocks[0].Length; @@ -814,7 +814,7 @@ namespace Recyclable.Collections ? ItemNotFoundIndex : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) - : checked((int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex)); + : checked((int)DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex)); public void Insert(int index, T item) => throw new NotSupportedException(); @@ -823,7 +823,7 @@ namespace Recyclable.Collections ? ItemNotFoundIndex : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) - : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + : DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool Remove(T item) From 7c7740b27c82611e9b86012767bfd601949974f5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 20:30:34 +0200 Subject: [PATCH 207/305] RecyclableList performance improvements to pre-calculate the value of `_blockSize - 1` & store it under `_blockSizeMinus1` --- Recyclable.Collections/RecyclableList.cs | 36 +++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 092f2e3..1af48b6 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -16,6 +16,7 @@ namespace Recyclable.Collections private int _blockSize; private byte _blockSizePow2Shift; + private int _blockSizeMinus1; private int _nextItemBlockIndex; private int _nextItemIndex; @@ -44,7 +45,7 @@ namespace Recyclable.Collections set => _longCount = value; } - public int ReservedBlockCount => checked((int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0)); + public int ReservedBlockCount => checked((int)(_capacity >> _blockSizePow2Shift) + ((_capacity & _blockSizeMinus1) > 0 ? 1 : 0)); public int BlockSize => _blockSize; public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -56,10 +57,10 @@ namespace Recyclable.Collections int nextItemIndex = list._nextItemIndex; int blockSize = list._blockSize; int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2Shift)); - int sourceItemIndex = checked((int)((index + 1) & (blockSize - 1))); + int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); - int targetItemIndex = checked((int)(index & (blockSize - 1))); + int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); int lastTakenBlockIndex = list.LastBlockWithData; while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) @@ -345,6 +346,7 @@ namespace Recyclable.Collections list._blockSizePow2Shift = (byte)MathUtils.GetPow2Shift(blockSize); list._blockSize = blockSize; + list._blockSizeMinus1 = blockSize - 1; } list._capacity = newCapacity; @@ -368,6 +370,7 @@ namespace Recyclable.Collections if (_memoryBlocks!.Length > 0 && _blockSize != _memoryBlocks[0].Length) { _blockSize = _memoryBlocks[0].Length; + _blockSizeMinus1 = _blockSize - 1; _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { @@ -377,11 +380,13 @@ namespace Recyclable.Collections else { _blockSize = minBlockSize; + _blockSizeMinus1 = minBlockSize - 1; } } else { _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockSizeMinus1 = _blockSize - 1; _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) @@ -405,9 +410,10 @@ namespace Recyclable.Collections _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); - if (_memoryBlocks?.Length > 0 && minBlockSize != _memoryBlocks[0].Length) + if (_memoryBlocks!.Length > 0 && minBlockSize != _memoryBlocks[0].Length) { _blockSize = _memoryBlocks[0].Length; + _blockSizeMinus1 = _blockSize - 1; _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { @@ -417,6 +423,7 @@ namespace Recyclable.Collections else { _blockSize = minBlockSize; + _blockSizeMinus1 = minBlockSize - 1; } } else @@ -426,6 +433,7 @@ namespace Recyclable.Collections ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) : RecyclableArrayPool.Null; + _blockSizeMinus1 = _blockSize - 1; _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -435,14 +443,14 @@ namespace Recyclable.Collections public T this[long index] { - get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; - set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & (_blockSize - 1)))] = value; + get => _memoryBlocks[index >> _blockSizePow2Shift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & _blockSizeMinus1))] = value; } public T this[int index] { - get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; - set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + get => _memoryBlocks[index >> _blockSizePow2Shift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & _blockSizeMinus1] = value; } public T[][] MemoryBlocks { get => _memoryBlocks; } @@ -604,14 +612,14 @@ namespace Recyclable.Collections } } - if ((targetCapacity & (blockSize - 1)) == 0) + if ((targetCapacity & _blockSizeMinus1) == 0) { _nextItemIndex = 0; _nextItemBlockIndex = targetBlockIndex + 1; } else { - _nextItemIndex = checked((int)(targetCapacity & (blockSize - 1))); + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); _nextItemBlockIndex = targetBlockIndex; } @@ -719,7 +727,7 @@ namespace Recyclable.Collections } } - public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) { if (source is RecyclableList sourceRecyclableList) { @@ -843,7 +851,7 @@ namespace Recyclable.Collections } else { - _nextItemIndex = _blockSize - 1; + _nextItemIndex = _blockSizeMinus1; _nextItemBlockIndex--; } @@ -902,7 +910,7 @@ namespace Recyclable.Collections } else { - _nextItemIndex = _blockSize - 1; + _nextItemIndex = _blockSizeMinus1; _nextItemBlockIndex--; } @@ -934,7 +942,7 @@ namespace Recyclable.Collections } else { - _nextItemIndex = _blockSize - 1; + _nextItemIndex = _blockSizeMinus1; _nextItemBlockIndex--; } From 595566d0b999a20ad8689ac7f71c401faa36d595 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 20:31:34 +0200 Subject: [PATCH 208/305] The current maximum pooled block size, from testing, added. --- Recyclable.Collections/RecyclableDefaults.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 9012a0e..16c4468 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -5,5 +5,6 @@ public const int BlockSize = 16_384; public const int Capacity = 1; public const int MinPooledArrayLength = 128; + public const int MaxPooledBlockSize = 2_147_483_591; } } From 098d862a3cf1bc2249fbdb2ea48e5e2b023b27f3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 20:33:59 +0200 Subject: [PATCH 209/305] x64 platform added. --- .../Recyclable.Collections.Benchmarks.csproj | 1 + Recyclable.Collections.sln | 16 +++++++++++++++- .../Recyclable.Collections.csproj | 1 + .../Recyclable.CollectionsTests.csproj | 2 ++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj b/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj index ee77c13..912d3fb 100644 --- a/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj +++ b/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj @@ -6,6 +6,7 @@ net6.0;net7.0 enable enable + AnyCPU;x64 diff --git a/Recyclable.Collections.sln b/Recyclable.Collections.sln index d746276..1af4cb0 100644 --- a/Recyclable.Collections.sln +++ b/Recyclable.Collections.sln @@ -7,26 +7,40 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.Collections", "R EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.CollectionsTests", "Recyclable.CollectionsTests\Recyclable.CollectionsTests.csproj", "{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Recyclable.Collections.Benchmarks", "Recyclable.Collections.Benchmarks\Recyclable.Collections.Benchmarks.csproj", "{CDC0CF78-FC11-43E3-9059-3261DA3639D1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.Collections.Benchmarks", "Recyclable.Collections.Benchmarks\Recyclable.Collections.Benchmarks.csproj", "{CDC0CF78-FC11-43E3-9059-3261DA3639D1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|x64.ActiveCfg = Debug|x64 + {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|x64.Build.0 = Debug|x64 {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|Any CPU.ActiveCfg = Release|Any CPU {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|Any CPU.Build.0 = Release|Any CPU + {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|x64.ActiveCfg = Release|x64 + {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|x64.Build.0 = Release|x64 {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|x64.ActiveCfg = Debug|x64 + {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|x64.Build.0 = Debug|x64 {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|Any CPU.Build.0 = Release|Any CPU + {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|x64.ActiveCfg = Release|x64 + {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|x64.Build.0 = Release|x64 {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|x64.ActiveCfg = Debug|x64 + {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|x64.Build.0 = Debug|x64 {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|Any CPU.ActiveCfg = Release|Any CPU {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|Any CPU.Build.0 = Release|Any CPU + {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|x64.ActiveCfg = Release|x64 + {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Recyclable.Collections/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj index e9f7027..50d3f17 100644 --- a/Recyclable.Collections/Recyclable.Collections.csproj +++ b/Recyclable.Collections/Recyclable.Collections.csproj @@ -20,6 +20,7 @@ README.md git RecyclableSortedList added. Minor improvements & cleanups. + AnyCPU;x64 diff --git a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj index 4a56fe7..4514a44 100644 --- a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj +++ b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj @@ -6,6 +6,8 @@ enable false + + AnyCPU;x64 From 9cb9aa8bf06006dd8895dcf7a5abc4626944a8e3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 20:38:25 +0200 Subject: [PATCH 210/305] ArraySizeLimitPocBenchmarks reworked. --- .../POC/ArraySizeLimitPoc.Benchmarks.cs | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs index 42c5783..69343bc 100644 --- a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs @@ -11,45 +11,52 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.FindArraySizeLimit(); } - [Benchmark] + private const int _maxArraySize = 2_147_483_605; + + + [Benchmark(OperationsPerInvoke = 1)] + public void Max_Allowed_Size_2_147_483_605() + { + var arr = ArrayPool.Shared.Rent(_maxArraySize); + } + + [Benchmark(OperationsPerInvoke = 1)] public void FindArraySizeLimit() { var pool = ArrayPool.Shared; - var bufferSize = int.MaxValue; + var bufferSize = int.MaxValue / 2; var oldValue = 0; while (true) { try { - Console.WriteLine($"Allocating {bufferSize}"); + Console.Write($"Allocating {bufferSize}..."); var arr = pool.Rent((int)bufferSize); - var nextValue = (oldValue - bufferSize) >> 1; - if (nextValue == 0) - { - break; - } - - oldValue = bufferSize; - bufferSize += nextValue; - + Console.WriteLine("success"); pool.Return(arr); + arr = null; + + var nextValue = bufferSize - oldValue; + oldValue = bufferSize; + bufferSize = checked(bufferSize + nextValue); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); } catch (Exception) { - Console.WriteLine($"Error during array creation when {nameof(bufferSize)} = {bufferSize}"); - var nextValue = (oldValue - bufferSize) >> 1; + Console.WriteLine($"error"); + var nextValue = (bufferSize - oldValue) >> 1; if (nextValue == 0) { break; } - oldValue = bufferSize; - bufferSize -= nextValue; + bufferSize = oldValue + nextValue; } } Console.WriteLine("All done"); + throw new Exception($"Everything was successful. You can create {oldValue} byte big arrays. Exception is logged to prevent more benchmarks."); } } } From 0449700244d89399a935a41543a57f57294b75c2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 11 Apr 2023 21:24:28 +0200 Subject: [PATCH 211/305] RecyclableList.EnsureCapacity performance improvement. --- 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 1af48b6..2a036fd 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -344,7 +344,7 @@ namespace Recyclable.Collections list._blockArrayPool = RecyclableArrayPool.Shared(blockSize); } - list._blockSizePow2Shift = (byte)MathUtils.GetPow2Shift(blockSize); + list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); list._blockSize = blockSize; list._blockSizeMinus1 = blockSize - 1; } From 3cb0a47c601303d7cb636fc3decef874803a560e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 12 Apr 2023 23:36:11 +0200 Subject: [PATCH 212/305] RecyclableCollectionsBenchmarks split into multiple files. --- .../Benchmarks/Add.cs | 56 +++ .../Benchmarks/AddRangeWhenSourceIsArray.cs | 40 ++ .../AddRangeWhenSourceIsIEnumerable.cs | 40 ++ .../Benchmarks/AddRangeWhenSourceIsIList.cs | 40 ++ .../Benchmarks/AddRangeWhenSourceIsList.cs | 40 ++ .../AddRangeWhenSourceIsSameType.cs | 32 ++ .../Benchmarks/AddRange_WithCapacity.cs | 44 +++ .../Benchmarks/Add_WithCapacity.cs | 56 +++ .../Benchmarks/Contains_FirstItems.cs | 67 ++++ .../Benchmarks/Contains_LastItems.cs | 68 ++++ .../Benchmarks/Count.cs | 43 +++ .../Benchmarks/Create.cs | 36 ++ .../Benchmarks/Create_WithCapacity.cs | 43 +++ .../Benchmarks/GetItem..cs | 174 +++++++++ .../Benchmarks/IndexOf_FirstItems.cs | 80 ++++ .../Benchmarks/IndexOf_LastItems.cs | 80 ++++ .../Benchmarks/LongCount.cs | 22 ++ .../RecyclableCollections.Benchmarks.cs} | 3 +- .../Benchmarks/RemoveAt_FirstItems.cs | 56 +++ .../Benchmarks/RemoveAt_LastItems.cs | 56 +++ .../Benchmarks/Remove_FirstItems.cs | 57 +++ .../Benchmarks/Remove_LastItems.cs | 57 +++ .../Benchmarks/SetItem.cs | 64 ++++ .../RecyclableCollectionsBenchmarks.Array.cs | 65 ---- .../RecyclableCollectionsBenchmarks.List.cs | 211 ---------- ...yclableCollectionsBenchmarks.PooledList.cs | 220 ----------- ...llectionsBenchmarks.RecyclableArrayList.cs | 183 --------- ...bleCollectionsBenchmarks.RecyclableList.cs | 362 ------------------ 28 files changed, 1253 insertions(+), 1042 deletions(-) create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Add.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Count.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Create.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs rename Recyclable.Collections.Benchmarks/{RecyclableCollectionsBenchmarks.cs => Benchmarks/RecyclableCollections.Benchmarks.cs} (95%) create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs delete mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs delete mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs delete mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs delete mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs delete mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs new file mode 100644 index 0000000..ed6b624 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs @@ -0,0 +1,56 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(ClearMode.Always); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs new file mode 100644 index 0000000..895ea47 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs @@ -0,0 +1,40 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + var list = new List(); + list.AddRange(data); + } + + //[Benchmark(Baseline = true)] + public void PooledList_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs new file mode 100644 index 0000000..8b1fc99 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs @@ -0,0 +1,40 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + var list = new List(); + list.AddRange(data); + } + + //[Benchmark(Baseline = true)] + public void PooledList_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs new file mode 100644 index 0000000..271f04d --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs @@ -0,0 +1,40 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsRecyclableList; + var list = new List(); + list.AddRange(data); + } + + //[Benchmark(Baseline = true)] + public void PooledList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsPooledList; + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsRecyclableList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsRecyclableList; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs new file mode 100644 index 0000000..12b34cf --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs @@ -0,0 +1,40 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + var list = new List(); + list.AddRange(data); + } + + //[Benchmark(Baseline = true)] + public void PooledList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs new file mode 100644 index 0000000..6fd6e49 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs @@ -0,0 +1,32 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = true)] + public void PooledList_AddRangeWhenSourceIsPooledList() + { + var data = TestObjectsAsPooledList; + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() + { + var data = TestObjectsAsRecyclableArrayList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_AddRangeWhenSourceIsRecyclableArrayList() + { + var data = TestObjectsAsRecyclableArrayList; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs new file mode 100644 index 0000000..0d6bcc8 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs @@ -0,0 +1,44 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(checked((int)dataCount)); + list.AddRange(data); + } + + //[Benchmark(Baseline = true)] + public void PooledList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(checked((int)dataCount)); + list.AddRange(data); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs new file mode 100644 index 0000000..8ecfdaf --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs @@ -0,0 +1,56 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(checked((int)dataCount)); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(capacity: checked((int)dataCount), ClearMode.Always); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(checked((int)dataCount)); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs new file mode 100644 index 0000000..98e7362 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs @@ -0,0 +1,67 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void Array_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[i]) >= 0); + } + } + + //[Benchmark(Baseline = false)] + public void List_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs new file mode 100644 index 0000000..dc9da22 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs @@ -0,0 +1,68 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void Array_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = false)] + public void List_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs new file mode 100644 index 0000000..d5286c5 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs @@ -0,0 +1,43 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void Array_Count() + { + var data = _testArray; + DoNothing(data.Length); + } + + //[Benchmark(Baseline = false)] + public void List_Count() + { + var data = TestObjectsAsList; + DoNothing(data.Count); + } + + //[Benchmark(Baseline = true)] + public void PooledList_Count() + { + var data = _testPooledList; + DoNothing(data.Count); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Count() + { + var data = TestObjectsAsRecyclableArrayList; + DoNothing(data.Count); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Count() + { + var data = _testRecyclableList; + DoNothing(data.Count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs new file mode 100644 index 0000000..d0f5a46 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs @@ -0,0 +1,36 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_Create() + { + var list = new List(); + DoNothing(list); + } + + //[Benchmark(Baseline = true)] + public void PooledList_Create() + { + using var list = new PooledList(ClearMode.Always); + DoNothing(list); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Create() + { + using var list = new RecyclableArrayList(); + DoNothing(list); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Create() + { + using var list = new RecyclableList(); + DoNothing(list); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs new file mode 100644 index 0000000..1317ae0 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs @@ -0,0 +1,43 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void Array_Create_WithCapacity() + { + var data = new object[TestObjectCount]; + DoNothing(data); + } + + //[Benchmark(Baseline = false)] + public void List_Create_WithCapacity() + { + var list = new List(checked((int)TestObjectCount)); + DoNothing(list); + } + + //[Benchmark(Baseline = true)] + public void PooledList_Create_WithCapacity() + { + using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + DoNothing(list); + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Create_WithCapacity() + { + using var list = new RecyclableArrayList(checked((int)TestObjectCount)); + DoNothing(list); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Create_WithCapacity() + { + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + DoNothing(list); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs new file mode 100644 index 0000000..8e9cae5 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -0,0 +1,174 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + [Benchmark(Baseline = false)] + public void Array_GetItem() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark(Baseline = false)] + public void List_GetItem() + { + var data = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark(Baseline = true)] + public void PooledList_GetItem() + { + var data = _testPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableArrayList_GetItem() + { + var data = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableList_GetItem() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableList_GetItem_AsSpan() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + + var memoryBlocks = new Span(data.MemoryBlocks, 0, data.LastBlockWithData + 1); + for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) + { + var memoryBlock = new Span(memoryBlocks[blockIndex], 0, blockSize); + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + + if (nextItemBlockIndex == data.LastBlockWithData) + { + + var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); + for (var itemIndex = 0; itemIndex < memoryBlock.Length; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + } + + [Benchmark(Baseline = false)] + public void RecyclableList_GetItem_AsArray() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + + var memoryBlocks = data.MemoryBlocks; + for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) + { + var memoryBlock = memoryBlocks[blockIndex]; + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + + if (nextItemBlockIndex == data.LastBlockWithData) + { + var memoryBlock = memoryBlocks[nextItemBlockIndex]; + blockSize = data.NextItemIndex; + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + } + + [Benchmark(Baseline = false)] + public void RecyclableList_GetItem_AsForEach() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + + foreach (var item in data) + { + DoNothing(item); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableList_GetItem_AsWhile() + { + var data = _testRecyclableList; + long dataCount = TestObjectCount; + if (dataCount == 0) + { + return; + } + + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + int itemIndex = 0; + int blockIndex = 0; + var memoryBlocks = data.MemoryBlocks; + var memoryBlock = memoryBlocks[0]; + while (blockIndex < nextItemBlockIndex) + { + DoNothing(memoryBlock[itemIndex]); + if (itemIndex + 1 < blockSize) + { + itemIndex++; + } + else + { + blockIndex++; + memoryBlock = memoryBlocks[blockIndex]; + itemIndex = 0; + } + } + + if (nextItemBlockIndex == data.LastBlockWithData) + { + memoryBlock = memoryBlocks[nextItemBlockIndex]; + blockSize = data.NextItemIndex; + while (itemIndex < blockSize) + { + DoNothing(memoryBlock[itemIndex++]); + } + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs new file mode 100644 index 0000000..412f4d6 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs @@ -0,0 +1,80 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void Array_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void List_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_LongIndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.LongIndexOf(data[i])); + } + } + + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs new file mode 100644 index 0000000..f12a329 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs @@ -0,0 +1,80 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void Array_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = false)] + public void List_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_LongIndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.LongIndexOf(data[^(i + 1)])); + } + } + + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs new file mode 100644 index 0000000..f9a2813 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs @@ -0,0 +1,22 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void Array_LongCount() + { + var data = _testArray; + DoNothing(data.LongLength); + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_LongCount() + { + var data = _testRecyclableList; + DoNothing(data.LongCount); + } + + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs similarity index 95% rename from Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs rename to Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 422fe09..2b2ca80 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -3,7 +3,8 @@ using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { - public partial class RecyclableCollectionsBenchmarks + [MemoryDiagnoser] + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { private object[] _testArray; private RecyclableArrayList _testRecyclableArrayList; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs new file mode 100644 index 0000000..e6fed8e --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs @@ -0,0 +1,56 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_RemoveAt_FirstItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new PooledList(data, ClearMode.Always); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableArrayList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs new file mode 100644 index 0000000..01ca9be --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs @@ -0,0 +1,56 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_RemoveAt_LastItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new PooledList(data, ClearMode.Always); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new RecyclableArrayList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs new file mode 100644 index 0000000..a771bae --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs @@ -0,0 +1,57 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_Remove_FirstItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new PooledList(data, ClearMode.Always); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableArrayList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs new file mode 100644 index 0000000..2070bc5 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs @@ -0,0 +1,57 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void List_Remove_LastItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_Remove_LastItems() + { + var data = TestObjects; + using var list = new PooledList(data, ClearMode.Always); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_Remove_LastItems() + { + var data = TestObjects; + using var list = new RecyclableArrayList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_Remove_LastItems() + { + var data = TestObjects; + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs new file mode 100644 index 0000000..54dc577 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs @@ -0,0 +1,64 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + //[Benchmark(Baseline = false)] + public void Array_SetItem() + { + var data = TestObjects; + var dataCount = TestObjectCount; + long index = 0; + for (var i = 0; i < dataCount; i++) + { + data[index] = TestObjects[index++]; + } + } + + //[Benchmark(Baseline = false)] + public void List_SetItem() + { + var data = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark(Baseline = true)] + public void PooledList_SetItem() + { + var data = _testPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableArrayList_SetItem() + { + var data = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark(Baseline = false)] + public void RecyclableList_SetItem() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs deleted file mode 100644 index 10da984..0000000 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ /dev/null @@ -1,65 +0,0 @@ -using BenchmarkDotNet.Attributes; -using MoreLinq.Extensions; - -namespace Recyclable.Collections.Benchmarks -{ - [MemoryDiagnoser] - public partial class RecyclableCollectionsBenchmarks : BenchmarkBase - { - [Benchmark(Baseline = false)] - public void Array_Create_WithCapacity() - { - var data = new object[TestObjectCount]; - DoNothing(data); - } - - [Benchmark(Baseline = false)] - public void Array_SetItem() - { - var data = TestObjects; - var dataCount = TestObjectCount; - long index = 0; - for(var i = 0; i < dataCount; i++) - { - data[index] = TestObjects[index++]; - } - } - - [Benchmark(Baseline = false)] - public void Array_GetItem() - { - var data = TestObjects; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void Array_Count() - { - var data = _testArray; - DoNothing(data.Length); - } - - [Benchmark(Baseline = false)] - public void Array_LongCount() - { - var data = _testArray; - DoNothing(data.LongLength); - } - - [Benchmark(Baseline = false)] - public void Array_IndexOf_FirstItems() - { - var data = TestObjects; - var list = TestObjects; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(Array.IndexOf(list, data[i])); - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs deleted file mode 100644 index a0bafdc..0000000 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ /dev/null @@ -1,211 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks -{ - public partial class RecyclableCollectionsBenchmarks : BenchmarkBase - { - [Benchmark(Baseline = false)] - public void List_Create() - { - var list = new List(); - DoNothing(list); - } - - [Benchmark(Baseline = false)] - public void List_Create_WithCapacity() - { - var list = new List(checked((int)TestObjectCount)); - DoNothing(list); - } - - [Benchmark(Baseline = false)] - public void List_Add() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new List(); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void List_Add_WithCapacity() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new List(checked((int)dataCount)); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void List_AddRangeWhenSourceIsArray() - { - var data = TestObjects; - var list = new List(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void List_AddRangeWhenSourceIsList() - { - var data = TestObjectsAsList; - var list = new List(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void List_AddRangeWhenSourceIsIList() - { - var data = (IList)TestObjectsAsRecyclableList; - var list = new List(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void List_AddRangeWhenSourceIsIEnumerable() - { - var data = TestObjectsAsIEnumerable; - var list = new List(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void List_AddRange_WithCapacity() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new List(checked((int)dataCount)); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void List_SetItem() - { - var data = TestObjectsAsList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - data[i] = TestObjects[i]; - } - } - - [Benchmark(Baseline = false)] - public void List_GetItem() - { - var data = TestObjectsAsList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void List_Count() - { - var data = TestObjectsAsList; - DoNothing(data.Count); - } - - [Benchmark(Baseline = false)] - public void List_Contains_FirstItems() - { - var data = TestObjects; - var list = TestObjectsAsList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Contains(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void List_Contains_LastItems() - { - var data = TestObjects; - var list = TestObjectsAsList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Contains(data[^(i + 1)])); - } - } - - [Benchmark(Baseline = false)] - public void List_IndexOf_FirstItems() - { - var data = TestObjects; - var list = TestObjectsAsList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.IndexOf(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void List_IndexOf_LastItems() - { - var data = TestObjects; - var list = TestObjectsAsList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.IndexOf(data[^(i + 1)])); - } - } - - [Benchmark(Baseline = false)] - public void List_RemoveAt_FirstItems() - { - var data = TestObjects; - var list = new List(data); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - list.RemoveAt(i); - } - } - - [Benchmark(Baseline = false)] - public void List_RemoveAt_LastItems() - { - var data = TestObjects; - var list = new List(data); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) - { - list.RemoveAt(i); - } - } - - [Benchmark(Baseline = false)] - public void List_Remove_FirstItems() - { - var data = TestObjects; - var list = new List(data); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Remove(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void List_Remove_LastItems() - { - var data = TestObjects; - var list = new List(data); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) - { - DoNothing(list.Remove(data[i])); - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs deleted file mode 100644 index f20abf9..0000000 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.PooledList.cs +++ /dev/null @@ -1,220 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; - -namespace Recyclable.Collections.Benchmarks -{ - public partial class RecyclableCollectionsBenchmarks : BenchmarkBase - { - [Benchmark(Baseline = false)] - public void PooledList_Create() - { - using var list = new PooledList(ClearMode.Always); - DoNothing(list); - } - - [Benchmark(Baseline = false)] - public void PooledList_Create_WithCapacity() - { - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); - DoNothing(list); - } - - [Benchmark(Baseline = false)] - public void PooledList_Add() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new PooledList(ClearMode.Always); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_Add_WithCapacity() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new PooledList(capacity: checked((int)dataCount), ClearMode.Always); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_AddRangeWhenSourceIsArray() - { - var data = TestObjects; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void PooledList_AddRangeWhenSourceIsList() - { - var data = TestObjectsAsList; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void PooledList_AddRangeWhenSourceIsIList() - { - var data = (IList)TestObjectsAsPooledList; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void PooledList_AddRangeWhenSourceIsIEnumerable() - { - var data = TestObjectsAsIEnumerable; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void PooledList_AddRangeWhenSourceIsPooledList() - { - var data = TestObjectsAsPooledList; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void PooledList_AddRange_WithCapacity() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void PooledList_GetItem() - { - var data = _testPooledList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_SetItem() - { - var data = _testPooledList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - data[i] = TestObjects[i]; - } - } - - [Benchmark(Baseline = false)] - public void PooledList_Count() - { - var data = _testPooledList; - DoNothing(data.Count); - } - - [Benchmark(Baseline = false)] - public void PooledList_Contains_FirstItems() - { - var data = TestObjects; - var list = TestObjectsAsPooledList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Contains(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_Contains_LastItems() - { - var data = TestObjects; - var list = TestObjectsAsPooledList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Contains(data[^(i + 1)])); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_IndexOf_FirstItems() - { - var data = TestObjects; - var list = TestObjectsAsPooledList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.IndexOf(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_IndexOf_LastItems() - { - var data = TestObjects; - var list = TestObjectsAsPooledList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.IndexOf(data[^(i + 1)])); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_Remove_FirstItems() - { - var data = TestObjects; - using var list = new PooledList(data, ClearMode.Always); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Remove(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_Remove_LastItems() - { - var data = TestObjects; - using var list = new PooledList(data, ClearMode.Always); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) - { - DoNothing(list.Remove(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_RemoveAt_FirstItems() - { - var data = TestObjects; - using var list = new PooledList(data, ClearMode.Always); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - list.RemoveAt(i); - } - } - - [Benchmark(Baseline = false)] - public void PooledList_RemoveAt_LastItems() - { - var data = TestObjects; - using var list = new PooledList(data, ClearMode.Always); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) - { - list.RemoveAt(i); - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs deleted file mode 100644 index d561175..0000000 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ /dev/null @@ -1,183 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks -{ - public partial class RecyclableCollectionsBenchmarks : BenchmarkBase - { - [Benchmark(Baseline = false)] - public void RecyclableArrayList_Create() - { - using var list = new RecyclableArrayList(); - DoNothing(list); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_Create_WithCapacity() - { - using var list = new RecyclableArrayList(checked((int)TestObjectCount)); - DoNothing(list); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_Add() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_Add_WithCapacity() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(checked((int)dataCount)); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_AddRangeWhenSourceIsArray() - { - var data = TestObjects; - using var list = new RecyclableArrayList(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_AddRangeWhenSourceIsList() - { - var data = TestObjectsAsList; - using var list = new RecyclableArrayList(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_AddRangeWhenSourceIsIList() - { - var data = (IList)TestObjectsAsRecyclableList; - using var list = new RecyclableArrayList(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() - { - var data = TestObjectsAsIEnumerable; - using var list = new RecyclableArrayList(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() - { - var data = TestObjectsAsRecyclableArrayList; - using var list = new RecyclableArrayList(); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_AddRange_WithCapacity() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(checked((int)dataCount)); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_SetItem() - { - var data = TestObjectsAsRecyclableArrayList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - data[i] = TestObjects[i]; - } - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_GetItem() - { - var data = TestObjectsAsRecyclableArrayList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_Count() - { - var data = TestObjectsAsRecyclableArrayList; - DoNothing(data.Count); - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_IndexOf_FirstItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableArrayList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.IndexOf(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_Remove_FirstItems() - { - var data = TestObjects; - using var list = new RecyclableArrayList(data); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Remove(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_Remove_LastItems() - { - var data = TestObjects; - using var list = new RecyclableArrayList(data); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) - { - DoNothing(list.Remove(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_RemoveAt_FirstItems() - { - var data = TestObjects; - using var list = new RecyclableArrayList(data); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - list.RemoveAt(i); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableArrayList_RemoveAt_LastItems() - { - var data = TestObjects; - using var list = new RecyclableArrayList(data); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) - { - list.RemoveAt(i); - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs deleted file mode 100644 index 71c4242..0000000 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ /dev/null @@ -1,362 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks -{ - public partial class RecyclableCollectionsBenchmarks : BenchmarkBase - { - [Benchmark(Baseline = false)] - public void RecyclableList_Create() - { - using var list = new RecyclableList(); - DoNothing(list); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_Create_WithCapacity() - { - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - DoNothing(list); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_Add() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableList(); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_Add_WithCapacity() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_AddRangeWhenSourceIsArray() - { - var data = TestObjects; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_AddRangeWhenSourceIsList() - { - var data = TestObjectsAsList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_AddRangeWhenSourceIsIList() - { - var data = (IList)TestObjectsAsRecyclableList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_AddRangeWhenSourceIsIEnumerable() - { - var data = TestObjectsAsIEnumerable; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_AddRangeWhenSourceIsRecyclableArrayList() - { - var data = TestObjectsAsRecyclableArrayList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_AddRange_WithCapacity() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - list.AddRange(data); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_GetItem() - { - var data = _testRecyclableList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_GetItem_AsSpan() - { - var data = _testRecyclableList; - var dataCount = TestObjectCount; - int blockSize = data.BlockSize; - int nextItemBlockIndex = data.NextItemBlockIndex; - - var memoryBlocks = new Span(data.MemoryBlocks, 0, data.LastBlockWithData + 1); - for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) - { - var memoryBlock = new Span(memoryBlocks[blockIndex], 0, blockSize); - for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) - { - DoNothing(memoryBlock[itemIndex]); - } - } - - if (nextItemBlockIndex == data.LastBlockWithData) - { - - var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); - for (var itemIndex = 0; itemIndex < memoryBlock.Length; itemIndex++) - { - DoNothing(memoryBlock[itemIndex]); - } - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_GetItem_AsArray() - { - var data = _testRecyclableList; - var dataCount = TestObjectCount; - int blockSize = data.BlockSize; - int nextItemBlockIndex = data.NextItemBlockIndex; - - var memoryBlocks = data.MemoryBlocks; - for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) - { - var memoryBlock = memoryBlocks[blockIndex]; - for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) - { - DoNothing(memoryBlock[itemIndex]); - } - } - - if (nextItemBlockIndex == data.LastBlockWithData) - { - var memoryBlock = memoryBlocks[nextItemBlockIndex]; - blockSize = data.NextItemIndex; - for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) - { - DoNothing(memoryBlock[itemIndex]); - } - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_GetItem_AsForEach() - { - var data = _testRecyclableList; - var dataCount = TestObjectCount; - - foreach (var item in data) - { - DoNothing(item); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_GetItem_AsWhile() - { - var data = _testRecyclableList; - long dataCount = TestObjectCount; - if (dataCount == 0) - { - return; - } - - int blockSize = data.BlockSize; - int nextItemBlockIndex = data.NextItemBlockIndex; - int itemIndex = 0; - int blockIndex = 0; - var memoryBlocks = data.MemoryBlocks; - var memoryBlock = memoryBlocks[0]; - while (blockIndex < nextItemBlockIndex) - { - DoNothing(memoryBlock[itemIndex]); - if (itemIndex + 1 < blockSize) - { - itemIndex++; - } - else - { - blockIndex++; - memoryBlock = memoryBlocks[blockIndex]; - itemIndex = 0; - } - } - - if (nextItemBlockIndex == data.LastBlockWithData) - { - memoryBlock = memoryBlocks[nextItemBlockIndex]; - blockSize = data.NextItemIndex; - while (itemIndex < blockSize) - { - DoNothing(memoryBlock[itemIndex++]); - } - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_SetItem() - { - var data = _testRecyclableList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - data[i] = TestObjects[i]; - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_Count() - { - var data = _testRecyclableList; - DoNothing(data.Count); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_LongCount() - { - var data = _testRecyclableList; - DoNothing(data.LongCount); - } - - [Benchmark(Baseline = false)] - public void RecyclableList_Contains_FirstItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Contains(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_Contains_LastItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Contains(data[^(i + 1)])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_IndexOf_FirstItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.IndexOf(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_IndexOf_LastItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.IndexOf(data[^(i + 1)])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_LongIndexOf_FirstItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.LongIndexOf(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_LongIndexOf_LastItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.LongIndexOf(data[^(i + 1)])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_Remove_FirstItems() - { - var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(list.Remove(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_Remove_LastItems() - { - var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) - { - DoNothing(list.Remove(data[i])); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_RemoveAt_FirstItems() - { - var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - list.RemoveAt(i); - } - } - - [Benchmark(Baseline = false)] - public void RecyclableList_RemoveAt_LastItems() - { - var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; - for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) - { - list.RemoveAt(i); - } - } - } -} From 9609b77a52e290cdeb843723de76ff9f65e45fb9 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 12 Apr 2023 23:39:25 +0200 Subject: [PATCH 213/305] Additional benchmarks added. --- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 2633 +++++++++++------ .../POC/RecyclableListPoc.Benchmarks.cs | 6 +- 2 files changed, 1812 insertions(+), 827 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index c0efe8b..b160456 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -6,882 +6,1858 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { - public partial class RecyclableListPocBenchmarks + #region RecyclableList v1 + + internal class RecyclableListV1 : IDisposable, IList { - #region Old class + private const int ItemNotFoundIndex = -1; - private class OldRecyclableList : IDisposable, IList + 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 int _blockSize; + private int _blockSizePow2Shift; + private int _nextItemBlockIndex; + private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity { - private const int ItemNotFoundIndex = -1; + get => _capacity; + protected set => _capacity = value; + } - 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]; + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); - private int _blockSize; - private int _blockSizePow2Shift; - private int _nextItemBlockIndex; - private int _nextItemIndex; + protected long _longCount; - protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + public long LongCount + { + get => _longCount; + set => _longCount = value; + } - protected ArrayPool _memoryBlocksPool; - protected ArrayPool _blockArrayPool; - protected T[][] _memoryBlocks; + public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; - private long _capacity; - public long Capacity + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void DoRemoveAt(long index) + { + int blockSize = _blockSize; + int nextItemIndex = _nextItemIndex; + T[][] memoryBlocks = _memoryBlocks; + _longCount--; + if (index < _longCount) { - get => _capacity; - protected set => _capacity = value; - } + int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); + int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); - public int Count => checked((int)_longCount); - public bool IsReadOnly { get; } - public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + int targetBlockIndex = (int)(index >> _blockSizePow2Shift); + int targetItemIndex = (int)(index & (blockSize - 1)); - protected long _longCount; - - public long LongCount - { - get => _longCount; - set => _longCount = value; - } - - public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); - public int BlockSize => _blockSize; - public int NextItemBlockIndex => _nextItemBlockIndex; - public int NextItemIndex => _nextItemIndex; - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private void DoRemoveAt(long index) - { - int blockSize = _blockSize; - int nextItemIndex = _nextItemIndex; - T[][] memoryBlocks = _memoryBlocks; - _longCount--; - if (index < _longCount) + int lastTakenBlockIndex = LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) { - int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); - int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); - int targetBlockIndex = (int)(index >> _blockSizePow2Shift); - int targetItemIndex = (int)(index & (blockSize - 1)); - - int lastTakenBlockIndex = LastTakenBlockIndex; - while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) { - int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 - ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) - : Math.Min(nextItemIndex, blockSize - targetItemIndex); - - Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); - // We didn't have enough room in the target array block. There are still items in the source array block to copy. - if (sourceItemIndex + toCopy < blockSize) + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else { - sourceItemIndex += toCopy; targetBlockIndex++; targetItemIndex = 0; } - // We copied all the source items in the current array block. But have we filled the target? + } + } + } + + if (nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; +#pragma warning restore CS8601 + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } else { - sourceItemIndex = 0; - sourceBlockIndex++; - if (targetItemIndex + toCopy < blockSize) - { - targetItemIndex += toCopy; - } - else - { - targetBlockIndex++; - targetItemIndex = 0; - } + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); } } - if (nextItemIndex > 0) + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + { + int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); + int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); + int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); + int blockIndex; + T[][]? newMemoryBlocks; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + { + memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + } + } + + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(requiredBlockCount); + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(source!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(source!, true); + } + } + + source = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + return newMemoryBlocks.Length << minBlockSizePowerToShift; + } + + internal long EnsureCapacity(long requestedCapacity) + { + long newCapacity = _capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); + if (newCapacity > 0 && _capacity == 0) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); + } + + _capacity = newCapacity; + return newCapacity; + } + + public RecyclableListV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + + if (expectedItemsCount > 0) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + + public RecyclableListV1(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) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(_longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListV1 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & (blockSize - 1)) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); + _nextItemBlockIndex = targetBlockIndex; + + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is RecyclableListV1 sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + } + } + + _capacity = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public void Insert(int index, T item) => throw new NotSupportedException(); + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public bool Remove(T item) + { + var itemIndex = LongIndexOf(item); + if (itemIndex >= 0) + { + DoRemoveAt(itemIndex); + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + public void RemoveAt(int index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + public void RemoveAt(long index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListV1() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + + #endregion + + #region RecyclableList v2 + + internal class RecyclableListV2 : 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][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private byte _blockSizePow2Shift; + private int _nextItemBlockIndex; + private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => checked((int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0)); + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableListV2 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2Shift)); + int sourceItemIndex = checked((int)((index + 1) & (blockSize - 1))); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); + int targetItemIndex = checked((int)(index & (blockSize - 1))); + + int lastTakenBlockIndex = list.LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableListV2 list, int minBlockSize, byte minBlockSizeBitShift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = checked((int)(list._capacity >> minBlockSizeBitShift)); + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizeBitShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + { + memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + } + } + + ArrayPool memoryBlocksPool = list._memoryBlocksPool; + + // Allocate new memory block for all arrays + T[][]? newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(requiredBlockCount); + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + return (long)newMemoryBlocks.Length << minBlockSizeBitShift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long EnsureCapacity(RecyclableListV2 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = RecyclableListV2.Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + if (newCapacity > 0 && blockSize != list._memoryBlocks[0].Length) + { + blockSize = list._memoryBlocks[0].Length; + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockArrayPool = RecyclableArrayPool.Shared(blockSize); + } + + list._blockSizePow2Shift = (byte)MathUtils.GetPow2Shift(blockSize); + list._blockSize = blockSize; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public RecyclableListV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_memoryBlocks!.Length > 0 && _blockSize != _memoryBlocks[0].Length) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); + } + } + else + { + _blockSize = minBlockSize; + } + } + else + { + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) + : RecyclableArrayPool.Null; + + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + + public RecyclableListV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + _memoryBlocksPool = memoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_memoryBlocks?.Length > 0 && minBlockSize != _memoryBlocks[0].Length) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); + } + } + else + { + _blockSize = minBlockSize; + } + } + else + { + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & (_blockSize - 1)))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListV2 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & (blockSize - 1)) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & (blockSize - 1))); + _nextItemBlockIndex = targetBlockIndex; + + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is RecyclableListV2 sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + } + } + + _capacity = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) { _nextItemIndex--; } else { - _nextItemIndex = blockSize - 1; + _nextItemIndex = _blockSize - 1; _nextItemBlockIndex--; } if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it - new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; #pragma warning restore CS8601 } + + return true; } - private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + return false; + } - protected void AddRangeEnumerated(IEnumerable source, int growByCount) + public void RemoveBlock(int index) + { + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) { - long i, copied = _longCount; - - int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; - - using IEnumerator enumerator = source.GetEnumerator(); - if (!enumerator.MoveNext()) - { - return; - } - - long capacity = _capacity; - long available = capacity - copied; - - Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); - Span blockArraySpan; - var memoryBlocksCount = memoryBlocksSpan.Length; - while (true) - { - if (copied + growByCount > capacity) - { - capacity = EnsureCapacity(capacity + growByCount); - memoryBlocksCount = _memoryBlocks.Length; - if (blockSize != _blockSize) - { - blockSize = _blockSize; - } - - memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); - available = capacity - copied; - } - - blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - for (i = 1; i <= available; i++) - { - blockArraySpan[targetItemIdx++] = enumerator.Current; - - if (!enumerator.MoveNext()) - { - if (targetItemIdx < blockSize) - { - _nextItemBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; - } - else - { - _nextItemBlockIndex = targetBlockIdx + 1; - _nextItemIndex = 0; - } - - _longCount += copied + i - _longCount; - return; - } - - if (targetItemIdx == blockSize) - { - targetBlockIdx++; - targetItemIdx = 0; - - if (targetBlockIdx == memoryBlocksCount) - { - break; - } - - blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - } - } - - copied += available; - } + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); } - protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) { - long copied = _longCount; - int blockSize = _blockSize; - int targetItemIdx = _nextItemIndex; - int targetBlockIdx = _nextItemBlockIndex; - - Span memoryBlocksSpan; - Span blockArraySpan; - long requiredCapacity = copied + requiredAdditionalCapacity; - if (_capacity < requiredCapacity) - { - _ = EnsureCapacity(requiredCapacity); - blockSize = _blockSize; - } - - var memoryBlocksCount = ReservedBlockCount; - memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - foreach (var item in source) - { - blockArraySpan[targetItemIdx++] = item; - if (targetItemIdx == blockSize) - { - targetBlockIdx++; - targetItemIdx = 0; - if (targetBlockIdx == memoryBlocksCount) - { - break; - } - - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - } - } - - _longCount = requiredCapacity; - _nextItemBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; - return; - } - - protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) - { - Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); - int itemIndex, blockIndex; - for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) - { - itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); - if (itemIndex >= 0) - { - return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); - } - } - - if (blockIndex == lastBlockIndex) - { - itemIndex = nextItemIndex != 0 - ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) - : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); - } - else - { - return ItemNotFoundIndex; - } - - return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) - { - int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); - int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); - int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); - int blockIndex; - T[][]? newMemoryBlocks; - Span memoryBlocksSpan; - - // Release excessive blocks if we're downsizing - if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) - { - memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); - blockIndex = 0; - while (blockIndex < memoryBlocksSpan.Length) - { - blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); - } - } - - // Allocate new memory block for all arrays - newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength - ? (new T[requiredBlockCount][]) - : memoryBlocksPool.Rent(requiredBlockCount); - - // Copy arrays from the old memory block for all arrays - if (sourceBlockCount > 0) - { - Array.Copy(source!, newMemoryBlocks, sourceBlockCount); - // We can now return the old memory block for all arrays itself - if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) - { - memoryBlocksPool.Return(source!, true); - } - } - - source = newMemoryBlocks; - - // Allocate arrays for any new blocks - if (requiredBlockCount > sourceBlockCount) - { - memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); - if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) - { - if (sourceBlockCount == 0) - { - memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); - minBlockSize = memoryBlocksSpan[0].Length; - blockIndex = 1; - } - else - { - blockIndex = 0; - } - - while (blockIndex < memoryBlocksSpan.Length) - { - memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); - } - } - else - { - blockIndex = 0; - while (blockIndex < memoryBlocksSpan.Length) - { - memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; - } - } - } - - return newMemoryBlocks.Length << minBlockSizePowerToShift; - } - - internal long EnsureCapacity(long requestedCapacity) - { - long newCapacity = _capacity > 0 - ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) - : requestedCapacity; - - newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); - if (newCapacity > 0 && _capacity == 0) - { - _blockSize = _memoryBlocks[0].Length; - _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); - } - - _capacity = newCapacity; - return newCapacity; - } - - public OldRecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) - { - memoryBlocksPool ??= _defaultMemoryBlocksPool; - blockArrayPool ??= _defaultBlockArrayPool; - _memoryBlocksPool = memoryBlocksPool; - _blockArrayPool = blockArrayPool; - - if (expectedItemsCount > 0) - { - _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); - if (_memoryBlocks.Length > 0) - { - minBlockSize = _memoryBlocks[0].Length; - } - - _blockSize = minBlockSize; - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - } - else - { - _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - _memoryBlocks = _emptyMemoryBlocksArray; - } - } - - public OldRecyclableList(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) - { - _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); - if (_memoryBlocks.Length > 0) - { - minBlockSize = _memoryBlocks[0].Length; - } - - _blockSize = minBlockSize; - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - } - else - { - _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - _memoryBlocks = _emptyMemoryBlocksArray; - } - - AddRange(source); - } - - public T this[long index] - { - get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; - set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; - } - - public T this[int index] - { - get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; - set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; - } - - public T[][] MemoryBlocks { get => _memoryBlocks; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(T item) - { - if (_capacity < _longCount + 1) - { - _ = EnsureCapacity(_longCount + 1); - } - - _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; - if (_nextItemIndex == _blockSize) - { - _nextItemBlockIndex++; - _nextItemIndex = 0; - } - - _longCount++; - } - - public void AddRange(in T[] items) - { - if (items.LongLength == 0) - { - return; - } - - long targetCapacity = _longCount + items.LongLength; - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; - - Span itemsSpan = new(items); - Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); - Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (targetBlockArraySpan.Length <= itemsSpan.Length) - { - itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); - itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; - targetBlockIndex++; - if (targetBlockIndex == memoryBlockCount) - { - break; - } - - targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); - } - - if (itemsSpan.Length > 0) - { - itemsSpan.CopyTo(targetBlockArraySpan); - } - - _nextItemIndex = itemsSpan.Length; - _longCount = targetCapacity; - _nextItemBlockIndex = targetBlockIndex; - } - - public void AddRange(RecyclableArrayList items) - { - if (items.Count == 0) - { - return; - } - - long targetCapacity = _longCount + items.Count; - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; - - Span itemsSpan = new(items.AsArray(), 0, items.Count); - Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); - Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (targetBlockArraySpan.Length <= itemsSpan.Length) - { - itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); - itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; - targetBlockIndex++; - if (targetBlockIndex == memoryBlockCount) - { - break; - } - - targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); - } - - _nextItemIndex = itemsSpan.Length; - itemsSpan.CopyTo(targetBlockArraySpan); - _longCount = targetCapacity; - _nextItemBlockIndex = targetBlockIndex; - } - - public void AddRange(OldRecyclableList items) - { - if (items.LongCount == 0) - { - return; - } - - long itemsCount = items.LongCount, - targetCapacity = _longCount + itemsCount; - - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - sourceBlockIndex = 0, - targetBlockIndex = _nextItemBlockIndex, - toCopy; - - Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); - Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); - Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); - Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - long copiedCount = 0; - while (copiedCount < itemsCount) - { - toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); - if (targetBlockArraySpan.Length < toCopy) - { - toCopy = targetBlockArraySpan.Length; - itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); - targetBlockIndex++; - targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); - itemsSpan = itemsSpan[toCopy..]; - copiedCount += toCopy; - } - else - { - itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); - targetBlockArraySpan = targetBlockArraySpan[toCopy..]; - itemsSpan = itemsSpan[toCopy..]; - if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) - { - sourceBlockIndex++; - itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); - } - - copiedCount += toCopy; - if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) - { - targetBlockIndex++; - targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); - } - } - } - - if ((targetCapacity & (blockSize - 1)) == 0) - { - _nextItemIndex = 0; - _nextItemBlockIndex = targetBlockIndex + 1; - } - else - { - _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); - _nextItemBlockIndex = targetBlockIndex; - - } - - _longCount = targetCapacity; - } - - public void AddRange(List items) - { - if (items.Count == 0) - { - return; - } - - long targetCapacity = _longCount + items.Count; - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - targetBlockIndex = _nextItemBlockIndex, - itemsCount = items.Count, - copied = Math.Min(blockSize - _nextItemIndex, itemsCount); - - Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); - items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); - if (_nextItemIndex + copied == blockSize) - { - targetBlockIndex++; - _nextItemIndex = 0; - } - else - { - _nextItemIndex += copied; - } - - while (blockSize <= itemsCount - copied) - { - items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); - copied += blockSize; - } - - if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) - { - _nextItemIndex = itemsCount - copied; - items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); - } - - _nextItemBlockIndex = targetBlockIndex; - _longCount = targetCapacity; - } - - public void AddRange(IList items) - { - if (items.Count == 0) - { - return; - } - - long targetCapacity = _longCount + items.Count; - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; - - T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; - try - { - items.CopyTo(itemsBuffer, 0); - - Span itemsSpan = new(itemsBuffer, 0, items.Count); - Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); - Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (targetBlockArraySpan.Length <= itemsSpan.Length) - { - itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); - itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; - targetBlockIndex++; - if (targetBlockIndex == memoryBlockCount) - { - break; - } - - targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); - } - - itemsSpan.CopyTo(targetBlockArraySpan); - _longCount = targetCapacity; - _nextItemBlockIndex = targetBlockIndex; - _nextItemIndex = itemsSpan.Length; - } - finally - { - if (items.Count >= RecyclableDefaults.MinPooledArrayLength) - { - _blockArrayPool.Return(itemsBuffer, NeedsClearing); - } - } - } - - public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) - { - if (source is OldRecyclableList sourceRecyclableList) - { - AddRange(sourceRecyclableList); - return; - } - - if (source is RecyclableArrayList sourceRecyclableArrayList) - { - AddRange(sourceRecyclableArrayList); - return; - } - - if (source is T[] sourceArray) - { - AddRange(sourceArray); - return; - } - - if (source is List sourceList) - { - AddRange(sourceList); - return; - } - - if (source is IList sourceIList) - { - AddRange(sourceIList); - return; - } - - if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) - { - AddRangeWithKnownCount(source, requiredAdditionalCapacity); - return; - } - - AddRangeEnumerated(source, growByCount); - } - - public void Clear() - { - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); - int memoryBlocksCount = memoryBlocksSpan.Length; - ArrayPool blockArrayPool = _blockArrayPool; - for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) - { - blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); - } - } - - _capacity = 0; - _nextItemBlockIndex = 0; _nextItemIndex = 0; - _longCount = 0; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(T item) + else if (_nextItemBlockIndex > index) { - if (_longCount == 0) - { - return false; - } - - if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) - { - return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; - } - - Span memoryBlocksSpan = new(_memoryBlocks); - int lastBlockIndex = _nextItemBlockIndex; - for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) - { - if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) - { - return true; - } - } - - return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 - ? ItemNotFoundIndex - : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) - ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); - - public void Insert(int index, T item) => throw new NotSupportedException(); - public long LongIndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndex - : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) - ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); - - public bool Remove(T item) - { - var itemIndex = LongIndexOf(item); - if (itemIndex >= 0) - { - DoRemoveAt(itemIndex); - return true; - } - - return false; - } - - public void RemoveBlock(int index) - { - if (_blockSize < RecyclableDefaults.MinPooledArrayLength) - { - _memoryBlocks[index] = _emptyBlockArray; - } - else - { - _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); - } - - _capacity -= _blockSize; - if (_nextItemBlockIndex == index) - { - _nextItemIndex = 0; - } - else if (_nextItemBlockIndex > index) - { - _nextItemBlockIndex--; - } - } - - public void RemoveAt(int index) - { - if (index > _longCount - 1 || index < 0) - { - ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); - } - - DoRemoveAt(index); - } - - public void RemoveAt(long index) - { - if (index > _longCount - 1 || index < 0) - { - ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); - } - - DoRemoveAt(index); - } - - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - - ~OldRecyclableList() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - if (_capacity > 0) - { - Clear(); - if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) - { - _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); - } - - GC.SuppressFinalize(this); - } + _nextItemBlockIndex--; } } - - #endregion + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListV2() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + public partial class RecyclableListPocBenchmarks + { + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacityV1_ByPowOf2() + { + using var list = new RecyclableListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = list.EnsureCapacity(TestObjectCount); + } [Benchmark(Baseline = true)] - public void RecyclableList_EnsureCapacity_Old_ByPowOf2() + public void RecyclableList_EnsureCapacityV2_ByPowOf2() { - using var list = new OldRecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - _ = list.EnsureCapacity(TestObjectCount); + using var list = new RecyclableListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); } [Benchmark(Baseline = false)] @@ -892,12 +1868,19 @@ namespace Recyclable.Collections.Benchmarks.POC } [Benchmark(Baseline = false)] - public void RecyclableList_EnsureCapacity_Old_ByBlockSize() + public void RecyclableList_EnsureCapacityV1_ByBlockSize() { - using var list = new OldRecyclableList(); + using var list = new RecyclableListV1(); _ = list.EnsureCapacity(TestObjectCount); } + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacityV2_ByBlockSize() + { + using var list = new RecyclableListV2(); + _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); + } + [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacity_ByBlockSize() { diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs index 738c0ea..1b48f5a 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -5,9 +5,11 @@ public static void Run() { var benchmark = new RecyclableListPocBenchmarks(); - benchmark.RecyclableList_EnsureCapacity_Old_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacityV1_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacityV2_ByPowOf2(); benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); - benchmark.RecyclableList_EnsureCapacity_Old_ByBlockSize(); + benchmark.RecyclableList_EnsureCapacityV1_ByBlockSize(); + benchmark.RecyclableList_EnsureCapacityV2_ByBlockSize(); benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); } } From 6fb6ec3f07aaeccb221927960df408d118131894 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 12 Apr 2023 23:44:07 +0200 Subject: [PATCH 214/305] RecyclableList performance improvements. --- Recyclable.Collections/RecyclableList.cs | 107 ++++++++++------------- 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 2a036fd..31eb0f2 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -14,9 +14,11 @@ namespace Recyclable.Collections private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; private static readonly T[] _emptyBlockArray = new T[0]; + //private MemoryBlockInfo _memoryBlockInfo; private int _blockSize; private byte _blockSizePow2Shift; private int _blockSizeMinus1; + private bool _blockPoolingRequired; private int _nextItemBlockIndex; private int _nextItemIndex; @@ -244,7 +246,7 @@ namespace Recyclable.Collections /// /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. - /// Pre-calculated bit shifting value for left & right shift operations against. + /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -253,16 +255,17 @@ namespace Recyclable.Collections /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizeBitShift, long newCapacity) + protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + //protected static long Resize(RecyclableList list, in MemoryBlockInfo blockInfo, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; - int sourceBlockCount = checked((int)(list._capacity >> minBlockSizeBitShift)); - int requiredBlockCount = checked((int)(newCapacity >> minBlockSizeBitShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int sourceBlockCount = checked((int)(list._capacity >> minBlockSizePow2Shift)); + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; Span memoryBlocksSpan; // Release excessive blocks if we're downsizing - if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + if (requiredBlockCount < sourceBlockCount && list._blockPoolingRequired) { memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, sourceBlockCount - requiredBlockCount); blockIndex = 0; @@ -276,7 +279,7 @@ namespace Recyclable.Collections // Allocate new memory block for all arrays T[][]? newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength - ? (new T[requiredBlockCount][]) + ? new T[requiredBlockCount][] : memoryBlocksPool.Rent(requiredBlockCount); // Copy arrays from the old memory block for all arrays @@ -296,7 +299,7 @@ namespace Recyclable.Collections if (requiredBlockCount > sourceBlockCount) { memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); - if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + if (list._blockPoolingRequired) { if (sourceBlockCount == 0) { @@ -324,7 +327,7 @@ namespace Recyclable.Collections } } - return (long)newMemoryBlocks.Length << minBlockSizeBitShift; + return (long)newMemoryBlocks.Length << minBlockSizePow2Shift; } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -336,105 +339,87 @@ namespace Recyclable.Collections int blockSize = list._blockSize; newCapacity = Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); - if (newCapacity > 0 && blockSize != list._memoryBlocks[0].Length) + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) { - blockSize = list._memoryBlocks[0].Length; - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - list._blockArrayPool = RecyclableArrayPool.Shared(blockSize); - } - - list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); - list._blockSize = blockSize; - list._blockSizeMinus1 = blockSize - 1; + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; } list._capacity = newCapacity; return newCapacity; } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockPoolingRequired = true; + list._blockArrayPool = blockArrayPool ?? RecyclableArrayPool.Shared(blockSize); + } + else + { + list._blockPoolingRequired = false; + list._blockArrayPool = RecyclableArrayPool.Null; + } + } + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; if (expectedItemsCount > 0) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) - : RecyclableArrayPool.Null; - - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); if (_memoryBlocks!.Length > 0 && _blockSize != _memoryBlocks[0].Length) { - _blockSize = _memoryBlocks[0].Length; + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); _blockSizeMinus1 = _blockSize - 1; - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); - } } else { - _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; } } else { - _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); _blockSizeMinus1 = _blockSize - 1; - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); - _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) - : RecyclableArrayPool.Null; - _memoryBlocks = _emptyMemoryBlocksArray; } } +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling public RecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 { - memoryBlocksPool ??= _defaultMemoryBlocksPool; - _memoryBlocksPool = memoryBlocksPool; - + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; if (expectedItemsCount > 0) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) - : RecyclableArrayPool.Null; - - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); if (_memoryBlocks!.Length > 0 && minBlockSize != _memoryBlocks[0].Length) { - _blockSize = _memoryBlocks[0].Length; + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); _blockSizeMinus1 = _blockSize - 1; - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); - } } else { - _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; } } else { - _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) - : RecyclableArrayPool.Null; - + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); _blockSizeMinus1 = _blockSize - 1; - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -770,7 +755,7 @@ namespace Recyclable.Collections public void Clear() { - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (_blockPoolingRequired) { Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); int memoryBlocksCount = memoryBlocksSpan.Length; @@ -870,13 +855,13 @@ namespace Recyclable.Collections public void RemoveBlock(int index) { - if (_blockSize < RecyclableDefaults.MinPooledArrayLength) + if (_blockPoolingRequired) { - _memoryBlocks[index] = _emptyBlockArray; + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); } else { - _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; } _capacity -= _blockSize; From ef09143eb1cc3a979f899b9023eb014898992bec Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 12 Apr 2023 23:56:07 +0200 Subject: [PATCH 215/305] Benchmark results updated. --- .../Results/AllInOneBenchmarks.txt | 166 +++++++++++------- 1 file changed, 107 insertions(+), 59 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt index 096d5f5..97593b9 100644 --- a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt +++ b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt @@ -1,72 +1,120 @@ -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | -|------------------------------------------------------------ |----------------------:|-------------------:|-------------------:|----------------------:|-----------:|--------:|----------:|----------:|---------:|-----------:|------------:| -| Array_Create_WithCapacity | 2,026,196.4844 ns | 25,507.5479 ns | 23,859.7761 ns | 2,028,675.0000 ns | 8.466 | 0.10 | 54.6875 | 54.6875 | 54.6875 | 8000029 B | NA | +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|------------------------------------------------------------ |-----------------------:|--------------------:|--------------------:|-----------------------:|-----------:|--------:|----------:|----------:|---------:|-----------:|------------:| -| Array_SetItem | 5,108,186.2723 ns | 5,237.9274 ns | 4,643.2862 ns | 5,108,716.0156 ns | 21.343 | 0.02 | - | - | - | 5 B | NA | -| List_SetItem | 5,209,882.6823 ns | 63,181.7652 ns | 59,100.2623 ns | 5,175,639.4531 ns | 21.768 | 0.25 | - | - | - | 5 B | NA | -| PooledList_SetItem | 5,341,256.3151 ns | 8,251.3008 ns | 6,442.0730 ns | 5,340,014.4531 ns | 22.315 | 0.04 | - | - | - | 5 B | NA | -| RecyclableArrayList_SetItem | 4,012,900.8333 ns | 8,775.9740 ns | 8,209.0515 ns | 4,014,782.8125 ns | 16.766 | 0.04 | - | - | - | 5 B | NA | -| RecyclableList_SetItem | 4,749,345.8854 ns | 27,832.8301 ns | 26,034.8466 ns | 4,741,316.4062 ns | 19.843 | 0.10 | - | - | - | 5 B | NA | -| List_Add | 10,418,761.3911 ns | 204,147.0502 ns | 311,754.1071 ns | 10,363,307.8125 ns | 44.147 | 1.27 | 140.6250 | 78.1250 | 78.1250 | 16777798 B | NA | -| PooledList_Add | 7,747,779.9279 ns | 19,784.5733 ns | 16,521.0148 ns | 7,750,001.5625 ns | 32.370 | 0.06 | - | - | - | 63 B | NA | -| RecyclableArrayList_Add | 7,237,424.9399 ns | 20,275.5325 ns | 16,930.9879 ns | 7,232,728.1250 ns | 30.238 | 0.08 | - | - | - | 1229 B | NA | -| RecyclableList_Add | 5,611,979.0365 ns | 8,138.0172 ns | 6,353.6285 ns | 5,610,862.5000 ns | 23.446 | 0.03 | - | - | - | 733 B | NA | +| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|------------------------------------------------------------ |-----------:|---------:|---------:|------:|--------:|--------:|--------:|--------:|----------:|------------:| +| Array_Create_WithCapacity | 2,447.9 us | 8.35 us | 7.40 us | 1.99 | 0.02 | 58.5938 | 58.5938 | 58.5938 | 8000061 B | 137,932.09 | +| List_Create_WithCapacity | 2,377.6 us | 43.03 us | 40.25 us | 1.93 | 0.03 | 54.6875 | 54.6875 | 54.6875 | 8000451 B | 137,938.81 | +| PooledList_Create_WithCapacity | 1,229.4 us | 10.70 us | 9.48 us | 1.00 | 0.00 | - | - | - | 58 B | 1.00 | +| RecyclableArrayList_Create_WithCapacity | 1,233.9 us | 3.93 us | 3.68 us | 1.00 | 0.01 | - | - | - | 41 B | 0.71 | +| RecyclableList_Create_WithCapacity | 786.5 us | 2.32 us | 1.94 us | 0.64 | 0.01 | - | - | - | 425 B | 7.33 | -| Array_GetItem | 239,342.1403 ns | 174.5315 ns | 163.2569 ns | 239,256.2256 ns | 1.000 | 0.00 | - | - | - | - | NA | -| List_GetItem | 362,950.6755 ns | 183.6751 ns | 143.4014 ns | 362,937.7930 ns | 1.516 | 0.00 | - | - | - | - | NA | -| PooledList_GetItem | 362,154.3983 ns | 139.8387 ns | 116.7716 ns | 362,175.5859 ns | 1.513 | 0.00 | - | - | - | - | NA | -| RecyclableArrayList_GetItem | 239,285.5675 ns | 103.7975 ns | 86.6756 ns | 239,275.0000 ns | 1.000 | 0.00 | - | - | - | - | NA | -| RecyclableList_GetItem | 580,268.8770 ns | 7,251.9194 ns | 6,783.4499 ns | 577,058.2520 ns | 2.424 | 0.03 | - | - | - | 1 B | NA | -| RecyclableList_GetItem_AsArray | 239,421.2158 ns | 63.1265 ns | 52.7135 ns | 239,409.4727 ns | 1.000 | 0.00 | - | - | - | - | NA | +| List_Create | 3.5523 ns | 0.0181 ns | 0.0141 ns | 3.5476 ns | 0.000 | 0.00 | 0.0153 | - | - | 32 B | 0.000 | +| PooledList_Create | 6.9236 ns | 0.0110 ns | 0.0098 ns | 6.9222 ns | 0.000 | 0.00 | 0.0268 | - | - | 56 B | 0.000 | +| RecyclableArrayList_Create | 4.6451 ns | 0.0063 ns | 0.0053 ns | 4.6457 ns | 0.000 | 0.00 | 0.0191 | - | - | 40 B | 0.000 | +| RecyclableList_Create | 212.8164 ns | 11.9841 ns | 35.3355 ns | 213.2258 ns | 0.000 | 0.00 | 0.0381 | 0.0379 | - | 80 B | 0.000 | -| Array_Count | 0.0073 ns | 0.0014 ns | 0.0013 ns | 0.0074 ns | 0.000 | 0.00 | - | - | - | - | NA | -| Array_LongCount | 0.0022 ns | 0.0012 ns | 0.0010 ns | 0.0022 ns | 0.000 | 0.00 | - | - | - | - | NA | -| List_Count | 1.2221 ns | 0.0014 ns | 0.0011 ns | 1.2221 ns | 0.000 | 0.00 | - | - | - | - | NA | -| PooledList_Count | 0.0046 ns | 0.0043 ns | 0.0040 ns | 0.0023 ns | 0.000 | 0.00 | - | - | - | - | NA | -| RecyclableArrayList_Count | 1.2188 ns | 0.0022 ns | 0.0019 ns | 1.2183 ns | 0.000 | 0.00 | - | - | - | - | NA | -| RecyclableList_Count | 0.0123 ns | 0.0007 ns | 0.0005 ns | 0.0121 ns | 0.000 | 0.00 | - | - | - | - | NA | -| RecyclableList_LongCount | 0.0022 ns | 0.0014 ns | 0.0012 ns | 0.0019 ns | 0.000 | 0.00 | - | - | - | - | NA | + Array_GetItem | 239.7 us | 0.36 us | 0.32 us | 0.66 | 0.00 | - | NA | + List_GetItem | 363.5 us | 0.67 us | 0.56 us | 1.00 | 0.00 | - | NA | + PooledList_GetItem | 365.3 us | 0.90 us | 0.75 us | 1.00 | 0.00 | - | NA | + RecyclableArrayList_GetItem | 239.2 us | 0.07 us | 0.06 us | 0.65 | 0.00 | - | NA | + RecyclableList_GetItem | 538.0 us | 1.01 us | 0.89 us | 1.47 | 0.00 | 1 B | NA | + RecyclableList_GetItem_AsSpan | 673.5 us | 4.85 us | 4.30 us | 1.84 | 0.01 | 1 B | NA | + RecyclableList_GetItem_AsArray | 239.4 us | 0.22 us | 0.20 us | 0.66 | 0.00 | - | NA | + RecyclableList_GetItem_AsForEach | 5,514.6 us | 27.28 us | 22.78 us | 15.10 | 0.06 | 109 B | NA | + RecyclableList_GetItem_AsWhile | 459.3 us | 0.38 us | 0.34 us | 1.26 | 0.00 | - | NA | -| List_Add_WithCapacity | 6,895,420.4314 ns | 135,354.6651 ns | 260,782.4874 ns | 6,854,155.0781 ns | 28.519 | 1.13 | 62.5000 | 62.5000 | 62.5000 | 8000360 B | NA | -| PooledList_Add_WithCapacity | 5,321,730.2455 ns | 12,387.1665 ns | 10,980.9005 ns | 5,321,642.5781 ns | 22.235 | 0.05 | - | - | - | 63 B | NA | -| RecyclableArrayList_Add_WithCapacity | 5,284,050.7212 ns | 18,133.4955 ns | 15,142.2900 ns | 5,280,705.4688 ns | 22.077 | 0.07 | - | - | - | 45 B | NA | -| RecyclableList_Add_WithCapacity | 5,792,708.2031 ns | 3,343.4720 ns | 3,127.4858 ns | 5,793,183.2031 ns | 24.203 | 0.02 | - | - | - | 173 B | NA | +| Array_SetItem | 2,576,086.8389 ns | 13,880.4584 ns | 11,590.8114 ns | 2,576,978.1250 ns | 2.194 | 0.01 | - | - | - | 2 B | 0.000 | +| List_SetItem | 3,361,745.7734 ns | 98,390.1277 ns | 290,105.5114 ns | 3,505,120.8984 ns | 2.771 | 0.29 | - | - | - | 2 B | 0.000 | +| PooledList_SetItem | 2,676,757.7524 ns | 1,228.8178 ns | 1,026.1185 ns | 2,676,991.4062 ns | 2.280 | 0.01 | - | - | - | 2 B | 0.000 | +| RecyclableArrayList_SetItem | 2,547,059.6540 ns | 9,002.9953 ns | 7,980.9208 ns | 2,542,056.8359 ns | 2.170 | 0.01 | - | - | - | 2 B | 0.000 | +| RecyclableList_SetItem | 2,326,771.9866 ns | 3,186.0942 ns | 2,824.3895 ns | 2,326,636.7188 ns | 1.981 | 0.01 | - | - | - | 2 B | 0.000 | -| List_AddRangeWhenSourceIsArray | 3,749,286.9792 ns | 54,523.3877 ns | 51,001.2107 ns | 3,758,780.4688 ns | 15.665 | 0.21 | 42.9688 | 42.9688 | 42.9688 | 8000363 B | NA | -| PooledList_AddRangeWhenSourceIsArray | 2,171,012.5601 ns | 3,257.1297 ns | 2,719.8508 ns | 2,170,896.0938 ns | 9.070 | 0.01 | - | - | - | 60 B | NA | -| RecyclableArrayList_AddRangeWhenSourceIsArray | 2,221,883.2552 ns | 6,140.5301 ns | 5,743.8557 ns | 2,223,654.2969 ns | 9.283 | 0.03 | - | - | - | 42 B | NA | -| RecyclableList_AddRangeWhenSourceIsArray | 2,375,847.7958 ns | 2,930.1660 ns | 2,597.5158 ns | 2,374,959.9609 ns | 9.927 | 0.01 | - | - | - | 170 B | NA | +| List_Add | 6,507,973.8528 ns | 130,648.8061 ns | 374,855.9157 ns | 6,502,201.1719 ns | 5.457 | 0.23 | 156.2500 | 93.7500 | 93.7500 | 8389554 B | 2.097 | +| PooledList_Add | 3,525,004.4922 ns | 4,824.6367 ns | 4,028.7902 ns | 3,525,046.6797 ns | 3.002 | 0.01 | - | - | - | 60 B | 0.000 | +| RecyclableArrayList_Add | 3,727,375.5709 ns | 3,512.8985 ns | 2,933.4293 ns | 3,728,521.8750 ns | 3.175 | 0.01 | - | - | - | 1226 B | 0.000 | +| RecyclableList_Add | 2,951,985.0651 ns | 4,403.2060 ns | 4,118.7616 ns | 2,952,207.6172 ns | 2.514 | 0.01 | - | - | - | 730 B | 0.000 | -| List_AddRangeWhenSourceIsList | 3,672,995.1904 ns | 71,114.1123 ns | 69,843.5671 ns | 3,681,876.3672 ns | 15.345 | 0.30 | 42.9688 | 42.9688 | 42.9688 | 8000308 B | NA | -| PooledList_AddRangeWhenSourceIsList | 2,174,547.5811 ns | 6,715.5228 ns | 5,607.7656 ns | 2,172,790.8203 ns | 9.085 | 0.03 | - | - | - | 60 B | NA | -| RecyclableArrayList_AddRangeWhenSourceIsList | 2,233,371.7849 ns | 23,364.1263 ns | 19,510.1037 ns | 2,239,082.8125 ns | 9.331 | 0.08 | - | - | - | 42 B | NA | -| RecyclableList_AddRangeWhenSourceIsList | 2,366,026.9252 ns | 2,060.7322 ns | 1,826.7854 ns | 2,366,219.5312 ns | 9.886 | 0.01 | - | - | - | 170 B | NA | +| List_Add_WithCapacity | 3,724,682.0668 ns | 88,719.4866 ns | 260,198.8962 ns | 3,717,873.0469 ns | 3.326 | 0.09 | 50.7813 | 50.7813 | 50.7813 | 4000368 B | 1.000 | +| PooledList_Add_WithCapacity | 2,575,618.8337 ns | 5,277.9410 ns | 4,678.7572 ns | 2,575,805.2734 ns | 2.194 | 0.01 | - | - | - | 60 B | 0.000 | +| RecyclableArrayList_Add_WithCapacity | 2,785,606.2500 ns | 4,595.4354 ns | 4,073.7338 ns | 2,785,292.7734 ns | 2.372 | 0.01 | - | - | - | 42 B | 0.000 | +| RecyclableList_Add_WithCapacity | 2,878,685.9701 ns | 44,974.7609 ns | 35,113.3350 ns | 2,873,102.9297 ns | 2.453 | 0.03 | - | - | - | 170 B | 0.000 | -| List_AddRangeWhenSourceIsIList | 3,745,132.7415 ns | 71,613.4245 ns | 87,947.7165 ns | 3,766,088.2812 ns | 15.676 | 0.39 | 46.8750 | 46.8750 | 46.8750 | 8000355 B | NA | -| PooledList_AddRangeWhenSourceIsIList | 2,181,849.8372 ns | 5,190.8155 ns | 4,052.6473 ns | 2,181,487.5000 ns | 9.115 | 0.02 | - | - | - | 60 B | NA | -| RecyclableArrayList_AddRangeWhenSourceIsIList | 2,196,517.2154 ns | 5,996.6282 ns | 5,315.8547 ns | 2,196,481.2500 ns | 9.177 | 0.02 | - | - | - | 42 B | NA | -| RecyclableList_AddRangeWhenSourceIsIList | 5,078,413.7835 ns | 4,215.5884 ns | 3,737.0093 ns | 5,078,082.4219 ns | 21.219 | 0.03 | - | - | - | 173 B | NA | +| List_AddRangeWhenSourceIsArray | 1,966,607.1581 ns | 39,075.4380 ns | 100,165.2861 ns | 1,958,457.4219 ns | 1.721 | 0.07 | 42.9688 | 42.9688 | 42.9688 | 4000364 B | 1.000 | +| PooledList_AddRangeWhenSourceIsArray | 972,973.4049 ns | 12,365.6121 ns | 11,566.8012 ns | 974,744.8242 ns | 0.829 | 0.01 | - | - | - | 57 B | 0.000 | +| RecyclableArrayList_AddRangeWhenSourceIsArray | 1,098,651.7578 ns | 3,202.7829 ns | 2,995.8851 ns | 1,097,979.4922 ns | 0.936 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsArray | 1,023,799.0234 ns | 2,772.1984 ns | 2,457.4816 ns | 1,023,618.7500 ns | 0.872 | 0.00 | - | - | - | 169 B | 0.000 | -| List_AddRangeWhenSourceIsIEnumerable | 90,967,420.2381 ns | 1,673,003.6192 ns | 1,483,074.1318 ns | 90,862,658.3333 ns | 380.079 | 6.30 | 4333.3333 | 2333.3333 | 500.0000 | 40778028 B | NA | -| PooledList_AddRangeWhenSourceIsIEnumerable | 61,756,721.3068 ns | 1,225,233.1407 ns | 2,301,284.8288 ns | 61,755,050.0000 ns | 258.529 | 11.85 | 4375.0000 | 2375.0000 | 500.0000 | 24000600 B | NA | -| RecyclableArrayList_AddRangeWhenSourceIsIEnumerable | 60,353,570.2532 ns | 1,156,946.0276 ns | 3,007,056.5522 ns | 60,136,425.0000 ns | 253.210 | 12.57 | 4250.0000 | 2500.0000 | 375.0000 | 24000331 B | NA | -| RecyclableList_AddRangeWhenSourceIsIEnumerable | 60,597,556.2500 ns | 1,206,305.7735 ns | 1,481,451.8228 ns | 60,891,993.7500 ns | 253.050 | 6.19 | 4375.0000 | 2375.0000 | 500.0000 | 24000503 B | NA | +| List_AddRangeWhenSourceIsList | 1,954,805.9462 ns | 37,631.3829 ns | 71,597.6170 ns | 1,947,531.6406 ns | 1.656 | 0.06 | 42.9688 | 42.9688 | 42.9688 | 4000363 B | 1.000 | +| PooledList_AddRangeWhenSourceIsList | 963,657.7116 ns | 5,594.8195 ns | 5,233.3976 ns | 965,055.4199 ns | 0.820 | 0.01 | - | - | - | 57 B | 0.000 | +| RecyclableArrayList_AddRangeWhenSourceIsList | 1,114,697.8385 ns | 4,015.8164 ns | 3,756.3971 ns | 1,115,863.6719 ns | 0.950 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsList | 1,008,424.0374 ns | 2,179.7502 ns | 1,932.2918 ns | 1,008,779.8828 ns | 0.859 | 0.00 | - | - | - | 169 B | 0.000 | -| PooledList_AddRangeWhenSourceIsPooledList | 2,206,735.6220 ns | 2,860.3858 ns | 2,388.5517 ns | 2,206,296.6797 ns | 9.220 | 0.01 | - | - | - | 60 B | NA | -| RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList | 2,213,033.1771 ns | 26,423.0604 ns | 24,716.1471 ns | 2,227,495.3125 ns | 9.246 | 0.10 | - | - | - | 42 B | NA | -| RecyclableList_AddRangeWhenSourceIsRecyclableArrayList | 2,456,687.9557 ns | 45,977.2836 ns | 43,007.1796 ns | 2,458,159.5703 ns | 10.264 | 0.18 | - | - | - | 170 B | NA | +| List_AddRangeWhenSourceIsIList | 2,045,453.8895 ns | 40,653.4327 ns | 98,956.0738 ns | 2,040,041.7969 ns | 1.734 | 0.12 | 42.9688 | 42.9688 | 42.9688 | 4000366 B | 1.000 | +| PooledList_AddRangeWhenSourceIsIList | 977,402.4790 ns | 4,165.1933 ns | 3,478.1251 ns | 978,420.3125 ns | 0.832 | 0.00 | - | - | - | 58 B | 0.000 | +| RecyclableArrayList_AddRangeWhenSourceIsIList | 1,109,563.8477 ns | 4,480.2733 ns | 4,190.8505 ns | 1,108,281.9336 ns | 0.945 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsIList | 2,632,797.0052 ns | 3,899.2563 ns | 3,044.2828 ns | 2,632,267.1875 ns | 2.243 | 0.01 | - | - | - | 170 B | 0.000 | -| List_AddRange_WithCapacity | 3,688,541.3969 ns | 72,945.5071 ns | 86,836.4373 ns | 3,678,040.4297 ns | 15.440 | 0.38 | 42.9688 | 42.9688 | 42.9688 | 8000363 B | NA | -| PooledList_AddRange_WithCapacity | 2,215,278.5993 ns | 4,630.4941 ns | 4,104.8124 ns | 2,216,485.9375 ns | 9.256 | 0.02 | - | - | - | 60 B | NA | -| RecyclableArrayList_AddRange_WithCapacity | 2,194,831.5569 ns | 7,092.2469 ns | 6,287.0921 ns | 2,193,706.8359 ns | 9.170 | 0.03 | - | - | - | 42 B | NA | -| RecyclableList_AddRange_WithCapacity | 2,863,515.4536 ns | 72,047.1346 ns | 210,164.8733 ns | 2,862,379.4922 ns | 11.665 | 0.69 | - | - | - | 170 B | NA | +| PooledList_AddRangeWhenSourceIsIEnumerable | 28,102,425.4630 ns | 556,294.1092 ns | 779,846.6478 ns | 28,009,543.7500 ns | 24.007 | 0.74 | 2187.5000 | 1375.0000 | 218.7500 | 12000526 B | 3.000 | +| List_AddRangeWhenSourceIsIEnumerable | 47,884,030.8442 ns | 955,391.3313 ns | 1,370,193.6989 ns | 48,152,172.7273 ns | 40.578 | 1.04 | 2272.7273 | 1363.6364 | 363.6364 | 20389288 B | 5.097 | +| RecyclableArrayList_AddRangeWhenSourceIsIEnumerable | 27,102,237.2533 ns | 535,598.7947 ns | 595,316.6412 ns | 27,248,382.8125 ns | 23.009 | 0.61 | 2250.0000 | 1406.2500 | 250.0000 | 12000305 B | 3.000 | +| RecyclableList_AddRangeWhenSourceIsIEnumerable | 27,206,882.1023 ns | 517,997.7196 ns | 636,147.7188 ns | 27,262,153.1250 ns | 23.049 | 0.64 | 2218.7500 | 1468.7500 | 250.0000 | 12000361 B | 3.000 | -| List_Contains_FirstItems | 7,538,040,293.3333 ns | 64,412,902.0196 ns | 60,251,868.5137 ns | 7,556,011,200.0000 ns | 31,494.854 | 253.82 | - | - | - | 600 B | NA | -| PooledList_Contains_FirstItems | 7,473,775,857.1429 ns | 51,447,673.7955 ns | 45,607,022.7642 ns | 7,475,783,150.0000 ns | 31,226.686 | 181.41 | - | - | - | 600 B | NA | -| RecyclableList_Contains_FirstItems | 7,499,097,740.0000 ns | 41,307,956.8328 ns | 38,639,488.4507 ns | 7,477,484,200.0000 ns | 31,332.183 | 171.85 | - | - | - | 600 B | NA | +| PooledList_AddRangeWhenSourceIsPooledList | 958,132.3033 ns | 4,444.5508 ns | 3,939.9785 ns | 957,355.9570 ns | 0.816 | 0.00 | - | - | - | 57 B | 0.000 | +| RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList | 1,068,135.4492 ns | 15,036.9473 ns | 12,556.5320 ns | 1,063,337.2070 ns | 0.910 | 0.01 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsRecyclableArrayList | 1,057,253.7249 ns | 2,697.5224 ns | 2,391.2834 ns | 1,057,475.9766 ns | 0.901 | 0.00 | - | - | - | 169 B | 0.000 | -| List_IndexOf_FirstItems | 7,556,502,520.0000 ns | 61,900,425.0807 ns | 57,901,696.0262 ns | 7,528,699,800.0000 ns | 31,571.988 | 243.79 | - | - | - | 600 B | NA | -| PooledList_IndexOf_FirstItems | 7,480,317,953.8462 ns | 61,668,998.5528 ns | 51,496,406.7947 ns | 7,454,337,900.0000 ns | 31,252.779 | 224.30 | - | - | - | 600 B | NA | -| RecyclableList_IndexOf_FirstItems | 7,528,408,666.6667 ns | 53,978,194.9132 ns | 50,491,237.0123 ns | 7,524,296,300.0000 ns | 31,454.598 | 211.23 | - | - | - | 600 B | NA | -| RecyclableList_LongIndexOf_FirstItems | 7,478,265,225.0000 ns | 4,283,522.6817 ns | 3,344,292.7517 ns | 7,477,691,600.0000 ns | 31,243.094 | 27.20 | - | - | - | 600 B | NA | +| List_AddRange_WithCapacity | 2,022,247.3039 ns | 44,489.4467 ns | 129,071.9520 ns | 2,030,418.9453 ns | 1.767 | 0.06 | 39.0625 | 39.0625 | 39.0625 | 4000318 B | 1.000 | +| PooledList_AddRange_WithCapacity | 965,635.9570 ns | 11,740.2154 ns | 10,981.8048 ns | 968,593.2617 ns | 0.822 | 0.01 | - | - | - | 57 B | 0.000 | +| RecyclableArrayList_AddRange_WithCapacity | 1,044,438.5517 ns | 3,047.5353 ns | 2,544.8300 ns | 1,045,038.8672 ns | 0.890 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRange_WithCapacity | 1,028,953.5156 ns | 17,735.8499 ns | 14,810.2379 ns | 1,022,104.4922 ns | 0.876 | 0.01 | - | - | - | 169 B | 0.000 | + +| Array_Count | 0.0087 ns | 0.0110 ns | 0.0103 ns | 0.0024 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| List_Count | 1.2190 ns | 0.0046 ns | 0.0040 ns | 1.2173 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| PooledList_Count | 0.0037 ns | 0.0015 ns | 0.0012 ns | 0.0040 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableArrayList_Count | 1.2096 ns | 0.0009 ns | 0.0008 ns | 1.2092 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableList_Count | 0.0103 ns | 0.0008 ns | 0.0007 ns | 0.0102 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | + +| Array_LongCount | 0.0021 ns | 0.0005 ns | 0.0004 ns | 0.0022 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableList_LongCount | 0.0030 ns | 0.0004 ns | 0.0003 ns | 0.0031 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | + +| List_Contains_FirstItems | 1,855,720,661.5385 ns | 16,632,292.4881 ns | 13,888,717.5079 ns | 1,850,307,400.0000 ns | 1,580.480 | 10.24 | - | - | - | 600 B | 0.000 | +| PooledList_Contains_FirstItems | 1,854,542,814.2857 ns | 3,151,361.3032 ns | 2,793,599.7119 ns | 1,855,147,650.0000 ns | 1,579.345 | 5.84 | - | - | - | 600 B | 0.000 | +| RecyclableList_Contains_FirstItems | 1,842,715,553.8462 ns | 5,837,438.3043 ns | 4,874,525.3630 ns | 1,840,917,300.0000 ns | 1,569.420 | 6.23 | - | - | - | 600 B | 0.000 | + +| List_Contains_LastItems | 56,934,350,853.3333 ns | 257,718,254.2389 ns | 241,069,814.9149 ns | 56,884,611,800.0000 ns | 48,516.143 | 270.39 | - | - | - | 600 B | 0.000 | +| PooledList_Contains_LastItems | 57,262,565,633.3333 ns | 295,685,051.7358 ns | 276,583,980.8497 ns | 57,246,796,900.0000 ns | 48,801.131 | 237.64 | - | - | - | 600 B | 0.000 | +| RecyclableList_Contains_LastItems | 57,019,747,564.2857 ns | 264,241,719.1753 ns | 234,243,401.3552 ns | 57,059,345,900.0000 ns | 48,552.402 | 223.09 | - | - | - | 600 B | 0.000 | + +| Array_IndexOf_FirstItems | 1,874,810,260.0000 ns | 25,774,115.5271 ns | 24,109,123.6555 ns | 1,867,388,700.0000 ns | 1,597.521 | 22.54 | - | - | - | 600 B | 0.000 | +| List_IndexOf_FirstItems | 1,864,704,725.0000 ns | 10,982,470.8154 ns | 8,574,390.8164 ns | 1,866,253,200.0000 ns | 1,588.702 | 10.94 | - | - | - | 600 B | 0.000 | +| PooledList_IndexOf_FirstItems | 1,840,856,800.0000 ns | 4,046,589.8784 ns | 3,159,311.2037 ns | 1,839,944,050.0000 ns | 1,568.374 | 6.17 | - | - | - | 600 B | 0.000 | +| RecyclableArrayList_IndexOf_FirstItems | 1,832,934,950.0000 ns | 1,729,308.0131 ns | 1,350,129.9476 ns | 1,832,475,300.0000 ns | 1,561.625 | 5.52 | - | - | - | 600 B | 0.000 | +| RecyclableList_IndexOf_FirstItems | 1,844,986,792.3077 ns | 3,209,050.1952 ns | 2,679,702.2516 ns | 1,843,943,600.0000 ns | 1,571.359 | 6.49 | - | - | - | 600 B | 0.000 | +| RecyclableList_LongIndexOf_FirstItems | 1,878,933,500.0000 ns | 17,118,131.6171 ns | 16,012,310.9355 ns | 1,870,009,600.0000 ns | 1,601.616 | 14.82 | - | - | - | 600 B | 0.000 | + +| List_IndexOf_LastItems | 57,303,575,140.0000 ns | 297,612,744.3548 ns | 278,387,145.7216 ns | 57,391,576,000.0000 ns | 48,792.865 | 303.56 | - | - | - | 600 B | 0.000 | +| PooledList_IndexOf_LastItems | 57,032,399,106.6667 ns | 281,241,865.3817 ns | 263,073,815.3730 ns | 56,895,992,200.0000 ns | 48,566.622 | 322.88 | - | - | - | 600 B | 0.000 | +| RecyclableList_IndexOf_LastItems | 57,308,279,713.3333 ns | 272,718,905.6589 ns | 255,101,433.5602 ns | 57,297,645,400.0000 ns | 48,767.562 | 239.96 | - | - | - | 600 B | 0.000 | +| RecyclableList_LongIndexOf_LastItems | 57,198,323,460.0000 ns | 282,971,632.5816 ns | 264,691,840.6850 ns | 57,198,418,300.0000 ns | 48,669.107 | 304.11 | - | - | - | 600 B | 0.000 | + +| List_Remove_FirstItems | 5,023,030,012.5000 ns | 82,615,052.4052 ns | 107,422,903.9562 ns | 5,000,417,000.0000 ns | 4,295.171 | 116.37 | - | - | - | 4000656 B | 1.000 | +| PooledList_Remove_FirstItems | 4,750,601,721.4286 ns | 17,989,258.8487 ns | 15,947,009.4038 ns | 4,747,622,550.0000 ns | 4,043.997 | 21.26 | - | - | - | 992 B | 0.000 | +| RecyclableArrayList_Remove_FirstItems | 4,950,634,900.0000 ns | 33,761,094.2570 ns | 29,928,330.6849 ns | 4,936,519,050.0000 ns | 4,218.075 | 23.90 | - | - | - | 640 B | 0.000 | +| RecyclableList_Remove_FirstItems | 5,020,383,250.0000 ns | 100,209,734.5497 ns | 115,401,679.3276 ns | 5,029,163,750.0000 ns | 4,306.772 | 86.95 | - | - | - | 952 B | 0.000 | + +| List_Remove_LastItems | 56,593,869,333.3333 ns | 258,693,958.6041 ns | 241,982,489.3835 ns | 56,513,091,800.0000 ns | 48,189.227 | 272.02 | - | - | - | 4000656 B | 1.000 | +| PooledList_Remove_LastItems | 56,465,443,542.8571 ns | 99,443,506.1766 ns | 88,154,078.0245 ns | 56,458,364,050.0000 ns | 48,092.866 | 216.09 | - | - | - | 992 B | 0.000 | +| RecyclableArrayList_Remove_LastItems | 57,432,405,146.6667 ns | 264,062,625.8226 ns | 247,004,344.0307 ns | 57,409,508,000.0000 ns | 48,952.707 | 234.83 | - | - | - | 640 B | 0.000 | +| RecyclableList_Remove_LastItems | 57,077,678,728.5714 ns | 210,992,675.8689 ns | 187,039,511.4398 ns | 57,038,290,150.0000 ns | 48,614.503 | 242.79 | - | - | - | 952 B | 0.000 | + +| List_RemoveAt_FirstItems | 4,517,669,893.3333 ns | 77,360,576.7460 ns | 72,363,131.4861 ns | 4,546,302,700.0000 ns | 3,850.590 | 62.34 | - | - | - | 4000656 B | 1.000 | +| PooledList_RemoveAt_FirstItems | 4,439,417,700.0000 ns | 40,915,112.8948 ns | 38,272,021.9874 ns | 4,418,366,400.0000 ns | 3,779.751 | 38.92 | - | - | - | 992 B | 0.000 | +| RecyclableArrayList_RemoveAt_FirstItems | 4,558,102,892.8571 ns | 25,076,973.8515 ns | 22,230,084.1404 ns | 4,559,174,800.0000 ns | 3,882.107 | 16.61 | - | - | - | 640 B | 0.000 | +| RecyclableList_RemoveAt_FirstItems | 4,617,033,766.6667 ns | 85,061,197.3076 ns | 79,566,296.7373 ns | 4,632,994,800.0000 ns | 3,928.768 | 72.17 | - | - | - | 952 B | 0.000 | + +| List_RemoveAt_LastItems | 2,005,211.3281 ns | 40,078.0351 ns | 84,538.1787 ns | 2,010,579.1016 ns | 1.725 | 0.08 | 42.9688 | 42.9688 | 42.9688 | 4000306 B | 1.000 | +| PooledList_RemoveAt_LastItems | 1,047,436.1654 ns | 5,655.2027 ns | 5,289.8801 ns | 1,044,690.9180 ns | 0.892 | 0.01 | - | - | - | 58 B | 0.000 | +| RecyclableArrayList_RemoveAt_LastItems | 1,256,380.8969 ns | 9,142.3285 ns | 7,634.2584 ns | 1,254,354.9805 ns | 1.070 | 0.01 | - | - | - | 41 B | 0.000 | +| RecyclableList_RemoveAt_LastItems | 1,621,193.5826 ns | 20,030.6198 ns | 17,756.6227 ns | 1,620,789.8438 ns | 1.379 | 0.02 | - | - | - | 353 B | 0.000 | From 22060c4390f5782b33009228f683ff0e854729e7 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 12 Apr 2023 23:57:17 +0200 Subject: [PATCH 216/305] Tiny cleanup --- Recyclable.Collections/RecyclableList.cs | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 31eb0f2..8ed94a0 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -330,6 +330,23 @@ namespace Recyclable.Collections return (long)newMemoryBlocks.Length << minBlockSizePow2Shift; } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockPoolingRequired = true; + list._blockArrayPool = blockArrayPool ?? RecyclableArrayPool.Shared(blockSize); + } + else + { + list._blockPoolingRequired = false; + list._blockArrayPool = RecyclableArrayPool.Null; + } + } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public static long EnsureCapacity(RecyclableList list, long requestedCapacity) { @@ -349,23 +366,6 @@ namespace Recyclable.Collections return newCapacity; } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) - { - list._blockSize = blockSize; - list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - list._blockPoolingRequired = true; - list._blockArrayPool = blockArrayPool ?? RecyclableArrayPool.Shared(blockSize); - } - else - { - list._blockPoolingRequired = false; - list._blockArrayPool = RecyclableArrayPool.Null; - } - } - [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) From ed11e42b480853d25d8eafc9e0fef13802fc17d2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 13 Apr 2023 03:54:01 +0200 Subject: [PATCH 217/305] BoolOrComparePoc benchmarks added to confirm findings from RecyclableList.EnsureCapacity runs. --- .../POC/BoolOrComparePoc.Benchmarks.cs | 50 +++++++++++++++++++ Recyclable.Collections.Benchmarks/Program.cs | 1 + 2 files changed, 51 insertions(+) create mode 100644 Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs diff --git a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs new file mode 100644 index 0000000..61b0c8d --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs @@ -0,0 +1,50 @@ +using BenchmarkDotNet.Attributes; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class BoolOrComparePocBenchmarks + { + public static void Run() + { + var benchmark = new BoolOrComparePocBenchmarks(); + benchmark.Bool(); + benchmark.Compare(); + } + + private static void DoNothing(T result, [CallerMemberName] string? callerName = null) + { + //Console.WriteLine($"{callerName}: result = {result}"); + } + + private bool _testBool = true; + private int _blockSize = 8; + + [Benchmark(Baseline = false)] + public void Bool() + { + if (_testBool) + { + DoNothing(_testBool); + } + else + { + DoNothing(_testBool); + } + } + + [Benchmark(Baseline = false)] + public void Compare() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + DoNothing(_testBool); + } + else + { + DoNothing(_testBool); + } + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index d7a156f..4bc71a0 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -42,6 +42,7 @@ internal class Program _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); } private static void Main(string[] args) From 60494c321950d20e0d6a4f8a898f1bf228381d57 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 16 Apr 2023 15:32:03 +0200 Subject: [PATCH 218/305] RecyclableList.Resize performance improvements. --- Recyclable.Collections/RecyclableList.cs | 53 ++++++++++++------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 8ed94a0..0900eaf 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { public class RecyclableList : IDisposable, IList - { + { private const int ItemNotFoundIndex = -1; private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); @@ -14,13 +14,13 @@ namespace Recyclable.Collections private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; private static readonly T[] _emptyBlockArray = new T[0]; - //private MemoryBlockInfo _memoryBlockInfo; private int _blockSize; private byte _blockSizePow2Shift; private int _blockSizeMinus1; private bool _blockPoolingRequired; private int _nextItemBlockIndex; private int _nextItemIndex; + private int _reservedBlockCount; protected static readonly bool NeedsClearing = !typeof(T).IsValueType; @@ -47,7 +47,7 @@ namespace Recyclable.Collections set => _longCount = value; } - public int ReservedBlockCount => checked((int)(_capacity >> _blockSizePow2Shift) + ((_capacity & _blockSizeMinus1) > 0 ? 1 : 0)); + public int ReservedBlockCount => _reservedBlockCount; public int BlockSize => _blockSize; public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -104,7 +104,7 @@ namespace Recyclable.Collections { long i, copied = _longCount; int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; - + using IEnumerator enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) { @@ -122,7 +122,7 @@ namespace Recyclable.Collections if (copied + growByCount > capacity) { capacity = EnsureCapacity(this, capacity + growByCount); - memoryBlocksCount = _memoryBlocks.Length; + memoryBlocksCount = _reservedBlockCount; if (blockSize != _blockSize) { blockSize = _blockSize; @@ -190,7 +190,7 @@ namespace Recyclable.Collections var memoryBlocksCount = ReservedBlockCount; memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); foreach (var item in source) { blockArraySpan[targetItemIdx++] = item; @@ -256,10 +256,9 @@ namespace Recyclable.Collections /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) - //protected static long Resize(RecyclableList list, in MemoryBlockInfo blockInfo, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; - int sourceBlockCount = checked((int)(list._capacity >> minBlockSizePow2Shift)); + int sourceBlockCount = list._reservedBlockCount; int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; Span memoryBlocksSpan; @@ -267,29 +266,28 @@ namespace Recyclable.Collections // Release excessive blocks if we're downsizing if (requiredBlockCount < sourceBlockCount && list._blockPoolingRequired) { - memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, sourceBlockCount - requiredBlockCount); + memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, requiredBlockCount - sourceBlockCount); blockIndex = 0; while (blockIndex < memoryBlocksSpan.Length) { - blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + blockArrayPool.Return(memoryBlocksSpan[blockIndex++], NeedsClearing); } } ArrayPool memoryBlocksPool = list._memoryBlocksPool; // Allocate new memory block for all arrays - T[][]? newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength - ? new T[requiredBlockCount][] - : memoryBlocksPool.Rent(requiredBlockCount); + bool memoryBlockPoolingRequired = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength; + T[][] newMemoryBlocks = memoryBlockPoolingRequired ? memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; // Copy arrays from the old memory block for all arrays if (sourceBlockCount > 0) { - Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + Array.Copy(list._memoryBlocks, newMemoryBlocks, sourceBlockCount); // We can now return the old memory block for all arrays itself if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) { - memoryBlocksPool.Return(list._memoryBlocks!, true); + memoryBlocksPool.Return(list._memoryBlocks, true); } } @@ -298,7 +296,7 @@ namespace Recyclable.Collections // Allocate arrays for any new blocks if (requiredBlockCount > sourceBlockCount) { - memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, requiredBlockCount - sourceBlockCount); if (list._blockPoolingRequired) { if (sourceBlockCount == 0) @@ -327,7 +325,8 @@ namespace Recyclable.Collections } } - return (long)newMemoryBlocks.Length << minBlockSizePow2Shift; + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -378,7 +377,7 @@ namespace Recyclable.Collections minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); - if (_memoryBlocks!.Length > 0 && _blockSize != _memoryBlocks[0].Length) + if (_blockSize != _memoryBlocks[0].Length && _capacity > 0) { SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); _blockSizeMinus1 = _blockSize - 1; @@ -406,7 +405,7 @@ namespace Recyclable.Collections minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); - if (_memoryBlocks!.Length > 0 && minBlockSize != _memoryBlocks[0].Length) + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) { SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); _blockSizeMinus1 = _blockSize - 1; @@ -473,7 +472,7 @@ namespace Recyclable.Collections int blockSize = _blockSize, targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; + memoryBlockCount = _reservedBlockCount; Span itemsSpan = new(items); Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); @@ -516,7 +515,7 @@ namespace Recyclable.Collections int blockSize = _blockSize, targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; + memoryBlockCount = _reservedBlockCount; Span itemsSpan = new(items.AsArray(), 0, items.Count); Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); @@ -560,9 +559,9 @@ namespace Recyclable.Collections targetBlockIndex = _nextItemBlockIndex, toCopy; - Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); - Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); long copiedCount = 0; while (copiedCount < itemsCount) @@ -630,7 +629,7 @@ namespace Recyclable.Collections itemsCount = items.Count, copied = Math.Min(blockSize - _nextItemIndex, itemsCount); - Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); if (_nextItemIndex + copied == blockSize) { @@ -673,7 +672,7 @@ namespace Recyclable.Collections int blockSize = _blockSize, targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; + memoryBlockCount = _reservedBlockCount; // We're better off to temporarily copy it to a fixed array, // than copying them item by item. We may run into OOMs, though. @@ -757,16 +756,18 @@ namespace Recyclable.Collections { if (_blockPoolingRequired) { - Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); int memoryBlocksCount = memoryBlocksSpan.Length; ArrayPool blockArrayPool = _blockArrayPool; for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) { blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + memoryBlocksSpan[toRemoveIdx] = _emptyBlockArray; } } _capacity = 0; + _reservedBlockCount = 0; _nextItemBlockIndex = 0; _nextItemIndex = 0; _longCount = 0; From 4600c4521e5ac420093bb6d52a061e277ff5d773 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 17 Apr 2023 00:11:23 +0200 Subject: [PATCH 219/305] RecyclableList.Resize performance improvements. --- Recyclable.Collections/RecyclableList.cs | 95 ++++++++++++++---------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 0900eaf..7b7c433 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -253,6 +253,10 @@ namespace Recyclable.Collections /// /// This method checks for integral overflow. /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) @@ -263,65 +267,65 @@ namespace Recyclable.Collections int blockIndex; Span memoryBlocksSpan; - // Release excessive blocks if we're downsizing - if (requiredBlockCount < sourceBlockCount && list._blockPoolingRequired) + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) { - memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, requiredBlockCount - sourceBlockCount); - blockIndex = 0; - while (blockIndex < memoryBlocksSpan.Length) + // Allocate new memory block for all arrays + T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) { - blockArrayPool.Return(memoryBlocksSpan[blockIndex++], NeedsClearing); + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } } + + list._memoryBlocks = newMemoryBlocks; } - ArrayPool memoryBlocksPool = list._memoryBlocksPool; - - // Allocate new memory block for all arrays - bool memoryBlockPoolingRequired = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength; - T[][] newMemoryBlocks = memoryBlockPoolingRequired ? memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; - - // Copy arrays from the old memory block for all arrays - if (sourceBlockCount > 0) - { - Array.Copy(list._memoryBlocks, newMemoryBlocks, sourceBlockCount); - // We can now return the old memory block for all arrays itself - if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) - { - memoryBlocksPool.Return(list._memoryBlocks, true); - } - } - - list._memoryBlocks = newMemoryBlocks; - // Allocate arrays for any new blocks if (requiredBlockCount > sourceBlockCount) { - memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, requiredBlockCount - sourceBlockCount); + memoryBlocksSpan = new(list._memoryBlocks!, sourceBlockCount, requiredBlockCount - sourceBlockCount); if (list._blockPoolingRequired) { if (sourceBlockCount == 0) { - memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); - minBlockSize = memoryBlocksSpan[0].Length; - blockIndex = 1; - } - else - { - blockIndex = 0; + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); } - while (blockIndex < memoryBlocksSpan.Length) + blockIndex = 0; + do { - memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); - } + memoryBlocksSpan[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < memoryBlocksSpan.Length) + { + blockIndex++; + } + else + { + break; + } + } while (true); } else { blockIndex = 0; - while (blockIndex < memoryBlocksSpan.Length) + do { - memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; - } + memoryBlocksSpan[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < memoryBlocksSpan.Length) + { + blockIndex++; + } + else + { + break; + } + } while (true); } } @@ -329,6 +333,15 @@ namespace Recyclable.Collections return (long)requiredBlockCount << minBlockSizePow2Shift; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) { @@ -347,7 +360,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long EnsureCapacity(RecyclableList list, long requestedCapacity) + protected static long EnsureCapacity(RecyclableList list, long requestedCapacity) { long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) @@ -377,7 +390,7 @@ namespace Recyclable.Collections minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); - if (_blockSize != _memoryBlocks[0].Length && _capacity > 0) + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) { SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); _blockSizeMinus1 = _blockSize - 1; From e5c0b2a231f7bfe6f41b2879d8aee729e952d6c4 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 17 Apr 2023 01:48:02 +0200 Subject: [PATCH 220/305] RecyclableList._blockPoolingRequired removed for better performance. --- Recyclable.Collections/RecyclableList.cs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 7b7c433..1e65d0a 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -17,7 +17,6 @@ namespace Recyclable.Collections private int _blockSize; private byte _blockSizePow2Shift; private int _blockSizeMinus1; - private bool _blockPoolingRequired; private int _nextItemBlockIndex; private int _nextItemIndex; private int _reservedBlockCount; @@ -290,7 +289,7 @@ namespace Recyclable.Collections if (requiredBlockCount > sourceBlockCount) { memoryBlocksSpan = new(list._memoryBlocks!, sourceBlockCount, requiredBlockCount - sourceBlockCount); - if (list._blockPoolingRequired) + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { if (sourceBlockCount == 0) { @@ -347,16 +346,9 @@ namespace Recyclable.Collections { list._blockSize = blockSize; list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - list._blockPoolingRequired = true; - list._blockArrayPool = blockArrayPool ?? RecyclableArrayPool.Shared(blockSize); - } - else - { - list._blockPoolingRequired = false; - list._blockArrayPool = RecyclableArrayPool.Null; - } + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -767,7 +759,7 @@ namespace Recyclable.Collections public void Clear() { - if (_blockPoolingRequired) + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); int memoryBlocksCount = memoryBlocksSpan.Length; @@ -869,7 +861,7 @@ namespace Recyclable.Collections public void RemoveBlock(int index) { - if (_blockPoolingRequired) + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); } From 5ac21f5d18aea17334c1a899fa5a88d3ef0f074e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 18 Apr 2023 00:20:07 +0200 Subject: [PATCH 221/305] RecyclableList.Resize & .Clear performance improvements. --- Recyclable.Collections/RecyclableList.cs | 37 +++++++++++++++--------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 1e65d0a..613be1d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -264,12 +264,12 @@ namespace Recyclable.Collections int sourceBlockCount = list._reservedBlockCount; int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; - Span memoryBlocksSpan; + T[][] newMemoryBlocks; if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) { // Allocate new memory block for all arrays - T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; // Copy arrays from the old memory block for all arrays if (sourceBlockCount > 0) @@ -284,11 +284,14 @@ namespace Recyclable.Collections list._memoryBlocks = newMemoryBlocks; } + else + { + newMemoryBlocks = list._memoryBlocks!; + } // Allocate arrays for any new blocks if (requiredBlockCount > sourceBlockCount) { - memoryBlocksSpan = new(list._memoryBlocks!, sourceBlockCount, requiredBlockCount - sourceBlockCount); if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { if (sourceBlockCount == 0) @@ -296,11 +299,11 @@ namespace Recyclable.Collections minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); } - blockIndex = 0; + blockIndex = sourceBlockCount; do { - memoryBlocksSpan[blockIndex] = blockArrayPool.Rent(minBlockSize); - if (blockIndex + 1 < memoryBlocksSpan.Length) + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) { blockIndex++; } @@ -312,11 +315,11 @@ namespace Recyclable.Collections } else { - blockIndex = 0; + blockIndex = sourceBlockCount; do { - memoryBlocksSpan[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < memoryBlocksSpan.Length) + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) { blockIndex++; } @@ -759,18 +762,24 @@ namespace Recyclable.Collections public void Clear() { + if (_longCount == 0) + { + return; + } + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { - Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); - int memoryBlocksCount = memoryBlocksSpan.Length; + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; ArrayPool blockArrayPool = _blockArrayPool; - for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) { - blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); - memoryBlocksSpan[toRemoveIdx] = _emptyBlockArray; + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); } } + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); _capacity = 0; _reservedBlockCount = 0; _nextItemBlockIndex = 0; From 79cb86fbce42a58ff064cc1f35febd68f3ac9fac Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 18 Apr 2023 00:21:01 +0200 Subject: [PATCH 222/305] Added missing cell clear to release the requested array, if needed. --- Recyclable.Collections/RecyclableList.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 613be1d..3e477dc 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -873,6 +873,7 @@ namespace Recyclable.Collections if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; } else { From 1bdf0404e40caf02ba8a21cf3545ee1ca84b93b3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 25 Apr 2023 01:42:56 +0200 Subject: [PATCH 223/305] RecyclableList._blockSizePow2Shift renamed to _blockSizePow2BitShift. Public property added to help avoiding recalculations. --- Recyclable.Collections/RecyclableList.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 3e477dc..ea9f967 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -15,7 +15,7 @@ namespace Recyclable.Collections private static readonly T[] _emptyBlockArray = new T[0]; private int _blockSize; - private byte _blockSizePow2Shift; + private byte _blockSizePow2BitShift; private int _blockSizeMinus1; private int _nextItemBlockIndex; private int _nextItemIndex; @@ -48,6 +48,8 @@ namespace Recyclable.Collections public int ReservedBlockCount => _reservedBlockCount; public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -57,10 +59,10 @@ namespace Recyclable.Collections T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; int blockSize = list._blockSize; - int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2Shift)); + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2BitShift)); int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); - int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); + int targetBlockIndex = checked((int)(index >> list._blockSizePow2BitShift)); int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); int lastTakenBlockIndex = list.LastBlockWithData; @@ -348,7 +350,7 @@ namespace Recyclable.Collections protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) { list._blockSize = blockSize; - list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) : RecyclableArrayPool.Null; @@ -362,7 +364,7 @@ namespace Recyclable.Collections : requestedCapacity; int blockSize = list._blockSize; - newCapacity = Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) { SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); @@ -384,7 +386,7 @@ namespace Recyclable.Collections { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) { SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); @@ -412,7 +414,7 @@ namespace Recyclable.Collections { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) { SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); @@ -435,14 +437,14 @@ namespace Recyclable.Collections public T this[long index] { - get => _memoryBlocks[index >> _blockSizePow2Shift][index & _blockSizeMinus1]; - set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & _blockSizeMinus1))] = value; + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; } public T this[int index] { - get => _memoryBlocks[index >> _blockSizePow2Shift][index & _blockSizeMinus1]; - set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & _blockSizeMinus1] = value; + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; } public T[][] MemoryBlocks { get => _memoryBlocks; } From aa78a2ba40a8babfe477bcfd1c48d46067b8ac7d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 25 Apr 2023 01:51:22 +0200 Subject: [PATCH 224/305] Dirty RecyclableList.IndexOf parallelization implemented. --- Recyclable.Collections/ExceptionExtensions.cs | 12 + Recyclable.Collections/ItemRangesIterator.cs | 247 ++++++++++++++++++ .../Recyclable.Collections.csproj | 1 + Recyclable.Collections/RecyclableList.cs | 181 ++++++++++++- .../ItemRangesIteratorTests.cs | 133 ++++++++++ 5 files changed, 566 insertions(+), 8 deletions(-) create mode 100644 Recyclable.Collections/ExceptionExtensions.cs create mode 100644 Recyclable.Collections/ItemRangesIterator.cs create mode 100644 Recyclable.CollectionsTests/ItemRangesIteratorTests.cs diff --git a/Recyclable.Collections/ExceptionExtensions.cs b/Recyclable.Collections/ExceptionExtensions.cs new file mode 100644 index 0000000..43167e4 --- /dev/null +++ b/Recyclable.Collections/ExceptionExtensions.cs @@ -0,0 +1,12 @@ +using System.Runtime.ExceptionServices; + +namespace Recyclable.Collections +{ + public static class ExceptionExtensions + { + public static void CaptureAndRethrow(this Exception ex) + { + ExceptionDispatchInfo.Capture(ex).Throw(); + } + } +} diff --git a/Recyclable.Collections/ItemRangesIterator.cs b/Recyclable.Collections/ItemRangesIterator.cs new file mode 100644 index 0000000..685f0b4 --- /dev/null +++ b/Recyclable.Collections/ItemRangesIterator.cs @@ -0,0 +1,247 @@ +using Microsoft.Extensions.ObjectPool; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections +{ + public sealed class ItemRange : IDisposable + { + private static readonly ObjectPool _defaultPool = ItemRangePool.Shared; + + public int BlockIndex; + public int StartingItemIndex; + public long ItemsToSearchCount; + private ObjectPool? _assignedPool; + + public ItemRange() + { + + } + + public ItemRange(int blockIndex, int startingItemIndex, long itemsToSearchCount) + { + BlockIndex = blockIndex; + ItemsToSearchCount = itemsToSearchCount; + StartingItemIndex = startingItemIndex; + } + + public void Dispose() + { + if (_assignedPool != null) + { + var assignedPool = _assignedPool; + _assignedPool = null; + assignedPool.Return(this); + } + + GC.SuppressFinalize(this); + } + + public static ItemRange Create(int blockIndex, int startingItemIndex, long itemsToSearchCount) + { + var itemRange = _defaultPool.Get(); + itemRange.BlockIndex = blockIndex; + itemRange.ItemsToSearchCount = itemsToSearchCount; + itemRange.StartingItemIndex = startingItemIndex; + return itemRange; + } + } + + public static class ItemRangePool + { + [ThreadStatic] + private static ObjectPool _shared; + public static ObjectPool Shared => _shared; + + static ItemRangePool() + { + _shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + } + } + + public sealed class BarrierSlimmer : IDisposable + { + + private static readonly ObjectPool _defaultPool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + private ObjectPool? _assignedPool; + private readonly object _lock = new(); + + public bool HasParticipants; + public int Participants; + + public void AddParticipant() + { + Monitor.Enter(_lock); + if (++Participants == 1) + { + HasParticipants = true; + } + + Monitor.Exit(_lock); + } + + public void RemoveParticipant() + { + Monitor.Enter(_lock); + if (Participants == 0) + { + Monitor.Exit(_lock); + ThrowNoMoreParticipants(); + } + + if (--Participants == 0) + { + HasParticipants = false; + } + + Monitor.Exit(_lock); + } + + private static void ThrowNoMoreParticipants() + { + throw new InvalidOperationException("There are no more participants. The operation is not allowed"); + } + + public BarrierSlimmer() + { + } + + public BarrierSlimmer(int participants) + { + Participants = participants; + HasParticipants = participants > 0; + } + + public static BarrierSlimmer Create(int participants) + { + var result = _defaultPool.Get(); + result.Participants = participants; + result.HasParticipants = participants > 0; + return result; + } + + public void Dispose() + { + var assignedPool = _assignedPool; + _assignedPool = null; + assignedPool?.Return(this); + + GC.SuppressFinalize(this); + } + + public void SignalAndWait() + { + RemoveParticipant(); + + if (!HasParticipants) + { + return; + } + + if (!HasParticipants) + { + return; + } + + if (!HasParticipants) + { + return; + } + + var waitSpinner = new SpinWait(); + while (HasParticipants) + { + waitSpinner.SpinOnce(); + } + } + } + + public sealed class ManualResetEventSlimmer : IDisposable + { + private static readonly ObjectPool _defaultPool = ManualResetEventSlimmerPool.Shared; + + private bool _isSet; + public bool IsSet => _isSet; + private ObjectPool? _assignedPool; + + public ManualResetEventSlimmer(bool initialState) + { + _isSet = initialState; + } + + public ManualResetEventSlimmer() + { + + } + + public void Set() + { + _isSet = true; + } + + public void Reset() + { + _isSet = false; + } + + public void Dispose() + { + var assignedPool = _assignedPool; + _assignedPool = null; + assignedPool?.Return(this); + + GC.SuppressFinalize(this); + } + + public static ManualResetEventSlimmer Create(bool initialState) + { + ManualResetEventSlimmer result = _defaultPool.Get(); + result._assignedPool = _defaultPool; + result._isSet = initialState; + return result; + } + } + + public static class ManualResetEventSlimmerPool + { + [ThreadStatic] + private static ObjectPool _shared; + public static ObjectPool Shared => _shared; + + static ManualResetEventSlimmerPool() + { + _shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + } + } + + public static class ItemRangesIterator + { + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] + public static void Iterate(int blockIndex, int blockSize, byte blockSizePow2BitShift, long itemsCount, long step, Func action) + { + int itemIndex = 0; + ItemRange itemRange; + while (itemsCount > step) + { + itemRange = ItemRange.Create(blockIndex, itemIndex, step); + if (!action(itemRange)) + { + break; + } + + itemsCount -= step; + //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); + blockIndex += (int)((itemIndex + step) >> blockSizePow2BitShift); + itemIndex = (int)((itemIndex + step) & (blockSize - 1)); + //itemIndex += step; + //while (itemIndex >= blockSize) + //{ + // blockIndex++; + // itemIndex -= blockSize; + //} + } + + itemRange = ItemRange.Create(blockIndex, itemIndex, itemsCount); + _ = action(itemRange); + } + } +} diff --git a/Recyclable.Collections/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj index 50d3f17..f920bca 100644 --- a/Recyclable.Collections/Recyclable.Collections.csproj +++ b/Recyclable.Collections/Recyclable.Collections.csproj @@ -33,6 +33,7 @@ + diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index ea9f967..0a00087 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -8,6 +8,7 @@ namespace Recyclable.Collections public class RecyclableList : IDisposable, IList { private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); @@ -38,12 +39,17 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + protected long _longCountIndexOfStep; protected long _longCount; public long LongCount { get => _longCount; - set => _longCount = value; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } } public int ReservedBlockCount => _reservedBlockCount; @@ -152,6 +158,7 @@ namespace Recyclable.Collections } _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); return; } @@ -209,11 +216,156 @@ namespace Recyclable.Collections } _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; return; } + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private static long DoLongIndexOfParallel(in RecyclableList list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int startingItemIndex = itemRange.StartingItemIndex; + T[][] memoryBlocks = list._memoryBlocks; + int index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, startingItemIndex, (int)Math.Min(blockSize - startingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - startingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndex; + } + + //T itemToFind = itemRange.ItemToFind; + //int lastBlockWithData = itemRange.LastBlockWithData; + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData + ? -1 + : blockIndex == list._nextItemBlockIndex + ? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + : Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) + //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); + + return index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private long DoIndexOfParallel(T item) + { + using var itemFoundSignal = ManualResetEventSlimmer.Create(false); + using var allDoneSignal = new Barrier(1); + long foundItemIndex = -1L; + Exception? ex = null; + + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + ItemRangesIterator.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); + allDoneSignal.SignalAndWait(); + if (ex == null) + { + return foundItemIndex; + } + + ex.CaptureAndRethrow(); + return ItemNotFoundIndex; + + //int blockIndex = 0; + //int itemIndex = 0; + //long itemsCount = _longCount; + //while (itemsCount > step) + //{ + // if (!LocalIndexOf(new(blockIndex, itemIndex, step))) + // { + // break; + // } + + // itemsCount -= step; + // //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); + // blockIndex += (int)((itemIndex + step) >> _blockSizePow2BitShift); + // itemIndex = (int)((itemIndex + step) & _blockSizeMinus1); + // //itemIndex += step; + // //while (itemIndex >= blockSize) + // //{ + // // blockIndex++; + // // itemIndex -= blockSize; + // //} + //} + + //_ = LocalIndexOf(new(blockIndex, itemIndex, itemsCount)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + bool LocalIndexOf(ItemRange itemRange) + { + _ = allDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal); + if (index >= 0) + { + itemFoundSignal.Set(); + foundItemIndex = index; + } + } + catch (Exception e) + { + //_ = Interlocked.Exchange(ref ex, e); + lock (allDoneSignal) + { + ex = e; + } + } + finally + { + itemRange.Dispose(); + allDoneSignal.RemoveParticipant(); + } + }); + //.ContinueWith(x => + //{ + // if (x.Exception != null) + // { + // lock (allDoneSignal) + // { + // ex = x.Exception; + // } + // } + //}); + + return !itemFoundSignal.IsSet; + } + } + [MethodImpl(MethodImplOptions.AggressiveOptimization)] protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) { @@ -447,7 +599,7 @@ namespace Recyclable.Collections set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; } - public T[][] MemoryBlocks { get => _memoryBlocks; } + public T[][] AsArray { get => _memoryBlocks; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) @@ -465,6 +617,7 @@ namespace Recyclable.Collections } _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); } public void AddRange(in T[] items) @@ -507,6 +660,7 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; } @@ -546,6 +700,7 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; } @@ -619,6 +774,7 @@ namespace Recyclable.Collections } _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); } public void AddRange(List items) @@ -665,6 +821,7 @@ namespace Recyclable.Collections _nextItemBlockIndex = targetBlockIndex; _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); } public void AddRange(IList items) @@ -709,6 +866,7 @@ namespace Recyclable.Collections itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; _nextItemIndex = itemsSpan.Length; } @@ -787,6 +945,7 @@ namespace Recyclable.Collections _nextItemBlockIndex = 0; _nextItemIndex = 0; _longCount = 0; + _longCountIndexOfStep = 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -822,18 +981,18 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex - : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) - ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) - : checked((int)DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex)); + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOfParallel(item)); public void Insert(int index, T item) => throw new NotSupportedException(); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public long LongIndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex - : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) - ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) - : DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex); + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool Remove(T item) @@ -842,6 +1001,8 @@ namespace Recyclable.Collections if (index >= 0) { _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + if (index < _longCount) { CopyItems(this, index); @@ -902,6 +1063,8 @@ namespace Recyclable.Collections } _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + if (index < _longCount) { CopyItems(this, index); @@ -934,6 +1097,8 @@ namespace Recyclable.Collections } _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + if (index < _longCount) { CopyItems(this, index); diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs new file mode 100644 index 0000000..d5fa9d3 --- /dev/null +++ b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs @@ -0,0 +1,133 @@ +using FluentAssertions; +using Recyclable.Collections; +using System.Numerics; + +namespace Recyclable.CollectionsTests +{ + public class ItemRangesIteratorTests + { + [Theory] + [MemberData(nameof(TestCases))] + public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, int StartingItemIndex, long ItemsCount)> expected) + { + // Prepare + byte blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + var actual = new List<(int BlockIndex, int StartingItemIndex, long ItemsCount)>(); + + // Act + //ItemRangesIterator.Iterate(startingBlockIndex, blockSize, blockSizePow2BitShift, itemsCount, step, (searchInfo) => + //{ + // actual.Add((searchInfo.BlockIndex, searchInfo.StartingItemIndex, searchInfo.ItemsToSearchCount)); + // return true; + //}); + + // Validate + //_ = actual.Should().HaveCount(expected.Count()).And.ContainInConsecutiveOrder(expected); + } + + public static IEnumerable TestCases => new[] + { + new object[] + { + 0, 128, 333, (long)(333 * 0.329), new(int BlockIndex, int StartingItemIndex, long ItemsCount)[] + { + (0, 0, 109), + (0, 109, 109), + (1, 90, 109), + (2, 71, 6) + } + }, + + new object[] + { + 0, 2, 10, 2, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + { + (0, 0, 2), + (1, 0, 2), + (2, 0, 2), + (3, 0, 2), + (4, 0, 2), + } + }, + + new object[] + { + 0, 2, 10, 3, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + { + (0, 0, 3), + (1, 1, 3), + (3, 0, 3), + (4, 1, 1), + } + }, + + new object[] + { + 0, 1, 10, 3, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + { + (0, 0, 3), + (3, 0, 3), + (6, 0, 3), + (9, 0, 1), + } + }, + + new object[] + { + 0, 2, 10, 4, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + { + (0, 0, 4), + (2, 0, 4), + (4, 0, 2), + } + }, + + new object[] + { + 0, 1, 10, 2, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + { + (0, 0, 2), + (2, 0, 2), + (4, 0, 2), + (6, 0, 2), + (8, 0, 2), + } + }, + + new object[] + { + 0, 2, 10, 1, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + { + (0, 0, 1), + (0, 1, 1), + (1, 0, 1), + (1, 1, 1), + (2, 0, 1), + (2, 1, 1), + (3, 0, 1), + (3, 1, 1), + (4, 0, 1), + (4, 1, 1), + } + }, + + new object[] + { + 0, 1, 10, 1, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + { + (0, 0, 1), + (1, 0, 1), + (2, 0, 1), + (3, 0, 1), + (4, 0, 1), + (5, 0, 1), + (6, 0, 1), + (7, 0, 1), + (8, 0, 1), + (9, 0, 1), + } + }, + + }; + } +} From 49682fd73395e78962128f4d26654ac1708f91bd Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 25 Apr 2023 01:55:39 +0200 Subject: [PATCH 225/305] Tiny cleanup --- .../Benchmarks/AddRange_WithCapacity.cs | 4 ++-- Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs index 0d6bcc8..1fc85d3 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs @@ -19,7 +19,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + using var list = new PooledList(capacity: checked((int)dataCount), ClearMode.Always); list.AddRange(data); } @@ -37,7 +37,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index 8e9cae5..3821750 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -63,7 +63,6 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_GetItem_AsSpan() { var data = _testRecyclableList; - var dataCount = TestObjectCount; int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; @@ -92,7 +91,6 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_GetItem_AsArray() { var data = _testRecyclableList; - var dataCount = TestObjectCount; int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; From abfe4765734a54fecd53406711ea24812eb658ad Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 25 Apr 2023 02:03:13 +0200 Subject: [PATCH 226/305] RecyclableList.MemoryBlocks renamed to AsArray. --- Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs | 6 +++--- Recyclable.Collections/RecyclableArrayList.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index 3821750..2b99bf2 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; - var memoryBlocks = new Span(data.MemoryBlocks, 0, data.LastBlockWithData + 1); + var memoryBlocks = new Span(data.AsArray, 0, data.LastBlockWithData + 1); for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) { var memoryBlock = new Span(memoryBlocks[blockIndex], 0, blockSize); @@ -94,7 +94,7 @@ namespace Recyclable.Collections.Benchmarks int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; - var memoryBlocks = data.MemoryBlocks; + var memoryBlocks = data.AsArray; for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) { var memoryBlock = memoryBlocks[blockIndex]; @@ -141,7 +141,7 @@ namespace Recyclable.Collections.Benchmarks int nextItemBlockIndex = data.NextItemBlockIndex; int itemIndex = 0; int blockIndex = 0; - var memoryBlocks = data.MemoryBlocks; + var memoryBlocks = data.AsArray; var memoryBlock = memoryBlocks[0]; while (blockIndex < nextItemBlockIndex) { diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 4c620ff..155e7a1 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -272,19 +272,19 @@ namespace Recyclable.Collections for (blockIndex = 0; blockIndex <= lastBlockIndex; blockIndex++) { - itemsSpan = new(items.MemoryBlocks[blockIndex], 0, sourceBlockSize); + itemsSpan = new(items.AsArray[blockIndex], 0, sourceBlockSize); itemsSpan.CopyTo(targetSpan); targetSpan = targetSpan[sourceBlockSize..]; } if (blockIndex == 0) { - itemsSpan = new(items.MemoryBlocks[blockIndex], 0, (int)sourceItemsCount); + itemsSpan = new(items.AsArray[blockIndex], 0, (int)sourceItemsCount); itemsSpan.CopyTo(targetSpan); } else if (blockIndex <= items.LastTakenBlockIndex) { - itemsSpan = new(items.MemoryBlocks[blockIndex], 0, items.NextItemIndex); + itemsSpan = new(items.AsArray[blockIndex], 0, items.NextItemIndex); itemsSpan.CopyTo(targetSpan); } From c45af6a88448bda314f3e0b1f39ea714a0f52c09 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:01:28 +0200 Subject: [PATCH 227/305] Test data item type changed to "long" for performance reasons. --- .../BenchmarkBase.cs | 20 +++++++++--------- .../Benchmarks/Add.cs | 8 +++---- .../Benchmarks/AddRangeWhenSourceIsArray.cs | 8 +++---- .../AddRangeWhenSourceIsIEnumerable.cs | 8 +++---- .../Benchmarks/AddRangeWhenSourceIsIList.cs | 16 +++++++------- .../Benchmarks/AddRangeWhenSourceIsList.cs | 8 +++---- .../AddRangeWhenSourceIsSameType.cs | 6 +++--- .../Benchmarks/AddRange_WithCapacity.cs | 8 +++---- .../Benchmarks/Add_WithCapacity.cs | 8 +++---- .../Benchmarks/Create.cs | 8 +++---- .../Benchmarks/Create_WithCapacity.cs | 10 ++++----- .../Benchmarks/GetItem..cs | 6 +++--- .../RecyclableCollections.Benchmarks.cs | 20 +++++++++--------- .../Benchmarks/RemoveAt_FirstItems.cs | 8 +++---- .../Benchmarks/RemoveAt_LastItems.cs | 8 +++---- .../Benchmarks/Remove_FirstItems.cs | 8 +++---- .../Benchmarks/Remove_LastItems.cs | 8 +++---- .../POC/ArraySizeLimitPoc.Benchmarks.cs | 7 +++---- .../POC/PocBenchmarkBase.cs | 2 +- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 21 +++++++++---------- 20 files changed, 97 insertions(+), 99 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index 874f783..2d078c6 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -4,23 +4,23 @@ namespace Recyclable.Collections.Benchmarks { public class BenchmarkBase : PocBenchmarkBase { - protected object[]? _testObjects; - protected object[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); + protected long[]? _testObjects; + protected long[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); - protected List? _testObjectsAsList; - protected List TestObjectsAsList => _testObjectsAsList ?? throw new NullReferenceException("Something went wront and the field is not initialized"); + protected List? _testObjectsAsList; + protected List TestObjectsAsList => _testObjectsAsList ?? throw new NullReferenceException("Something went wront and the field is not initialized"); - 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 + 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 { get { for (long i = 0; i < TestObjectCount; i++) { - yield return new object(); + yield return i; } } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs index ed6b624..e881a81 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs @@ -10,7 +10,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - var list = new List(); + var list = new List(); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -22,7 +22,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(ClearMode.Always); + using var list = new PooledList(ClearMode.Auto); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -34,7 +34,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -46,7 +46,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(); + using var list = new RecyclableList(); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs index 895ea47..f0ea17a 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; - var list = new List(); + var list = new List(); list.AddRange(data); } @@ -17,7 +17,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsArray() { var data = TestObjects; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); list.AddRange(data); } @@ -25,7 +25,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; - using var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); list.AddRange(data); } @@ -33,7 +33,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_AddRangeWhenSourceIsArray() { var data = TestObjects; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs index 8b1fc99..aebf532 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks public void List_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; - var list = new List(); + var list = new List(); list.AddRange(data); } @@ -17,7 +17,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); list.AddRange(data); } @@ -25,7 +25,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; - using var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); list.AddRange(data); } @@ -33,7 +33,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs index 271f04d..a7bff76 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs @@ -8,32 +8,32 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsRecyclableList; - var list = new List(); + var data = (IList)TestObjectsAsRecyclableList; + var list = new List(); list.AddRange(data); } //[Benchmark(Baseline = true)] public void PooledList_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsPooledList; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + var data = (IList)TestObjectsAsPooledList; + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); list.AddRange(data); } //[Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsRecyclableList; - using var list = new RecyclableArrayList(); + var data = (IList)TestObjectsAsRecyclableList; + using var list = new RecyclableArrayList(); list.AddRange(data); } //[Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsRecyclableList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var data = (IList)TestObjectsAsRecyclableList; + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs index 12b34cf..92178ec 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks public void List_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; - var list = new List(); + var list = new List(); list.AddRange(data); } @@ -17,7 +17,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); list.AddRange(data); } @@ -25,7 +25,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; - using var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); list.AddRange(data); } @@ -33,7 +33,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs index 6fd6e49..6133b30 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_AddRangeWhenSourceIsPooledList() { var data = TestObjectsAsPooledList; - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); list.AddRange(data); } @@ -17,7 +17,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; - using var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); list.AddRange(data); } @@ -25,7 +25,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs index 1fc85d3..70c2080 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs @@ -10,7 +10,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - var list = new List(checked((int)dataCount)); + var list = new List(dataCount); list.AddRange(data); } @@ -19,7 +19,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(capacity: checked((int)dataCount), ClearMode.Always); + using var list = new PooledList(capacity: dataCount, ClearMode.Auto); list.AddRange(data); } @@ -28,7 +28,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(checked((int)dataCount)); + using var list = new RecyclableArrayList(dataCount); list.AddRange(data); } @@ -37,7 +37,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs index 8ecfdaf..1509649 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs @@ -10,7 +10,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - var list = new List(checked((int)dataCount)); + var list = new List(dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -22,7 +22,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new PooledList(capacity: checked((int)dataCount), ClearMode.Always); + using var list = new PooledList(capacity: dataCount, ClearMode.Auto); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -34,7 +34,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(checked((int)dataCount)); + using var list = new RecyclableArrayList(dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); @@ -46,7 +46,7 @@ namespace Recyclable.Collections.Benchmarks { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs index d0f5a46..6574f42 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs @@ -8,28 +8,28 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = false)] public void List_Create() { - var list = new List(); + var list = new List(); DoNothing(list); } //[Benchmark(Baseline = true)] public void PooledList_Create() { - using var list = new PooledList(ClearMode.Always); + using var list = new PooledList(ClearMode.Auto); DoNothing(list); } //[Benchmark(Baseline = false)] public void RecyclableArrayList_Create() { - using var list = new RecyclableArrayList(); + using var list = new RecyclableArrayList(); DoNothing(list); } //[Benchmark(Baseline = false)] public void RecyclableList_Create() { - using var list = new RecyclableList(); + using var list = new RecyclableList(); DoNothing(list); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs index 1317ae0..7d43835 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs @@ -8,35 +8,35 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = false)] public void Array_Create_WithCapacity() { - var data = new object[TestObjectCount]; + var data = new long[TestObjectCount]; DoNothing(data); } //[Benchmark(Baseline = false)] public void List_Create_WithCapacity() { - var list = new List(checked((int)TestObjectCount)); + var list = new List(TestObjectCount); DoNothing(list); } //[Benchmark(Baseline = true)] public void PooledList_Create_WithCapacity() { - using var list = new PooledList(capacity: checked((int)TestObjectCount), ClearMode.Always); + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); DoNothing(list); } //[Benchmark(Baseline = false)] public void RecyclableArrayList_Create_WithCapacity() { - using var list = new RecyclableArrayList(checked((int)TestObjectCount)); + using var list = new RecyclableArrayList(TestObjectCount); DoNothing(list); } //[Benchmark(Baseline = false)] public void RecyclableList_Create_WithCapacity() { - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(list); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index 2b99bf2..db56607 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -66,10 +66,10 @@ namespace Recyclable.Collections.Benchmarks int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; - var memoryBlocks = new Span(data.AsArray, 0, data.LastBlockWithData + 1); + var memoryBlocks = new Span(data.AsArray, 0, data.LastBlockWithData + 1); for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) { - var memoryBlock = new Span(memoryBlocks[blockIndex], 0, blockSize); + var memoryBlock = new Span(memoryBlocks[blockIndex], 0, blockSize); for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) { DoNothing(memoryBlock[itemIndex]); @@ -79,7 +79,7 @@ namespace Recyclable.Collections.Benchmarks if (nextItemBlockIndex == data.LastBlockWithData) { - var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); + var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); for (var itemIndex = 0; itemIndex < memoryBlock.Length; itemIndex++) { DoNothing(memoryBlock[itemIndex]); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 2b2ca80..1be4904 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -6,32 +6,32 @@ namespace Recyclable.Collections.Benchmarks [MemoryDiagnoser] public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private object[] _testArray; - private RecyclableArrayList _testRecyclableArrayList; - private RecyclableList _testRecyclableList; - private PooledList _testPooledList; + private long[] _testArray; + private RecyclableArrayList _testRecyclableArrayList; + private RecyclableList _testRecyclableList; + private PooledList _testPooledList; - public PooledList TestObjectsAsPooledList => _testPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); + public PooledList TestObjectsAsPooledList => _testPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); [GlobalSetup] public void Setup() { Console.WriteLine("******* GLOBAL SETUP RAISED *******"); _testObjects = EnumerateTestObjects().ToArray(); - _testRecyclableArrayList = new(TestObjects, initialCapacity: checked((int)TestObjectCount)); - _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: checked((int)TestObjectCount)); + _testRecyclableArrayList = new(TestObjects, initialCapacity: TestObjectCount); + _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); _testArray = TestObjects.ToArray(); _testObjectsAsList = TestObjects.ToList(); _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); _testObjectsAsRecyclableList = TestObjects.ToRecyclableList(); - _testPooledList = new PooledList(TestObjects, ClearMode.Always); + _testPooledList = new PooledList(TestObjects, ClearMode.Always); } - private IEnumerable EnumerateTestObjects() + private IEnumerable EnumerateTestObjects() { for (long i = 0; i < TestObjectCount; i++) { - yield return new object(); + yield return i; } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs index e6fed8e..ddb0f98 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks public void List_RemoveAt_FirstItems() { var data = TestObjects; - var list = new List(data); + var list = new List(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -21,7 +21,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_RemoveAt_FirstItems() { var data = TestObjects; - using var list = new PooledList(data, ClearMode.Always); + using var list = new PooledList(data, ClearMode.Auto); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -33,7 +33,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_RemoveAt_FirstItems() { var data = TestObjects; - using var list = new RecyclableArrayList(data); + using var list = new RecyclableArrayList(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -45,7 +45,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_RemoveAt_FirstItems() { var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs index 01ca9be..9d84783 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks public void List_RemoveAt_LastItems() { var data = TestObjects; - var list = new List(data); + var list = new List(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { @@ -21,7 +21,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_RemoveAt_LastItems() { var data = TestObjects; - using var list = new PooledList(data, ClearMode.Always); + using var list = new PooledList(data, ClearMode.Auto); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { @@ -33,7 +33,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_RemoveAt_LastItems() { var data = TestObjects; - using var list = new RecyclableArrayList(data); + using var list = new RecyclableArrayList(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { @@ -45,7 +45,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_RemoveAt_LastItems() { var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs index a771bae..e53045a 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks public void List_Remove_FirstItems() { var data = TestObjects; - var list = new List(data); + var list = new List(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -21,7 +21,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_Remove_FirstItems() { var data = TestObjects; - using var list = new PooledList(data, ClearMode.Always); + using var list = new PooledList(data, ClearMode.Auto); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -33,7 +33,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_Remove_FirstItems() { var data = TestObjects; - using var list = new RecyclableArrayList(data); + using var list = new RecyclableArrayList(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -45,7 +45,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_Remove_FirstItems() { var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs index 2070bc5..571d56a 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs @@ -10,7 +10,7 @@ namespace Recyclable.Collections.Benchmarks public void List_Remove_LastItems() { var data = TestObjects; - var list = new List(data); + var list = new List(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { @@ -22,7 +22,7 @@ namespace Recyclable.Collections.Benchmarks public void PooledList_Remove_LastItems() { var data = TestObjects; - using var list = new PooledList(data, ClearMode.Always); + using var list = new PooledList(data, ClearMode.Auto); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { @@ -34,7 +34,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_Remove_LastItems() { var data = TestObjects; - using var list = new RecyclableArrayList(data); + using var list = new RecyclableArrayList(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { @@ -46,7 +46,7 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_Remove_LastItems() { var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { diff --git a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs index 69343bc..222a4a9 100644 --- a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs @@ -13,17 +13,16 @@ namespace Recyclable.Collections.Benchmarks.POC private const int _maxArraySize = 2_147_483_605; - [Benchmark(OperationsPerInvoke = 1)] public void Max_Allowed_Size_2_147_483_605() { - var arr = ArrayPool.Shared.Rent(_maxArraySize); + var arr = ArrayPool.Shared.Rent(_maxArraySize); } [Benchmark(OperationsPerInvoke = 1)] public void FindArraySizeLimit() { - var pool = ArrayPool.Shared; + var pool = ArrayPool.Shared; var bufferSize = int.MaxValue / 2; var oldValue = 0; while (true) @@ -31,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks.POC try { Console.Write($"Allocating {bufferSize}..."); - var arr = pool.Rent((int)bufferSize); + var arr = pool.Rent(bufferSize); Console.WriteLine("success"); pool.Return(arr); arr = null; diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 552979a..88bc0cc 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -8,7 +8,7 @@ //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public long TestObjectCount = 1_000_000; + public int TestObjectCount = 1_000_000; //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] //[Params(10, 20, 50, 100)] diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index b160456..4601ef0 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -1845,47 +1845,46 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion public partial class RecyclableListPocBenchmarks { - [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV1_ByPowOf2() { - using var list = new RecyclableListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); _ = list.EnsureCapacity(TestObjectCount); } [Benchmark(Baseline = true)] public void RecyclableList_EnsureCapacityV2_ByPowOf2() { - using var list = new RecyclableListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); + using var list = new RecyclableListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); } [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacity_ByPowOf2() { - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - _ = RecyclableList.EnsureCapacity(list, TestObjectCount); + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = RecyclableList.EnsureCapacity(list, TestObjectCount); } [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV1_ByBlockSize() { - using var list = new RecyclableListV1(); + using var list = new RecyclableListV1(); _ = list.EnsureCapacity(TestObjectCount); } [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV2_ByBlockSize() { - using var list = new RecyclableListV2(); - _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); + using var list = new RecyclableListV2(); + _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); } [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacity_ByBlockSize() { - using var list = new RecyclableList(); - _ = RecyclableList.EnsureCapacity(list, TestObjectCount); + using var list = new RecyclableList(); + _ = RecyclableList.EnsureCapacity(list, TestObjectCount); } } } From 46b074a35c8d6baee211c67bf04109ed4d9b544b Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:24:12 +0200 Subject: [PATCH 228/305] Benchmark organization improvements to reuse code. --- .../BenchmarkBase.cs | 25 ++++++++++++++++--- .../RecyclableCollections.Benchmarks.cs | 20 +++------------ .../Core/DataGenerator.cs | 20 +++++++++++++++ .../POC/PocBenchmarkBase.cs | 10 +++++++- 4 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 Recyclable.Collections.Benchmarks/Core/DataGenerator.cs diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index 2d078c6..41925f4 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -1,4 +1,6 @@ -using Recyclable.Collections.Benchmarks.POC; +using BenchmarkDotNet.Attributes; +using Recyclable.Collections.Benchmarks.Core; +using Recyclable.Collections.Benchmarks.POC; namespace Recyclable.Collections.Benchmarks { @@ -25,9 +27,26 @@ namespace Recyclable.Collections.Benchmarks } } - protected static void DoNothing(T obj) + [GlobalSetup] + public virtual void Setup() { - _ = obj; + Console.WriteLine("******* GLOBAL SETUP *******"); + _testObjects = DataGenerator.EnumerateTestObjects(TestObjectCount); + Console.WriteLine("******* TEST DATA CREATED *******"); + } + + [GlobalCleanup] + public virtual void Cleanup() + { + Console.WriteLine("******* GLOBAL CLEANUP *******"); + // Enable this if item type is changed to reference type + //if (_testObjects != null) + //{ + // Array.Fill(_testObjects, 0); + //} + + _testObjects = default; + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 1be4904..2d501c6 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -13,11 +13,9 @@ namespace Recyclable.Collections.Benchmarks public PooledList TestObjectsAsPooledList => _testPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); - [GlobalSetup] - public void Setup() + public override void Setup() { - Console.WriteLine("******* GLOBAL SETUP RAISED *******"); - _testObjects = EnumerateTestObjects().ToArray(); + base.Setup(); _testRecyclableArrayList = new(TestObjects, initialCapacity: TestObjectCount); _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); _testArray = TestObjects.ToArray(); @@ -27,18 +25,8 @@ namespace Recyclable.Collections.Benchmarks _testPooledList = new PooledList(TestObjects, ClearMode.Always); } - private IEnumerable EnumerateTestObjects() + public override void Cleanup() { - for (long i = 0; i < TestObjectCount; i++) - { - yield return i; - } - } - - [GlobalCleanup] - public void Cleanup() - { - Console.WriteLine("******* GLOBAL CLEANUP *******"); _testRecyclableArrayList?.Dispose(); _testRecyclableList?.Dispose(); _testPooledList?.Dispose(); @@ -46,7 +34,7 @@ namespace Recyclable.Collections.Benchmarks _testObjectsAsRecyclableArrayList = default; _testObjectsAsRecyclableList = default; _testObjectsAsList = default; - _testObjects = default; + base.Cleanup(); } } } diff --git a/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs b/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs new file mode 100644 index 0000000..b4d8321 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs @@ -0,0 +1,20 @@ +namespace Recyclable.Collections.Benchmarks.Core +{ + public class DataGenerator + { + public static long[] EnumerateTestObjects(long testObjectCount) + { + var result = new long[testObjectCount]; + Console.WriteLine("******* TEST DATA ARRAY CREATED *******"); + + Span resultSpan = result; + for (int i = 0; i < testObjectCount; i++) + { + resultSpan[i] = i; + } + + return result; + } + + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 88bc0cc..1a56203 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -1,4 +1,6 @@ -namespace Recyclable.Collections.Benchmarks.POC +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC { public class PocBenchmarkBase { @@ -21,5 +23,11 @@ > 0 and <= 10 => (int)TestObjectCount, _ => TestObjectCount / Divider > 0 ? checked((int)(TestObjectCount / Divider)) : checked((int)TestObjectCount) }; + + [MethodImpl(MethodImplOptions.NoInlining)] + protected static void DoNothing(in T item) + { + _ = item; + } } } From 450701f800f6d9383a66e16eeacdd26c738132b1 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:38:45 +0200 Subject: [PATCH 229/305] RecyclableList.EnsureCapacity v3 benchmarks added. --- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 990 +++++++++++++++++- .../POC/RecyclableListPoc.Benchmarks.cs | 65 +- 2 files changed, 1046 insertions(+), 9 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index 4601ef0..1732974 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -1843,48 +1843,1026 @@ namespace Recyclable.Collections.Benchmarks.POC } #endregion + + #region RecyclableList v3 + + internal class RecyclableListV3 : 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][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private byte _blockSizePow2Shift; + private int _blockSizeMinus1; + private bool _blockPoolingRequired; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableListV3 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2Shift)); + int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); + int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableListV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && list._blockPoolingRequired) + { + memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, requiredBlockCount - sourceBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++], NeedsClearing); + } + } + + ArrayPool memoryBlocksPool = list._memoryBlocksPool; + + // Allocate new memory block for all arrays + bool memoryBlockPoolingRequired = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength; + T[][] newMemoryBlocks = memoryBlockPoolingRequired ? memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(list._memoryBlocks, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, requiredBlockCount - sourceBlockCount); + if (list._blockPoolingRequired) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableListV3 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockPoolingRequired = true; + list._blockArrayPool = blockArrayPool ?? RecyclableArrayPool.Shared(blockSize); + } + else + { + list._blockPoolingRequired = false; + list._blockArrayPool = RecyclableArrayPool.Null; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableListV3 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & _blockSizeMinus1] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListV3 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockPoolingRequired) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + memoryBlocksSpan[toRemoveIdx] = _emptyBlockArray; + } + } + + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockPoolingRequired) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListV3() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + public partial class RecyclableListPocBenchmarks { [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV1_ByPowOf2() { using var list = new RecyclableListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - _ = list.EnsureCapacity(TestObjectCount); + DoNothing(_ensureCapacityV1Func.Invoke(list, new object[] { TestObjectCount })); } [Benchmark(Baseline = true)] public void RecyclableList_EnsureCapacityV2_ByPowOf2() { using var list = new RecyclableListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); + DoNothing(_ensureCapacityV2Func.Invoke(null, new object[] { list, TestObjectCount })); + } + + [Benchmark(Baseline = true)] + public void RecyclableList_EnsureCapacityV3_ByPowOf2() + { + using var list = new RecyclableListV3(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); } [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacity_ByPowOf2() { using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - _ = RecyclableList.EnsureCapacity(list, TestObjectCount); + DoNothing(_ensureCapacityNewFunc.Invoke(null, new object[] { list, TestObjectCount })); } [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV1_ByBlockSize() { using var list = new RecyclableListV1(); - _ = list.EnsureCapacity(TestObjectCount); + DoNothing(_ensureCapacityV1Func.Invoke(list, new object[] { TestObjectCount })); } [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV2_ByBlockSize() { using var list = new RecyclableListV2(); - _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); + DoNothing(_ensureCapacityV2Func.Invoke(null, new object[] { list, TestObjectCount })); + } + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacityV3_ByBlockSize() + { + using var list = new RecyclableListV3(); + DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); } [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacity_ByBlockSize() { using var list = new RecyclableList(); - _ = RecyclableList.EnsureCapacity(list, TestObjectCount); + DoNothing(_ensureCapacityNewFunc.Invoke(null, new object[] { list, TestObjectCount })); } } } diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs index 1b48f5a..26b5f0b 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -1,16 +1,75 @@ -namespace Recyclable.Collections.Benchmarks.POC +using System.Reflection; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using Recyclable.Collections.Benchmarks.Core; + +namespace Recyclable.Collections.Benchmarks.POC { public partial class RecyclableListPocBenchmarks : PocBenchmarkBase { + private long[]? _testArray; + public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); + private RecyclableList? _testRecyclableList; + public RecyclableList TestObjectsAsRecyclableList => _testRecyclableList ?? throw new NullReferenceException("Something is wrong and RecyclableList is null"); + + private static readonly MethodInfo _ensureCapacityNewFunc; + private static readonly MethodInfo _ensureCapacityV1Func; + private static readonly MethodInfo _ensureCapacityV2Func; + private static readonly MethodInfo _ensureCapacityV3Func; + + static RecyclableListPocBenchmarks() + { + _ensureCapacityNewFunc = typeof(RecyclableList).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableList)}"); + + _ensureCapacityV1Func = typeof(RecyclableListV1).GetMethod("EnsureCapacity", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableListV1)}"); + + _ensureCapacityV2Func = typeof(RecyclableListV2).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.Public) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableListV2)}"); + + _ensureCapacityV3Func = typeof(RecyclableListV3).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableListV3)}"); + + _indexOfFunc = typeof(RecyclableList).GetMethod("IndexOf", BindingFlags.Public | BindingFlags.Instance) + ?? throw new NullReferenceException($"Method IndexOf not found in class {nameof(RecyclableList)}"); + + _indexOfV4Func = typeof(RecyclableListIndexOfV1).GetMethod("IndexOf", BindingFlags.Public | BindingFlags.Instance) + ?? throw new NullReferenceException($"Method IndexOf not found in class {nameof(RecyclableListIndexOfV1)}"); + } + public static void Run() { var benchmark = new RecyclableListPocBenchmarks(); + benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); benchmark.RecyclableList_EnsureCapacityV1_ByPowOf2(); benchmark.RecyclableList_EnsureCapacityV2_ByPowOf2(); - benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacityV3_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); benchmark.RecyclableList_EnsureCapacityV1_ByBlockSize(); benchmark.RecyclableList_EnsureCapacityV2_ByBlockSize(); - benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); + benchmark.RecyclableList_EnsureCapacityV3_ByBlockSize(); + } + + [GlobalSetup] + public void Setup() + { + _testArray = DataGenerator.EnumerateTestObjects(TestObjectCount); + _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + + // Warm up memory pools for fair comparison with 1st run + RecyclableList_EnsureCapacity_ByPowOf2(); + RecyclableList_EnsureCapacityV1_ByPowOf2(); + RecyclableList_EnsureCapacityV2_ByPowOf2(); + RecyclableList_EnsureCapacityV3_ByPowOf2(); + } + + [GlobalCleanup] + public void Cleanup() + { + _testArray = null; + _testRecyclableList?.Dispose(); + _testRecyclableList = null; } } } From 0951b442aefdf35c4c1049fd8cd826c63be02158 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:42:06 +0200 Subject: [PATCH 230/305] Compilation warnings resolved --- .../Benchmarks/Count.cs | 6 +++--- .../Benchmarks/GetItem..cs | 12 ++++++------ .../Benchmarks/LongCount.cs | 4 ++-- .../Benchmarks/RecyclableCollections.Benchmarks.cs | 8 ++++---- .../Benchmarks/SetItem.cs | 4 ++-- .../RecyclableListPoc.Benchmarks.EnsureCapacity.cs | 2 +- Recyclable.Collections/RecyclableSortedList.cs | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs index d5286c5..1803de3 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs @@ -8,7 +8,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = false)] public void Array_Count() { - var data = _testArray; + var data = TestObjects; DoNothing(data.Length); } @@ -22,7 +22,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = true)] public void PooledList_Count() { - var data = _testPooledList; + var data = TestObjectsAsPooledList; DoNothing(data.Count); } @@ -36,7 +36,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = false)] public void RecyclableList_Count() { - var data = _testRecyclableList; + var data = TestObjectsAsRecyclableList; DoNothing(data.Count); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index db56607..27006d4 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -29,7 +29,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark(Baseline = true)] public void PooledList_GetItem() { - var data = _testPooledList; + var data = TestObjectsAsPooledList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -51,7 +51,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark(Baseline = false)] public void RecyclableList_GetItem() { - var data = _testRecyclableList; + var data = TestObjectsAsRecyclableList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -62,7 +62,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsSpan() { - var data = _testRecyclableList; + var data = TestObjectsAsRecyclableList; int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; @@ -90,7 +90,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsArray() { - var data = _testRecyclableList; + var data = TestObjectsAsRecyclableList; int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; @@ -118,7 +118,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsForEach() { - var data = _testRecyclableList; + var data = TestObjectsAsRecyclableList; var dataCount = TestObjectCount; foreach (var item in data) @@ -130,7 +130,7 @@ namespace Recyclable.Collections.Benchmarks [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsWhile() { - var data = _testRecyclableList; + var data = TestObjectsAsRecyclableList; long dataCount = TestObjectCount; if (dataCount == 0) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs index f9a2813..e299491 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs @@ -7,14 +7,14 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = false)] public void Array_LongCount() { - var data = _testArray; + var data = TestObjects; DoNothing(data.LongLength); } //[Benchmark(Baseline = false)] public void RecyclableList_LongCount() { - var data = _testRecyclableList; + var data = TestObjectsAsRecyclableList; DoNothing(data.LongCount); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 2d501c6..a927053 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -6,10 +6,10 @@ namespace Recyclable.Collections.Benchmarks [MemoryDiagnoser] public partial class RecyclableCollectionsBenchmarks : BenchmarkBase { - private long[] _testArray; - private RecyclableArrayList _testRecyclableArrayList; - private RecyclableList _testRecyclableList; - private PooledList _testPooledList; + private long[]? _testArray; + private RecyclableArrayList? _testRecyclableArrayList; + private RecyclableList? _testRecyclableList; + private PooledList? _testPooledList; public PooledList TestObjectsAsPooledList => _testPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs index 54dc577..ce4eaef 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs @@ -31,7 +31,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = true)] public void PooledList_SetItem() { - var data = _testPooledList; + var data = TestObjectsAsPooledList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -53,7 +53,7 @@ namespace Recyclable.Collections.Benchmarks //[Benchmark(Baseline = false)] public void RecyclableList_SetItem() { - var data = _testRecyclableList; + var data = TestObjectsAsRecyclableList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index 1732974..5f4ebe2 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -1277,7 +1277,7 @@ namespace Recyclable.Collections.Benchmarks.POC _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); - if (_memoryBlocks?.Length > 0 && minBlockSize != _memoryBlocks[0].Length) + if (_memoryBlocks!.Length > 0 && minBlockSize != _memoryBlocks[0].Length) { _blockSize = _memoryBlocks[0].Length; _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs index ee807e2..1c64e82 100644 --- a/Recyclable.Collections/RecyclableSortedList.cs +++ b/Recyclable.Collections/RecyclableSortedList.cs @@ -38,7 +38,7 @@ public void BeginUpdate() => _isUpdating++; - public void Clear() + public new void Clear() { BeginUpdate(); try From 8ccd129bd6ba81200773a2346a9ac80d78ad9f0f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:44:17 +0200 Subject: [PATCH 231/305] RecyclableList.IndexOf benchmark for all items added --- .../Benchmarks/IndexOf_AllItems.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs new file mode 100644 index 0000000..eff6950 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs @@ -0,0 +1,80 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + [Benchmark(Baseline = false)] + public void Array_IndexOf_AllItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[i])); + } + } + + [Benchmark(Baseline = false)] + public void List_IndexOf_AllItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + [Benchmark(Baseline = true)] + public void PooledList_IndexOf_AllItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableArrayList_IndexOf_AllItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableList_IndexOf_AllItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + [Benchmark(Baseline = false)] + public void RecyclableList_LongIndexOf_AllItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.LongIndexOf(data[i])); + } + } + + } +} From 112a482583b5bd4339bfa7d2eeeeac7d8c393204 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:51:46 +0200 Subject: [PATCH 232/305] Additional POC benchmarks added. --- .../POC/RoundBenchmarks.cs | 27 ++ .../POC/SpanVsArrayBenchmarks.cs | 71 +++++ .../POC/WhenParallelSearchBenchmarks.cs | 280 ++++++++++++++++++ Recyclable.Collections.Benchmarks/Program.cs | 9 +- 4 files changed, 384 insertions(+), 3 deletions(-) create mode 100644 Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs diff --git a/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs new file mode 100644 index 0000000..48e1bcf --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs @@ -0,0 +1,27 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class RoundBenchmarks : PocBenchmarkBase + { + private const double OptimalParallelSearchStep = 0.329; + + [Benchmark(Baseline = true)] + public void RoundWithMathFloor() + { + DoNothing((long)Math.Floor(TestObjectCount * OptimalParallelSearchStep)); + } + + [Benchmark] + public void RoundWithMathRound() + { + DoNothing((long)Math.Round(TestObjectCount * OptimalParallelSearchStep, 0)); + } + + [Benchmark(Baseline = false)] + public void RoundWithTypeCasting() + { + DoNothing((long)(TestObjectCount * OptimalParallelSearchStep)); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs new file mode 100644 index 0000000..d4dd096 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs @@ -0,0 +1,71 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class SpanVsArrayBenchmarks : BenchmarkBase + { + //[Benchmark] + public void ArrayGetWithVar() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark(Baseline = true)] + public void ArrayGetWithField() + { + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + DoNothing(_testObjects![i]); + } + } + + //[Benchmark] + public void SpanGet() + { + var dataCount = TestObjectCount; + Span data = new(_testObjects, 0, dataCount); + for (int i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark] + public void ArraySetWithVar() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + data[i] = i; + } + } + + [Benchmark(Baseline = true)] + public void ArraySetWithField() + { + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + _testObjects![i] = i; + } + } + + [Benchmark] + public void SpanSet() + { + var dataCount = TestObjectCount; + Span data = new(_testObjects, 0, dataCount); + for (int i = 0; i < dataCount; i++) + { + data[i] = i; + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs new file mode 100644 index 0000000..5e474ec --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -0,0 +1,280 @@ +using BenchmarkDotNet.Attributes; +using MoreLinq; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class WhenParallelSearchBenchmarks : PocBenchmarkBase + { + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DoNothing(T index) + { + //Console.WriteLine(index); + } + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32)] + //public int NumberOfThreads { get; set; } = 3; + + //[Params(10_000, 20_000, 35_000, 50_000, 75_000, 100_000, 125_000, 150_000, 175_000, 200_000, 225_000, 250_000, 275_000, 300_000, 325_000, 350_000, 375_000, 400_000, 500_000)] + //[Params(320_000, 321_000, 322_000, 323_000, 324_000, 325_000, 326_000, 327_000, 328_000, 329_000, 330_000)] + //[Params(329_000, 512_000, 750_000, 1_000_000, 3_290_000)] + //public int Step { get; set; } = 329_000; // This was the fastest so far + + private static readonly int _numberOfProcessors = Environment.ProcessorCount; + private RecyclableList? _testData; + + public object? ItemToFind { get; set; } + + [GlobalSetup] + public void Setup() + { + _testData = Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableList(BlockSize); + ItemToFind = _testData.Last(); + } + + [GlobalCleanup] + public void Cleanup() + { + _testData = default; + } + + + [Benchmark(Baseline = true)] + public void IndexOfSequentially() + { + long index = DoLongIndexOfSequentially(); + if (index >= 0) + { + DoNothing(index); + } + else + { + throw new Exception("Item not found"); + } + } + + private long DoLongIndexOfSequentially() + { + // This won't work for TestObjectCount == 0 + int index; + int blockSize = _testData!.BlockSize; + int lastBlockWithData = _testData.LastBlockWithData; + var memoryBlocks = _testData.AsArray; + var itemToFind = ItemToFind; + int blockIndex = 0; + while (true) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (index >= 0) + { + return (blockIndex << _testData.BlockSizePow2BitShift) + index; + } + + if (blockIndex + 1 < lastBlockWithData) + { + blockIndex++; + } + else + { + break; + } + } + + index = lastBlockWithData == _testData.NextItemBlockIndex + ? Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, _testData.NextItemIndex) + : Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, blockSize); + + return index >= 0 ? (lastBlockWithData << _testData.BlockSizePow2BitShift) + index : -1; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static long DoLongIndexOfParallel(RecyclableList list, in long itemToFind, ItemRange searchInfo, ManualResetEventSlim itemFoundSignal) + { + int index; + int blockSize = list.BlockSize; + int blockIndex = searchInfo.BlockIndex; + long itemsToSearchCount = searchInfo.ItemsToSearchCount; + long[][] memoryBlocks = list.AsArray; + index = Array.IndexOf(memoryBlocks[searchInfo.BlockIndex], itemToFind, searchInfo.StartingItemIndex, blockSize - searchInfo.StartingItemIndex); + if (itemFoundSignal.IsSet) + { + return -1; + } + else if (index >= 0) + { + return (blockIndex << list.BlockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize - searchInfo.StartingItemIndex; + if (itemsToSearchCount < 0) + { + return -1; + } + + int lastBlockWithData = blockIndex + (int)(itemsToSearchCount >> list.BlockSizePow2BitShift) + ((itemsToSearchCount & (list.BlockSize - 1)) > 0 ? 1 : 0); + blockIndex++; + while (true) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return -1; + } + else if (index >= 0) + { + return ((long)blockIndex << list.BlockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + if (blockIndex + 1 < lastBlockWithData && itemsToSearchCount < 0) + { + blockIndex++; + } + else + { + break; + } + } + + index = itemsToSearchCount < 0 || itemFoundSignal.IsSet || lastBlockWithData > list.NextItemBlockIndex + ? -1 + : lastBlockWithData == list.NextItemBlockIndex + ? Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, checked((int)Math.Min(list.NextItemIndex, itemsToSearchCount))) + : Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); + + return itemFoundSignal.IsSet || index < 0 ? -1 : ((long)lastBlockWithData << list.BlockSizePow2BitShift) + index; + } + + [Benchmark] + public void IndexOfParallel() + { + var index = DoIndexOfParallel(); + if (index >= 0) + { + DoNothing(index); + } + else + { + throw new Exception("Item not found"); + } + } + + private long DoIndexOfParallel() + { + using var itemFoundSignal = new ManualResetEventSlim(false); + using var allDoneSignal = new Barrier(1); + //long step = Step;// TestObjectCount / NumberOfThreads; + int blockSize = _testData!.BlockSize; + //object[][] memoryBlocks = _testData.AsArray(); + //long itemsRemaining = testObjectCount; + var foundItemIndex = -1L; + var itemToFind = ItemToFind; + //ItemRangesIterator.Iterate(0, blockSize, BlockSizePow2BitShift, TestObjectCount, (long)(TestObjectCount * 0.329), (searchInfo) => + //{ + // _ = allDoneSignal.AddParticipant(); + // _ = Task.Factory.StartNew((state) => + // { + // try + // { + // var index = DoLongIndexOfParallel(_testData, itemToFind, searchInfo, itemFoundSignal); + // if (index >= 0) + // { + // itemFoundSignal.Set(); + // lock (itemFoundSignal) + // { + // foundItemIndex = index; + // } + // } + // } + // finally + // { + // lock (allDoneSignal) + // { + // allDoneSignal.RemoveParticipant(); + // } + // } + // }, state: searchInfo); + + // return !itemFoundSignal.IsSet; + //}); + + //while (true) + //{ + // var toGive = Math.Min(step, itemsRemaining); + // SearchInfo searchInfo = new( + // blockIndex: blockIndex, + // startingItemIndex: itemIndex, + // itemsToSearchCount: toGive, + // itemToFind: ItemToFind, + // itemFoundSignal: itemFoundSignal); + + // _ = Task.Factory.StartNew((state) => + // { + // try + // { + // var index = DoLongIndexOfParallel(_testData, (SearchInfo)state!); + // if (index >= 0) + // { + // itemFoundSignal.Set(); + // lock (itemFoundSignal) + // { + // foundItemIndex = index; + // } + // } + // } + // finally + // { + // lock (allDoneSignal) + // { + // allDoneSignal.RemoveParticipant(); + // } + // } + // }, state: searchInfo); + + // //itemIndex = checked((int)((itemIndex + toGive) >> BlockSizePow2BitShift)) - 1; + // while (toGive > 0) + // { + // if (itemIndex + toGive < blockSize) + // { + // itemIndex += (int)toGive; + // break; + // } + // else + // { + // blockIndex++; + // //itemIndex += blockSize; + // //if (itemIndex > blockSize) + // //{ + // // itemIndex + // //} + // //if (itemIndex + blockSize < ) + // //itemIndex = (int)(toGive - blockSize + itemIndex); + // toGive -= blockSize; + // } + + // //if (blockIndex == _testData.LastBlockWithData) + // //{ + // // itemIndex = checked((int)toGive); + // // break; + // //} + + // } + + + // if (threadIndex + 1 < numberOfRanges && blockIndex <= _testData.LastBlockWithData) + // { + // threadIndex++; + // } + // else + // { + // break; + // } + //} + + allDoneSignal.SignalAndWait(); + return foundItemIndex; + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 4bc71a0..43fc561 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -37,12 +37,15 @@ internal class Program static void RunPocBenchmarks() { - _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); - _ = BenchmarkRunner.Run(BenchmarkConfig); - _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + //ArraySizeLimitPocBenchmarks>(BenchmarkConfig); } private static void Main(string[] args) From 8d199d83e390181f0953f8e0dd66f9ffe77dfd56 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:52:46 +0200 Subject: [PATCH 233/305] Visual Studio Code settings added. --- .vscode/launch.json | 26 +++++++ .vscode/tasks.json | 68 +++++++++++++++++++ .../Properties/launchSettings.json | 12 ++++ 3 files changed, 106 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 Recyclable.Collections.Benchmarks/Properties/launchSettings.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bcbe434 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Recyclable.Collections.Benchmarks/bin/Debug/net6.0/Recyclable.Collections.Benchmarks.dll", + "args": [], + "cwd": "${workspaceFolder}/Recyclable.Collections.Benchmarks", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..af5b07b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,68 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build debug", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build release", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-c", + "Release" + ], + "problemMatcher": "$msCompile", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj" + ], + "problemMatcher": "$msCompile" + }, + { + "type": "msbuild", + "problemMatcher": [ + "$msCompile" + ], + "group": "build", + "label": "Build: solution", + "detail": "Build the Recyclable.Collections.sln solution using dotnet build" + } + ] +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/Properties/launchSettings.json b/Recyclable.Collections.Benchmarks/Properties/launchSettings.json new file mode 100644 index 0000000..192f7ef --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "WSL": { + "commandName": "WSL2", + "distributionName": "" + }, + ".Net": { + "commandName": "Project", + "nativeDebugging": true + } + } +} \ No newline at end of file From ef670dbf2d1a9bdf65b0ce6637af9570d9c93803 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:54:51 +0200 Subject: [PATCH 234/305] Additional test object count combinations added. --- Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 1a56203..e310976 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { @@ -10,6 +11,11 @@ namespace Recyclable.Collections.Benchmarks.POC //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 100_000_000)] + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000, RecyclableDefaults.MaxPooledBlockSize)] + //[Params(1_000_000, 10_000_000, 100_000_000, 1_000_000_000)] + //[Params(1, 8, 16, 128, 192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000)] + //[Params(RecyclableDefaults.MaxPooledBlockSize)] public int TestObjectCount = 1_000_000; //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] From 33b4837411622e9650f42f42087a2bd395c1f5ab Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 21:58:38 +0200 Subject: [PATCH 235/305] BlockSizePow2BitShift added to PocBenchmarkBase for simpler reuse. --- Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index e310976..689c121 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Attributes; +using System.Numerics; using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC @@ -30,6 +31,8 @@ namespace Recyclable.Collections.Benchmarks.POC _ => TestObjectCount / Divider > 0 ? checked((int)(TestObjectCount / Divider)) : checked((int)TestObjectCount) }; + public byte BlockSizePow2BitShift => checked((byte)(31 - BitOperations.LeadingZeroCount((uint)BlockSize))); + [MethodImpl(MethodImplOptions.NoInlining)] protected static void DoNothing(in T item) { From 9d2cb050d223726e9d5a803e4be72678d9e4ffdd Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 22:03:30 +0200 Subject: [PATCH 236/305] RecyclableList.IndexOf POC benchmarks added. --- .../RecyclableListPoc.Benchmarks.IndexOf.cs | 1181 +++++++++++++++++ .../POC/RecyclableListPoc.Benchmarks.cs | 15 +- 2 files changed, 1190 insertions(+), 6 deletions(-) create mode 100644 Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs new file mode 100644 index 0000000..3ae38da --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs @@ -0,0 +1,1181 @@ +using BenchmarkDotNet.Attributes; +using System.Buffers; +using System.Collections; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC +{ + #region RecyclableList IndexOf + + internal class RecyclableListIndexOfV1 : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; + + 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 int _blockSize; + private byte _blockSizePow2BitShift; + private int _blockSizeMinus1; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCountIndexOfStep; + protected long _longCount; + + public long LongCount + { + get => _longCount; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableListIndexOfV1 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2BitShift)); + int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2BitShift)); + int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private static long DoLongIndexOfParallel(in RecyclableListIndexOfV1 list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int startingItemIndex = itemRange.StartingItemIndex; + T[][] memoryBlocks = list._memoryBlocks; + int index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, startingItemIndex, (int)Math.Min(blockSize - startingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - startingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndex; + } + + //T itemToFind = itemRange.ItemToFind; + //int lastBlockWithData = itemRange.LastBlockWithData; + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData + ? -1 + : blockIndex == list._nextItemBlockIndex + ? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + : Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) + //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); + + return index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private long DoIndexOfParallel(T item) + { + using var itemFoundSignal = ManualResetEventSlimmer.Create(false); + using var allDoneSignal = new Barrier(1); //BarrierSlimmer.Create(1); + long foundItemIndex = -1L; + Exception? ex = null; + + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + ItemRangesIterator.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); + allDoneSignal.SignalAndWait(); + if (ex == null) + { + return foundItemIndex; + } + + ex.CaptureAndRethrow(); + return ItemNotFoundIndex; + + //int blockIndex = 0; + //int itemIndex = 0; + //long itemsCount = _longCount; + //while (itemsCount > step) + //{ + // if (!LocalIndexOf(new(blockIndex, itemIndex, step))) + // { + // break; + // } + + // itemsCount -= step; + // //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); + // blockIndex += (int)((itemIndex + step) >> _blockSizePow2BitShift); + // itemIndex = (int)((itemIndex + step) & _blockSizeMinus1); + // //itemIndex += step; + // //while (itemIndex >= blockSize) + // //{ + // // blockIndex++; + // // itemIndex -= blockSize; + // //} + //} + + //_ = LocalIndexOf(new(blockIndex, itemIndex, itemsCount)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + bool LocalIndexOf(ItemRange itemRange) + { + _ = allDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal); + if (index >= 0) + { + itemFoundSignal.Set(); + foundItemIndex = index; + } + } + catch (Exception e) + { + //_ = Interlocked.Exchange(ref ex, e); + lock (allDoneSignal) + { + ex = e; + } + } + finally + { + itemRange.Dispose(); + allDoneSignal.RemoveParticipant(); + } + }); + //.ContinueWith(x => + //{ + // if (x.Exception != null) + // { + // lock (allDoneSignal) + // { + // ex = x.Exception; + // } + // } + //}); + + return !itemFoundSignal.IsSet; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableListIndexOfV1 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + do + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } while (true); + } + else + { + blockIndex = sourceBlockCount; + do + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } while (true); + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableListIndexOfV1 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableListIndexOfV1 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListIndexOfV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListIndexOfV1(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListIndexOfV1 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + + } + + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + _longCountIndexOfStep = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 + ? ItemNotFoundIndex + //// Optimization for tiny collections to avoid any vars etc. + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOfParallel(item)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListIndexOfV1() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + public partial class RecyclableListPocBenchmarks + { + [Benchmark(Baseline = true)] + public void RecyclableList_IndexOfV1() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableListIndexOfV1; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + [Benchmark(Baseline = false)] + 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.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs index 26b5f0b..ca2013c 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -11,6 +11,8 @@ namespace Recyclable.Collections.Benchmarks.POC public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); private RecyclableList? _testRecyclableList; public RecyclableList TestObjectsAsRecyclableList => _testRecyclableList ?? throw new NullReferenceException("Something is wrong and RecyclableList is null"); + private RecyclableListIndexOfV1? _testRecyclableListIndexOfV1; + internal RecyclableListIndexOfV1 TestObjectsAsRecyclableListIndexOfV1 => _testRecyclableListIndexOfV1 ?? throw new NullReferenceException("Something is wrong and RecyclableListIndexOfV1 is null"); private static readonly MethodInfo _ensureCapacityNewFunc; private static readonly MethodInfo _ensureCapacityV1Func; @@ -30,12 +32,6 @@ namespace Recyclable.Collections.Benchmarks.POC _ensureCapacityV3Func = typeof(RecyclableListV3).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableListV3)}"); - - _indexOfFunc = typeof(RecyclableList).GetMethod("IndexOf", BindingFlags.Public | BindingFlags.Instance) - ?? throw new NullReferenceException($"Method IndexOf not found in class {nameof(RecyclableList)}"); - - _indexOfV4Func = typeof(RecyclableListIndexOfV1).GetMethod("IndexOf", BindingFlags.Public | BindingFlags.Instance) - ?? throw new NullReferenceException($"Method IndexOf not found in class {nameof(RecyclableListIndexOfV1)}"); } public static void Run() @@ -49,6 +45,8 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.RecyclableList_EnsureCapacityV1_ByBlockSize(); benchmark.RecyclableList_EnsureCapacityV2_ByBlockSize(); benchmark.RecyclableList_EnsureCapacityV3_ByBlockSize(); + benchmark.RecyclableList_IndexOf(); + benchmark.RecyclableList_IndexOfV1(); } [GlobalSetup] @@ -56,12 +54,15 @@ namespace Recyclable.Collections.Benchmarks.POC { _testArray = DataGenerator.EnumerateTestObjects(TestObjectCount); _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testRecyclableListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); // Warm up memory pools for fair comparison with 1st run RecyclableList_EnsureCapacity_ByPowOf2(); RecyclableList_EnsureCapacityV1_ByPowOf2(); RecyclableList_EnsureCapacityV2_ByPowOf2(); RecyclableList_EnsureCapacityV3_ByPowOf2(); + RecyclableList_IndexOf(); + RecyclableList_IndexOfV1(); } [GlobalCleanup] @@ -70,6 +71,8 @@ namespace Recyclable.Collections.Benchmarks.POC _testArray = null; _testRecyclableList?.Dispose(); _testRecyclableList = null; + _testRecyclableListIndexOfV1?.Dispose(); + _testRecyclableListIndexOfV1 = null; } } } From ac6d71686c5c7fa4e07bb9b0c5c7e769aef892eb Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 22:04:01 +0200 Subject: [PATCH 237/305] ItemRangesIterator tests re-enabled. --- .../ItemRangesIteratorTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs index d5fa9d3..59715f8 100644 --- a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs +++ b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs @@ -15,14 +15,14 @@ namespace Recyclable.CollectionsTests var actual = new List<(int BlockIndex, int StartingItemIndex, long ItemsCount)>(); // Act - //ItemRangesIterator.Iterate(startingBlockIndex, blockSize, blockSizePow2BitShift, itemsCount, step, (searchInfo) => - //{ - // actual.Add((searchInfo.BlockIndex, searchInfo.StartingItemIndex, searchInfo.ItemsToSearchCount)); - // return true; - //}); + ItemRangesIterator.Iterate(startingBlockIndex, blockSize, blockSizePow2BitShift, itemsCount, step, (searchInfo) => + { + actual.Add((searchInfo.BlockIndex, searchInfo.StartingItemIndex, searchInfo.ItemsToSearchCount)); + return true; + }); // Validate - //_ = actual.Should().HaveCount(expected.Count()).And.ContainInConsecutiveOrder(expected); + _ = actual.Should().HaveCount(expected.Count()).And.ContainInConsecutiveOrder(expected); } public static IEnumerable TestCases => new[] From 8a500df8d2aa501f9ac64b5a5fb3fa52eb0f0823 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 22:06:59 +0200 Subject: [PATCH 238/305] SpanVsArray benchmark results added. --- .../POC/SpanVsArrayBenchmarks.txt | 993 ++++++++++++++++++ 1 file changed, 993 insertions(+) create mode 100644 Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt diff --git a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt new file mode 100644 index 0000000..6fb1573 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt @@ -0,0 +1,993 @@ +Docker Desktop - Windows Host + +| Method | TestObjectCount | Mean | Error | StdDev | Ratio | RatioSD | +|------------------ |---------------- |--------------------:|------------------:|------------------:|------:|--------:| +| ArrayGetWithVar | 1 | 1.5515 ns | 0.0611 ns | 0.0542 ns | 5.78 | 0.63 | +| ArrayGetWithField | 1 | 0.2697 ns | 0.0287 ns | 0.0268 ns | 1.00 | 0.00 | +| SpanGet | 1 | 0.5019 ns | 0.0117 ns | 0.0098 ns | 1.88 | 0.20 | +| | | | | | | | +| ArrayGetWithVar | 2 | 1.7411 ns | 0.0135 ns | 0.0113 ns | 2.78 | 0.09 | +| ArrayGetWithField | 2 | 0.6258 ns | 0.0215 ns | 0.0191 ns | 1.00 | 0.00 | +| SpanGet | 2 | 0.7584 ns | 0.0172 ns | 0.0161 ns | 1.21 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 3 | 2.0160 ns | 0.0264 ns | 0.0247 ns | 2.19 | 0.07 | +| ArrayGetWithField | 3 | 0.9226 ns | 0.0309 ns | 0.0258 ns | 1.00 | 0.00 | +| SpanGet | 3 | 1.0568 ns | 0.0240 ns | 0.0213 ns | 1.15 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 4 | 2.2360 ns | 0.0200 ns | 0.0167 ns | 1.89 | 0.07 | +| ArrayGetWithField | 4 | 1.1815 ns | 0.0457 ns | 0.0427 ns | 1.00 | 0.00 | +| SpanGet | 4 | 1.2586 ns | 0.0343 ns | 0.0321 ns | 1.07 | 0.05 | +| | | | | | | | +| ArrayGetWithVar | 5 | 2.5662 ns | 0.0591 ns | 0.0523 ns | 1.45 | 0.06 | +| ArrayGetWithField | 5 | 1.7752 ns | 0.0663 ns | 0.0554 ns | 1.00 | 0.00 | +| SpanGet | 5 | 2.3339 ns | 0.0548 ns | 0.0512 ns | 1.31 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 6 | 2.9409 ns | 0.0605 ns | 0.0566 ns | 1.46 | 0.03 | +| ArrayGetWithField | 6 | 2.0072 ns | 0.0225 ns | 0.0188 ns | 1.00 | 0.00 | +| SpanGet | 6 | 2.5796 ns | 0.0595 ns | 0.0557 ns | 1.28 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 7 | 3.2910 ns | 0.0638 ns | 0.0596 ns | 1.27 | 0.05 | +| ArrayGetWithField | 7 | 2.6211 ns | 0.0830 ns | 0.0923 ns | 1.00 | 0.00 | +| SpanGet | 7 | 3.0480 ns | 0.0499 ns | 0.0416 ns | 1.18 | 0.05 | +| | | | | | | | +| ArrayGetWithVar | 8 | 3.6615 ns | 0.1036 ns | 0.1348 ns | 1.34 | 0.05 | +| ArrayGetWithField | 8 | 2.7175 ns | 0.0753 ns | 0.0668 ns | 1.00 | 0.00 | +| SpanGet | 8 | 3.2317 ns | 0.0873 ns | 0.0681 ns | 1.19 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 9 | 4.1159 ns | 0.1028 ns | 0.0912 ns | 1.39 | 0.06 | +| ArrayGetWithField | 9 | 2.9648 ns | 0.0797 ns | 0.0707 ns | 1.00 | 0.00 | +| SpanGet | 9 | 3.4643 ns | 0.0946 ns | 0.0838 ns | 1.17 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 10 | 4.5874 ns | 0.1178 ns | 0.1210 ns | 1.38 | 0.06 | +| ArrayGetWithField | 10 | 3.3250 ns | 0.0664 ns | 0.0588 ns | 1.00 | 0.00 | +| SpanGet | 10 | 3.6339 ns | 0.0678 ns | 0.0601 ns | 1.09 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 11 | 5.0294 ns | 0.0905 ns | 0.0803 ns | 1.41 | 0.04 | +| ArrayGetWithField | 11 | 3.5648 ns | 0.0933 ns | 0.0872 ns | 1.00 | 0.00 | +| SpanGet | 11 | 4.0259 ns | 0.1108 ns | 0.1319 ns | 1.13 | 0.05 | +| | | | | | | | +| ArrayGetWithVar | 12 | 5.2701 ns | 0.1157 ns | 0.1025 ns | 1.42 | 0.04 | +| ArrayGetWithField | 12 | 3.7002 ns | 0.0957 ns | 0.0848 ns | 1.00 | 0.00 | +| SpanGet | 12 | 4.3132 ns | 0.1102 ns | 0.0977 ns | 1.17 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 13 | 5.7393 ns | 0.1438 ns | 0.1345 ns | 1.41 | 0.03 | +| ArrayGetWithField | 13 | 4.0569 ns | 0.1034 ns | 0.0863 ns | 1.00 | 0.00 | +| SpanGet | 13 | 4.4506 ns | 0.0812 ns | 0.0720 ns | 1.10 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 14 | 6.0965 ns | 0.1023 ns | 0.0854 ns | 1.40 | 0.03 | +| ArrayGetWithField | 14 | 4.3728 ns | 0.0803 ns | 0.0751 ns | 1.00 | 0.00 | +| SpanGet | 14 | 4.6293 ns | 0.1178 ns | 0.2124 ns | 1.09 | 0.06 | +| | | | | | | | +| ArrayGetWithVar | 15 | 7.0026 ns | 0.1261 ns | 0.1053 ns | 1.42 | 0.02 | +| ArrayGetWithField | 15 | 4.9188 ns | 0.0506 ns | 0.0422 ns | 1.00 | 0.00 | +| SpanGet | 15 | 5.1823 ns | 0.1318 ns | 0.1233 ns | 1.05 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 16 | 8.3418 ns | 0.1920 ns | 0.1702 ns | 1.51 | 0.04 | +| ArrayGetWithField | 16 | 5.5086 ns | 0.1068 ns | 0.0947 ns | 1.00 | 0.00 | +| SpanGet | 16 | 5.4778 ns | 0.1416 ns | 0.2246 ns | 1.02 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 17 | 8.1520 ns | 0.0699 ns | 0.0620 ns | 1.69 | 0.01 | +| ArrayGetWithField | 17 | 4.8123 ns | 0.0442 ns | 0.0369 ns | 1.00 | 0.00 | +| SpanGet | 17 | 5.5824 ns | 0.0741 ns | 0.0693 ns | 1.16 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 18 | 7.4813 ns | 0.0414 ns | 0.0367 ns | 1.42 | 0.01 | +| ArrayGetWithField | 18 | 5.2566 ns | 0.0312 ns | 0.0260 ns | 1.00 | 0.00 | +| SpanGet | 18 | 5.9601 ns | 0.0816 ns | 0.0763 ns | 1.13 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 19 | 7.7273 ns | 0.0835 ns | 0.0781 ns | 1.49 | 0.02 | +| ArrayGetWithField | 19 | 5.2033 ns | 0.0313 ns | 0.0261 ns | 1.00 | 0.00 | +| SpanGet | 19 | 6.3312 ns | 0.1551 ns | 0.1593 ns | 1.22 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 20 | 7.9391 ns | 0.0515 ns | 0.0457 ns | 1.37 | 0.03 | +| ArrayGetWithField | 20 | 5.8071 ns | 0.1415 ns | 0.1390 ns | 1.00 | 0.00 | +| SpanGet | 20 | 5.9330 ns | 0.0500 ns | 0.0443 ns | 1.02 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 30 | 10.6019 ns | 0.1303 ns | 0.1219 ns | 1.29 | 0.02 | +| ArrayGetWithField | 30 | 8.2413 ns | 0.1019 ns | 0.0851 ns | 1.00 | 0.00 | +| SpanGet | 30 | 8.4137 ns | 0.1042 ns | 0.0924 ns | 1.02 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 32 | 11.0617 ns | 0.1536 ns | 0.1362 ns | 1.27 | 0.02 | +| ArrayGetWithField | 32 | 8.6916 ns | 0.1022 ns | 0.0906 ns | 1.00 | 0.00 | +| SpanGet | 32 | 8.8803 ns | 0.0642 ns | 0.0536 ns | 1.02 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 40 | 12.8859 ns | 0.0889 ns | 0.0742 ns | 1.21 | 0.01 | +| ArrayGetWithField | 40 | 10.6318 ns | 0.0740 ns | 0.0618 ns | 1.00 | 0.00 | +| SpanGet | 40 | 10.8128 ns | 0.0622 ns | 0.0519 ns | 1.02 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 50 | 15.2773 ns | 0.1358 ns | 0.1204 ns | 1.17 | 0.01 | +| ArrayGetWithField | 50 | 13.0385 ns | 0.0772 ns | 0.0603 ns | 1.00 | 0.00 | +| SpanGet | 50 | 13.2485 ns | 0.0861 ns | 0.0719 ns | 1.02 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 60 | 19.4536 ns | 0.3026 ns | 0.2682 ns | 1.25 | 0.02 | +| ArrayGetWithField | 60 | 15.5407 ns | 0.1195 ns | 0.0998 ns | 1.00 | 0.00 | +| SpanGet | 60 | 15.7285 ns | 0.0886 ns | 0.0740 ns | 1.01 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 80 | 23.6613 ns | 0.3427 ns | 0.3206 ns | 0.95 | 0.01 | +| ArrayGetWithField | 80 | 24.9804 ns | 0.1885 ns | 0.1671 ns | 1.00 | 0.00 | +| SpanGet | 80 | 20.7933 ns | 0.2044 ns | 0.1912 ns | 0.83 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 90 | 25.2124 ns | 0.2229 ns | 0.1976 ns | 1.09 | 0.01 | +| ArrayGetWithField | 90 | 23.0452 ns | 0.1873 ns | 0.1660 ns | 1.00 | 0.00 | +| SpanGet | 90 | 23.1852 ns | 0.1221 ns | 0.1142 ns | 1.01 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 100 | 38.4832 ns | 0.3491 ns | 0.3094 ns | 1.23 | 0.02 | +| ArrayGetWithField | 100 | 31.3061 ns | 0.2504 ns | 0.2220 ns | 1.00 | 0.00 | +| SpanGet | 100 | 31.1156 ns | 0.3401 ns | 0.3015 ns | 0.99 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 128 | 45.1141 ns | 0.2042 ns | 0.1594 ns | 1.18 | 0.01 | +| ArrayGetWithField | 128 | 38.3883 ns | 0.3717 ns | 0.3477 ns | 1.00 | 0.00 | +| SpanGet | 128 | 37.9068 ns | 0.2695 ns | 0.2250 ns | 0.99 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 256 | 77.4174 ns | 0.6796 ns | 0.6024 ns | 1.12 | 0.01 | +| ArrayGetWithField | 256 | 68.8870 ns | 0.4120 ns | 0.3441 ns | 1.00 | 0.00 | +| SpanGet | 256 | 68.4291 ns | 0.4472 ns | 0.4183 ns | 0.99 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 512 | 139.0822 ns | 1.1336 ns | 1.0049 ns | 1.06 | 0.01 | +| ArrayGetWithField | 512 | 131.7129 ns | 0.8504 ns | 0.7101 ns | 1.00 | 0.00 | +| SpanGet | 512 | 131.8641 ns | 0.9284 ns | 0.8684 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 1024 | 266.5707 ns | 4.5696 ns | 5.4397 ns | 1.04 | 0.02 | +| ArrayGetWithField | 1024 | 257.5734 ns | 1.2839 ns | 1.1382 ns | 1.00 | 0.00 | +| SpanGet | 1024 | 256.7122 ns | 1.0711 ns | 0.9495 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 2048 | 511.6425 ns | 1.5722 ns | 1.3129 ns | 1.00 | 0.01 | +| ArrayGetWithField | 2048 | 509.8994 ns | 2.5392 ns | 2.2509 ns | 1.00 | 0.00 | +| SpanGet | 2048 | 507.5335 ns | 4.0426 ns | 3.3758 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 4096 | 1,019.1013 ns | 10.1205 ns | 9.4668 ns | 1.00 | 0.01 | +| ArrayGetWithField | 4096 | 1,018.5594 ns | 4.6897 ns | 4.1573 ns | 1.00 | 0.00 | +| SpanGet | 4096 | 1,012.1960 ns | 7.4658 ns | 6.6183 ns | 0.99 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 8192 | 2,017.1872 ns | 14.3314 ns | 12.7044 ns | 1.00 | 0.01 | +| ArrayGetWithField | 8192 | 2,023.1594 ns | 12.9330 ns | 11.4648 ns | 1.00 | 0.00 | +| SpanGet | 8192 | 2,014.0275 ns | 14.3402 ns | 12.7123 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 1000000 | 245,965.8295 ns | 1,733.9504 ns | 1,447.9271 ns | 1.00 | 0.01 | +| ArrayGetWithField | 1000000 | 245,337.9044 ns | 1,055.8869 ns | 881.7134 ns | 1.00 | 0.00 | +| SpanGet | 1000000 | 245,687.0731 ns | 1,561.6594 ns | 1,219.2409 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 2147483591 | 525,684,023.0769 ns | 3,207,250.2244 ns | 2,678,199.1945 ns | 0.99 | 0.01 | +| ArrayGetWithField | 2147483591 | 529,688,963.4615 ns | 3,381,258.2043 ns | 2,823,503.7386 ns | 1.00 | 0.00 | +| SpanGet | 2147483591 | 529,698,631.2857 ns | 4,050,107.8977 ns | 3,590,315.1583 ns | 1.00 | 0.01 | + +// * Warnings * +MultimodalDistribution + SpanVsArrayBenchmarks.SpanGet: Default -> It seems that the distribution is bimodal (mValue = 3.54) + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (3.23 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was detected (1.78 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (2.12 ns, 2.14 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (3.33 ns, 3.35 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (2.26 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (2.59 ns, 2.62 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 2 outliers were detected (2.57 ns, 2.65 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (3.86 ns, 3.91 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (4.31 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 3 outliers were removed (3.58 ns..4.00 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (3.71 ns, 3.73 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (4.94 ns, 4.98 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 3 outliers were detected (5.00 ns, 5.79 ns, 5.85 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.62 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (5.22 ns..5.28 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (6.25 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.91 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (5.41 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (6.96 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (5.20 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (5.55 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (7.00 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (6.16 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (7.55 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (5.25 ns, 5.66 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (6.38 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 4 outliers were removed (6.16 ns..6.78 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (6.37 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (8.13 ns, 8.29 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 6 outliers were removed (7.09 ns..7.33 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 3 outliers were detected (8.48 ns, 9.28 ns, 9.33 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (6.81 ns, 6.84 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (10.70 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (7.53 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (8.13 ns..8.25 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (10.23 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (6.56 ns, 6.56 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.33 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (6.89 ns, 7.03 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (6.89 ns, 7.00 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.66 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (8.03 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (10.35 ns, 10.41 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (10.22 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (12.95 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (10.60 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (10.67 ns, 10.75 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (14.69 ns, 14.85 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (12.44 ns, 12.66 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (12.61 ns, 12.77 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (17.56 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 3 outliers were removed (14.97 ns..14.98 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (15.18 ns, 15.61 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (22.40 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (17.54 ns, 18.13 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (17.74 ns, 17.99 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (26.15 ns, 27.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (27.26 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (25.32 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (41.56 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (33.96 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (33.76 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (47.36 ns..47.54 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (40.60 ns, 40.85 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (81.64 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (72.86 ns, 73.52 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (139.97 ns, 145.75 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (137.07 ns, 138.83 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (286.19 ns, 314.20 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (267.48 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (265.60 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (522.93 ns, 540.60 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (508.42 ns, 525.02 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (525.76 ns, 527.95 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 3 outliers were detected (1.01 us, 1.01 us, 1.07 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (1.07 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (2.08 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (2.10 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (2.12 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (256.02 us, 256.31 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (250.53 us, 250.70 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (252.66 us..282.22 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 4 outliers were detected (519.00 ms, 522.14 ms, 531.86 ms, 536.31 ms) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (544.93 ms, 551.49 ms) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (557.67 ms) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec) + +************************************************************************************************************************************************* +************************************************************************************************************************************************* +************************************************************************************************************************************************* + +BenchmarkDotNet=v0.13.3, OS=Windows 11 (10.0.22621.1555) +AMD Ryzen 7 4700U with Radeon Graphics, 1 CPU, 8 logical and 8 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestObjectCount | Mean | Error | StdDev | Median | Ratio | RatioSD | +|------------------ |---------------- |--------------------:|----------------:|----------------:|--------------------:|------:|--------:| +| ArrayGetWithVar | 1 | 1.4115 ns | 0.0127 ns | 0.0119 ns | 1.4133 ns | 6.74 | 0.18 | +| ArrayGetWithField | 1 | 0.2095 ns | 0.0051 ns | 0.0045 ns | 0.2107 ns | 1.00 | 0.00 | +| SpanGet | 1 | 0.2636 ns | 0.0024 ns | 0.0021 ns | 0.2632 ns | 1.26 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 2 | 1.7398 ns | 0.0122 ns | 0.0114 ns | 1.7406 ns | 2.84 | 0.14 | +| ArrayGetWithField | 2 | 0.6150 ns | 0.0407 ns | 0.0318 ns | 0.6095 ns | 1.00 | 0.00 | +| SpanGet | 2 | 0.4950 ns | 0.0079 ns | 0.0066 ns | 0.4981 ns | 0.81 | 0.05 | +| | | | | | | | | +| ArrayGetWithVar | 3 | 2.2776 ns | 0.0778 ns | 0.2294 ns | 2.4503 ns | 2.21 | 0.25 | +| ArrayGetWithField | 3 | 1.0145 ns | 0.0439 ns | 0.0410 ns | 1.0062 ns | 1.00 | 0.00 | +| SpanGet | 3 | 0.7529 ns | 0.0250 ns | 0.0234 ns | 0.7476 ns | 0.74 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 4 | 2.2875 ns | 0.0630 ns | 0.0559 ns | 2.2669 ns | 2.27 | 0.06 | +| ArrayGetWithField | 4 | 1.0066 ns | 0.0164 ns | 0.0153 ns | 1.0050 ns | 1.00 | 0.00 | +| SpanGet | 4 | 0.9895 ns | 0.0123 ns | 0.0115 ns | 0.9922 ns | 0.98 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 5 | 2.6949 ns | 0.0633 ns | 0.0592 ns | 2.6701 ns | 1.42 | 0.16 | +| ArrayGetWithField | 5 | 1.9059 ns | 0.0682 ns | 0.1868 ns | 1.8684 ns | 1.00 | 0.00 | +| SpanGet | 5 | 1.5092 ns | 0.0392 ns | 0.0348 ns | 1.5102 ns | 0.79 | 0.09 | +| | | | | | | | | +| ArrayGetWithVar | 6 | 3.1465 ns | 0.0931 ns | 0.1143 ns | 3.0808 ns | 1.69 | 0.06 | +| ArrayGetWithField | 6 | 1.8594 ns | 0.0124 ns | 0.0116 ns | 1.8588 ns | 1.00 | 0.00 | +| SpanGet | 6 | 2.0726 ns | 0.0716 ns | 0.0879 ns | 2.0578 ns | 1.11 | 0.05 | +| | | | | | | | | +| ArrayGetWithVar | 7 | 3.2474 ns | 0.0525 ns | 0.0410 ns | 3.2404 ns | 1.51 | 0.02 | +| ArrayGetWithField | 7 | 2.1444 ns | 0.0223 ns | 0.0198 ns | 2.1434 ns | 1.00 | 0.00 | +| SpanGet | 7 | 2.3791 ns | 0.0572 ns | 0.0535 ns | 2.3924 ns | 1.11 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 8 | 3.6571 ns | 0.1017 ns | 0.2055 ns | 3.5399 ns | 1.51 | 0.07 | +| ArrayGetWithField | 8 | 2.3724 ns | 0.0196 ns | 0.0174 ns | 2.3738 ns | 1.00 | 0.00 | +| SpanGet | 8 | 2.6088 ns | 0.0644 ns | 0.0602 ns | 2.6322 ns | 1.10 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 9 | 3.8172 ns | 0.0061 ns | 0.0057 ns | 3.8155 ns | 1.46 | 0.02 | +| ArrayGetWithField | 9 | 2.6231 ns | 0.0313 ns | 0.0293 ns | 2.6143 ns | 1.00 | 0.00 | +| SpanGet | 9 | 2.9815 ns | 0.0446 ns | 0.0372 ns | 2.9849 ns | 1.14 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 10 | 4.3250 ns | 0.1161 ns | 0.2237 ns | 4.1797 ns | 1.36 | 0.07 | +| ArrayGetWithField | 10 | 3.1594 ns | 0.0278 ns | 0.0246 ns | 3.1674 ns | 1.00 | 0.00 | +| SpanGet | 10 | 2.9200 ns | 0.0834 ns | 0.0780 ns | 2.9040 ns | 0.92 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 11 | 4.8931 ns | 0.1262 ns | 0.2308 ns | 4.9847 ns | 1.44 | 0.09 | +| ArrayGetWithField | 11 | 3.3435 ns | 0.0487 ns | 0.0432 ns | 3.3299 ns | 1.00 | 0.00 | +| SpanGet | 11 | 3.3408 ns | 0.0429 ns | 0.0380 ns | 3.3398 ns | 1.00 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 12 | 5.2640 ns | 0.1334 ns | 0.2077 ns | 5.3314 ns | 1.59 | 0.06 | +| ArrayGetWithField | 12 | 3.2920 ns | 0.0112 ns | 0.0093 ns | 3.2942 ns | 1.00 | 0.00 | +| SpanGet | 12 | 3.5210 ns | 0.0979 ns | 0.0916 ns | 3.4896 ns | 1.07 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 13 | 5.3287 ns | 0.0715 ns | 0.0669 ns | 5.3427 ns | 1.34 | 0.02 | +| ArrayGetWithField | 13 | 3.9872 ns | 0.0499 ns | 0.0467 ns | 3.9771 ns | 1.00 | 0.00 | +| SpanGet | 13 | 4.0250 ns | 0.0803 ns | 0.0751 ns | 4.0180 ns | 1.01 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 14 | 5.5447 ns | 0.1082 ns | 0.0845 ns | 5.5129 ns | 1.35 | 0.03 | +| ArrayGetWithField | 14 | 4.1072 ns | 0.0352 ns | 0.0329 ns | 4.1011 ns | 1.00 | 0.00 | +| SpanGet | 14 | 4.3101 ns | 0.1149 ns | 0.2042 ns | 4.4471 ns | 1.05 | 0.06 | +| | | | | | | | | +| ArrayGetWithVar | 15 | 5.9592 ns | 0.1007 ns | 0.0942 ns | 5.9521 ns | 1.38 | 0.02 | +| ArrayGetWithField | 15 | 4.3303 ns | 0.0146 ns | 0.0137 ns | 4.3332 ns | 1.00 | 0.00 | +| SpanGet | 15 | 4.5349 ns | 0.0952 ns | 0.0795 ns | 4.5473 ns | 1.05 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 16 | 6.2242 ns | 0.1520 ns | 0.2081 ns | 6.1473 ns | 1.30 | 0.05 | +| ArrayGetWithField | 16 | 4.8017 ns | 0.0312 ns | 0.0292 ns | 4.8077 ns | 1.00 | 0.00 | +| SpanGet | 16 | 4.6087 ns | 0.1206 ns | 0.1128 ns | 4.5630 ns | 0.96 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 17 | 6.5752 ns | 0.1582 ns | 0.2057 ns | 6.5724 ns | 1.44 | 0.05 | +| ArrayGetWithField | 17 | 4.5792 ns | 0.0274 ns | 0.0243 ns | 4.5754 ns | 1.00 | 0.00 | +| SpanGet | 17 | 5.1442 ns | 0.0570 ns | 0.0533 ns | 5.1497 ns | 1.12 | 0.01 | +| | | | | | | | | +| ArrayGetWithVar | 18 | 6.6317 ns | 0.1327 ns | 0.1108 ns | 6.5870 ns | 1.17 | 0.02 | +| ArrayGetWithField | 18 | 5.6728 ns | 0.0296 ns | 0.0277 ns | 5.6707 ns | 1.00 | 0.00 | +| SpanGet | 18 | 4.9786 ns | 0.0787 ns | 0.0736 ns | 4.9597 ns | 0.88 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 19 | 6.9428 ns | 0.0477 ns | 0.0398 ns | 6.9283 ns | 1.33 | 0.01 | +| ArrayGetWithField | 19 | 5.2353 ns | 0.0249 ns | 0.0221 ns | 5.2269 ns | 1.00 | 0.00 | +| SpanGet | 19 | 5.2300 ns | 0.0300 ns | 0.0266 ns | 5.2179 ns | 1.00 | 0.01 | +| | | | | | | | | +| ArrayGetWithVar | 20 | 7.4626 ns | 0.0782 ns | 0.0653 ns | 7.4410 ns | 1.28 | 0.04 | +| ArrayGetWithField | 20 | 5.7347 ns | 0.1435 ns | 0.1815 ns | 5.6633 ns | 1.00 | 0.00 | +| SpanGet | 20 | 5.4807 ns | 0.0085 ns | 0.0075 ns | 5.4807 ns | 0.94 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 30 | 10.4216 ns | 0.2361 ns | 0.2986 ns | 10.4605 ns | 1.31 | 0.04 | +| ArrayGetWithField | 30 | 7.9253 ns | 0.0101 ns | 0.0089 ns | 7.9231 ns | 1.00 | 0.00 | +| SpanGet | 30 | 7.8815 ns | 0.0056 ns | 0.0046 ns | 7.8799 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 32 | 10.9016 ns | 0.2460 ns | 0.2928 ns | 10.9234 ns | 1.20 | 0.04 | +| ArrayGetWithField | 32 | 9.0887 ns | 0.1490 ns | 0.1393 ns | 9.0872 ns | 1.00 | 0.00 | +| SpanGet | 32 | 8.3599 ns | 0.0023 ns | 0.0018 ns | 8.3594 ns | 0.92 | 0.01 | +| | | | | | | | | +| ArrayGetWithVar | 40 | 12.1384 ns | 0.0332 ns | 0.0259 ns | 12.1259 ns | 1.18 | 0.00 | +| ArrayGetWithField | 40 | 10.2890 ns | 0.0161 ns | 0.0142 ns | 10.2858 ns | 1.00 | 0.00 | +| SpanGet | 40 | 10.2771 ns | 0.0087 ns | 0.0082 ns | 10.2725 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 50 | 14.5209 ns | 0.0094 ns | 0.0074 ns | 14.5204 ns | 1.14 | 0.00 | +| ArrayGetWithField | 50 | 12.7090 ns | 0.0251 ns | 0.0235 ns | 12.7142 ns | 1.00 | 0.00 | +| SpanGet | 50 | 12.6737 ns | 0.0104 ns | 0.0082 ns | 12.6719 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 60 | 16.9274 ns | 0.0157 ns | 0.0131 ns | 16.9289 ns | 1.12 | 0.00 | +| ArrayGetWithField | 60 | 15.0932 ns | 0.0385 ns | 0.0341 ns | 15.0869 ns | 1.00 | 0.00 | +| SpanGet | 60 | 15.0897 ns | 0.0438 ns | 0.0409 ns | 15.0743 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 80 | 22.2228 ns | 0.1792 ns | 0.1676 ns | 22.1639 ns | 1.07 | 0.01 | +| ArrayGetWithField | 80 | 20.8031 ns | 0.1991 ns | 0.1863 ns | 20.8343 ns | 1.00 | 0.00 | +| SpanGet | 80 | 20.2242 ns | 0.1555 ns | 0.1378 ns | 20.2300 ns | 0.97 | 0.01 | +| | | | | | | | | +| ArrayGetWithVar | 90 | 24.1033 ns | 0.0211 ns | 0.0176 ns | 24.0949 ns | 1.08 | 0.00 | +| ArrayGetWithField | 90 | 22.2535 ns | 0.0180 ns | 0.0160 ns | 22.2475 ns | 1.00 | 0.00 | +| SpanGet | 90 | 22.2278 ns | 0.0063 ns | 0.0052 ns | 22.2260 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 100 | 35.7519 ns | 0.1380 ns | 0.1291 ns | 35.7208 ns | 1.18 | 0.00 | +| ArrayGetWithField | 100 | 30.3591 ns | 0.0262 ns | 0.0219 ns | 30.3633 ns | 1.00 | 0.00 | +| SpanGet | 100 | 30.1375 ns | 0.0272 ns | 0.0241 ns | 30.1293 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 128 | 42.4070 ns | 0.0753 ns | 0.0668 ns | 42.3966 ns | 1.15 | 0.00 | +| ArrayGetWithField | 128 | 37.0188 ns | 0.0491 ns | 0.0436 ns | 37.0096 ns | 1.00 | 0.00 | +| SpanGet | 128 | 36.9420 ns | 0.1566 ns | 0.1465 ns | 36.8590 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 256 | 72.3880 ns | 0.5299 ns | 0.4957 ns | 72.2868 ns | 1.08 | 0.01 | +| ArrayGetWithField | 256 | 67.2595 ns | 0.1700 ns | 0.1590 ns | 67.1937 ns | 1.00 | 0.00 | +| SpanGet | 256 | 66.5708 ns | 0.1292 ns | 0.1145 ns | 66.5350 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 512 | 133.9119 ns | 0.3940 ns | 0.3290 ns | 133.9845 ns | 1.05 | 0.00 | +| ArrayGetWithField | 512 | 127.9565 ns | 0.1367 ns | 0.1141 ns | 127.9270 ns | 1.00 | 0.00 | +| SpanGet | 512 | 127.7372 ns | 0.1064 ns | 0.0943 ns | 127.7265 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 1024 | 255.4683 ns | 0.7553 ns | 0.6695 ns | 255.1568 ns | 1.02 | 0.00 | +| ArrayGetWithField | 1024 | 250.5938 ns | 0.2526 ns | 0.2239 ns | 250.5821 ns | 1.00 | 0.00 | +| SpanGet | 1024 | 250.7683 ns | 0.6080 ns | 0.5687 ns | 250.6417 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 2048 | 500.8917 ns | 1.0776 ns | 1.0080 ns | 500.4943 ns | 1.01 | 0.00 | +| ArrayGetWithField | 2048 | 495.4565 ns | 0.3916 ns | 0.3270 ns | 495.4714 ns | 1.00 | 0.00 | +| SpanGet | 2048 | 495.2572 ns | 0.4029 ns | 0.3364 ns | 495.2063 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 4096 | 992.1368 ns | 2.8754 ns | 2.6897 ns | 990.8590 ns | 1.01 | 0.00 | +| ArrayGetWithField | 4096 | 986.0712 ns | 0.8923 ns | 0.7910 ns | 985.8429 ns | 1.00 | 0.00 | +| SpanGet | 4096 | 984.9568 ns | 0.4072 ns | 0.3610 ns | 985.0019 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 8192 | 1,968.7538 ns | 0.2638 ns | 0.2060 ns | 1,968.7178 ns | 1.00 | 0.00 | +| ArrayGetWithField | 8192 | 1,966.1739 ns | 2.8904 ns | 2.5623 ns | 1,964.7451 ns | 1.00 | 0.00 | +| SpanGet | 8192 | 1,967.1070 ns | 5.1706 ns | 4.8366 ns | 1,964.2349 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 1000000 | 239,186.3037 ns | 31.2120 ns | 26.0634 ns | 239,181.6895 ns | 1.00 | 0.00 | +| ArrayGetWithField | 1000000 | 239,251.0824 ns | 190.1186 ns | 177.8370 ns | 239,231.1279 ns | 1.00 | 0.00 | +| SpanGet | 1000000 | 239,076.0725 ns | 42.7034 ns | 37.8555 ns | 239,069.7632 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 2147483591 | 513,362,891.6667 ns | 51,845.5735 ns | 40,477.6135 ns | 513,367,700.0000 ns | 1.00 | 0.00 | +| ArrayGetWithField | 2147483591 | 513,708,415.3846 ns | 214,407.5874 ns | 179,040.0460 ns | 513,678,800.0000 ns | 1.00 | 0.00 | +| SpanGet | 2147483591 | 513,460,507.1429 ns | 68,937.1375 ns | 61,110.9768 ns | 513,482,050.0000 ns | 1.00 | 0.00 | + +// * Warnings * +MultimodalDistribution + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> It seems that the distribution is bimodal (mValue = 3.76) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> It seems that the distribution can have several modes (mValue = 3.11) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> It seems that the distribution can have several modes (mValue = 3) + SpanVsArrayBenchmarks.SpanGet: Default -> It seems that the distribution is bimodal (mValue = 3.7) + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were detected (2.89 ns, 2.89 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (1.70 ns, 1.72 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (1.78 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was detected (3.22 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 3 outliers were removed (2.30 ns..2.31 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed, 3 outliers were detected (1.99 ns, 2.03 ns, 2.03 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (4.22 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were detected (2.49 ns, 2.49 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (3.18 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (5.32 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (5.54 ns..5.70 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (3.71 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was detected (3.69 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (3.84 ns, 3.94 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was detected (3.93 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (4.61 ns, 4.66 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.74 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (5.13 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (4.99 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed, 3 outliers were detected (4.78 ns, 4.82 ns, 4.83 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was detected (6.67 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (7.60 ns..7.80 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed, 3 outliers were detected (5.80 ns, 6.25 ns, 6.30 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.06 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (6.20 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (8.53 ns, 8.53 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (8.60 ns, 8.88 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (6.83 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (6.86 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (9.40 ns, 9.72 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (7.04 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 4 outliers were detected (11.40 ns..11.44 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (9.46 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (9.41 ns, 9.45 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were detected (11.72 ns, 11.76 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was detected (10.28 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (9.87 ns..9.93 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (16.07 ns..16.45 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (11.86 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (16.06 ns..16.08 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (14.23 ns..14.41 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (18.48 ns, 18.50 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (16.73 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (22.24 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (25.69 ns, 25.74 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (23.83 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (23.77 ns, 23.80 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (31.96 ns, 32.11 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (31.75 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (44.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (39.06 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (69.39 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 4 outliers were detected (135.67 ns, 135.69 ns, 137.00 ns, 137.67 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (132.07 ns, 132.46 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (130.78 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (259.74 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 3 outliers were detected (252.78 ns, 252.79 ns, 253.80 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (500.68 ns, 502.22 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (500.38 ns, 500.51 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (991.97 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 3 outliers were detected (986.70 ns, 986.75 ns, 991.81 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (1.97 us..1.98 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (1.98 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (239.28 us, 239.68 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (240.97 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (513.53 ms..514.07 ms) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (515.12 ms, 515.88 ms) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 2 outliers were detected (513.32 ms, 513.60 ms) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Median : Value separating the higher half of all measurements (50th percentile) + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec)BenchmarkDotNet=v0.13.3, OS=ubuntu 20.04 + +************************************************************************************************************************************************* +************************************************************************************************************************************************* +************************************************************************************************************************************************* + +Intel Xeon Platinum 8272CL CPU 2.60GHz, 1 CPU, 4 l +ogical and 2 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestObjectCount | Mean | Error | StdDev | Ratio | RatioSD | +|------------------ |---------------- |--------------------:|------------------:|------------------:|------:|--------:| +| ArrayGetWithVar | 1 | 1.7144 ns | 0.0035 ns | 0.0033 ns | 4.97 | 0.01 | +| ArrayGetWithField | 1 | 0.3450 ns | 0.0009 ns | 0.0008 ns | 1.00 | 0.00 | +| SpanGet | 1 | 0.3670 ns | 0.0007 ns | 0.0007 ns | 1.06 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 2 | 1.7904 ns | 0.0007 ns | 0.0006 ns | 4.58 | 0.01 | +| ArrayGetWithField | 2 | 0.3912 ns | 0.0010 ns | 0.0009 ns | 1.00 | 0.00 | +| SpanGet | 2 | 0.6750 ns | 0.0021 ns | 0.0018 ns | 1.73 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 3 | 2.4049 ns | 0.0006 ns | 0.0005 ns | 2.50 | 0.00 | +| ArrayGetWithField | 3 | 0.9626 ns | 0.0018 ns | 0.0015 ns | 1.00 | 0.00 | +| SpanGet | 3 | 1.5743 ns | 0.0010 ns | 0.0008 ns | 1.64 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 4 | 2.9208 ns | 0.0017 ns | 0.0013 ns | 1.94 | 0.00 | +| ArrayGetWithField | 4 | 1.5076 ns | 0.0024 ns | 0.0021 ns | 1.00 | 0.00 | +| SpanGet | 4 | 1.9629 ns | 0.0027 ns | 0.0024 ns | 1.30 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 5 | 3.9197 ns | 0.0028 ns | 0.0025 ns | 1.57 | 0.00 | +| ArrayGetWithField | 5 | 2.4945 ns | 0.0048 ns | 0.0042 ns | 1.00 | 0.00 | +| SpanGet | 5 | 2.7509 ns | 0.0344 ns | 0.0322 ns | 1.10 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 6 | 3.5986 ns | 0.0261 ns | 0.0232 ns | 1.72 | 0.02 | +| ArrayGetWithField | 6 | 2.0963 ns | 0.0163 ns | 0.0152 ns | 1.00 | 0.00 | +| SpanGet | 6 | 3.0419 ns | 0.0818 ns | 0.0765 ns | 1.45 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 7 | 3.9044 ns | 0.0373 ns | 0.0349 ns | 1.64 | 0.02 | +| ArrayGetWithField | 7 | 2.3830 ns | 0.0172 ns | 0.0161 ns | 1.00 | 0.00 | +| SpanGet | 7 | 3.7281 ns | 0.0007 ns | 0.0006 ns | 1.57 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 8 | 4.1182 ns | 0.0017 ns | 0.0015 ns | 1.50 | 0.00 | +| ArrayGetWithField | 8 | 2.7423 ns | 0.0008 ns | 0.0007 ns | 1.00 | 0.00 | +| SpanGet | 8 | 3.9225 ns | 0.0006 ns | 0.0005 ns | 1.43 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 9 | 4.3981 ns | 0.0023 ns | 0.0018 ns | 1.53 | 0.00 | +| ArrayGetWithField | 9 | 2.8798 ns | 0.0008 ns | 0.0007 ns | 1.00 | 0.00 | +| SpanGet | 9 | 4.2915 ns | 0.0005 ns | 0.0005 ns | 1.49 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 10 | 4.6168 ns | 0.0045 ns | 0.0042 ns | 1.40 | 0.00 | +| ArrayGetWithField | 10 | 3.3040 ns | 0.0007 ns | 0.0006 ns | 1.00 | 0.00 | +| SpanGet | 10 | 4.3824 ns | 0.0087 ns | 0.0082 ns | 1.33 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 11 | 5.0594 ns | 0.0063 ns | 0.0049 ns | 1.35 | 0.00 | +| ArrayGetWithField | 11 | 3.7469 ns | 0.0008 ns | 0.0006 ns | 1.00 | 0.00 | +| SpanGet | 11 | 4.7152 ns | 0.0148 ns | 0.0138 ns | 1.26 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 12 | 5.1340 ns | 0.0012 ns | 0.0010 ns | 1.26 | 0.00 | +| ArrayGetWithField | 12 | 4.0666 ns | 0.0080 ns | 0.0071 ns | 1.00 | 0.00 | +| SpanGet | 12 | 5.0472 ns | 0.0073 ns | 0.0069 ns | 1.24 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 13 | 5.6316 ns | 0.0015 ns | 0.0012 ns | 1.28 | 0.01 | +| ArrayGetWithField | 13 | 4.3903 ns | 0.0249 ns | 0.0221 ns | 1.00 | 0.00 | +| SpanGet | 13 | 5.3302 ns | 0.0088 ns | 0.0078 ns | 1.21 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 14 | 5.8691 ns | 0.0060 ns | 0.0056 ns | 1.22 | 0.00 | +| ArrayGetWithField | 14 | 4.7932 ns | 0.0059 ns | 0.0056 ns | 1.00 | 0.00 | +| SpanGet | 14 | 5.6502 ns | 0.0179 ns | 0.0158 ns | 1.18 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 15 | 6.6377 ns | 0.0028 ns | 0.0022 ns | 1.27 | 0.00 | +| ArrayGetWithField | 15 | 5.2252 ns | 0.0014 ns | 0.0013 ns | 1.00 | 0.00 | +| SpanGet | 15 | 5.9511 ns | 0.0158 ns | 0.0148 ns | 1.14 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 16 | 6.8981 ns | 0.0119 ns | 0.0112 ns | 1.24 | 0.00 | +| ArrayGetWithField | 16 | 5.5505 ns | 0.0114 ns | 0.0106 ns | 1.00 | 0.00 | +| SpanGet | 16 | 6.2346 ns | 0.0200 ns | 0.0177 ns | 1.12 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 17 | 7.7612 ns | 0.0088 ns | 0.0078 ns | 1.33 | 0.00 | +| ArrayGetWithField | 17 | 5.8474 ns | 0.0084 ns | 0.0078 ns | 1.00 | 0.00 | +| SpanGet | 17 | 6.5241 ns | 0.0204 ns | 0.0191 ns | 1.12 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 18 | 8.0601 ns | 0.0168 ns | 0.0149 ns | 1.24 | 0.02 | +| ArrayGetWithField | 18 | 6.4858 ns | 0.1023 ns | 0.0957 ns | 1.00 | 0.00 | +| SpanGet | 18 | 6.8394 ns | 0.0342 ns | 0.0320 ns | 1.05 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 19 | 8.5406 ns | 0.0016 ns | 0.0013 ns | 1.27 | 0.00 | +| ArrayGetWithField | 19 | 6.7132 ns | 0.0154 ns | 0.0128 ns | 1.00 | 0.00 | +| SpanGet | 19 | 7.1487 ns | 0.0293 ns | 0.0274 ns | 1.06 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 20 | 8.8478 ns | 0.0129 ns | 0.0121 ns | 1.26 | 0.00 | +| ArrayGetWithField | 20 | 7.0262 ns | 0.0066 ns | 0.0058 ns | 1.00 | 0.00 | +| SpanGet | 20 | 7.4524 ns | 0.0215 ns | 0.0190 ns | 1.06 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 30 | 12.6107 ns | 0.0573 ns | 0.0536 ns | 1.18 | 0.01 | +| ArrayGetWithField | 30 | 10.7281 ns | 0.0160 ns | 0.0150 ns | 1.00 | 0.00 | +| SpanGet | 30 | 10.5565 ns | 0.0429 ns | 0.0380 ns | 0.98 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 32 | 13.2197 ns | 0.0958 ns | 0.0896 ns | 1.15 | 0.01 | +| ArrayGetWithField | 32 | 11.5303 ns | 0.0275 ns | 0.0244 ns | 1.00 | 0.00 | +| SpanGet | 32 | 11.7189 ns | 0.0331 ns | 0.0309 ns | 1.02 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 40 | 17.1164 ns | 0.0674 ns | 0.0598 ns | 1.04 | 0.00 | +| ArrayGetWithField | 40 | 16.4160 ns | 0.0464 ns | 0.0434 ns | 1.00 | 0.00 | +| SpanGet | 40 | 14.8238 ns | 0.0468 ns | 0.0438 ns | 0.90 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 50 | 18.5728 ns | 0.0593 ns | 0.0554 ns | 1.02 | 0.00 | +| ArrayGetWithField | 50 | 18.1323 ns | 0.0233 ns | 0.0218 ns | 1.00 | 0.00 | +| SpanGet | 50 | 18.3620 ns | 0.0352 ns | 0.0312 ns | 1.01 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 60 | 27.7889 ns | 0.0589 ns | 0.0551 ns | 1.08 | 0.00 | +| ArrayGetWithField | 60 | 25.7314 ns | 0.0342 ns | 0.0320 ns | 1.00 | 0.00 | +| SpanGet | 60 | 19.3356 ns | 0.0274 ns | 0.0243 ns | 0.75 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 80 | 50.0414 ns | 0.3840 ns | 0.3592 ns | 1.16 | 0.01 | +| ArrayGetWithField | 80 | 43.3203 ns | 0.0732 ns | 0.0685 ns | 1.00 | 0.00 | +| SpanGet | 80 | 46.2991 ns | 0.1615 ns | 0.1348 ns | 1.07 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 90 | 30.3271 ns | 0.0299 ns | 0.0250 ns | 0.92 | 0.00 | +| ArrayGetWithField | 90 | 32.9228 ns | 0.0448 ns | 0.0397 ns | 1.00 | 0.00 | +| SpanGet | 90 | 30.1212 ns | 0.0279 ns | 0.0247 ns | 0.91 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 100 | 50.9207 ns | 0.0524 ns | 0.0438 ns | 1.15 | 0.00 | +| ArrayGetWithField | 100 | 44.3034 ns | 0.0203 ns | 0.0169 ns | 1.00 | 0.00 | +| SpanGet | 100 | 48.1193 ns | 0.0569 ns | 0.0533 ns | 1.09 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 128 | 63.5936 ns | 0.0735 ns | 0.0687 ns | 1.17 | 0.00 | +| ArrayGetWithField | 128 | 54.3614 ns | 0.0326 ns | 0.0305 ns | 1.00 | 0.00 | +| SpanGet | 128 | 59.9921 ns | 0.0440 ns | 0.0390 ns | 1.10 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 256 | 101.1193 ns | 0.0666 ns | 0.0556 ns | 1.00 | 0.00 | +| ArrayGetWithField | 256 | 101.0048 ns | 0.0942 ns | 0.0835 ns | 1.00 | 0.00 | +| SpanGet | 256 | 97.7434 ns | 0.0330 ns | 0.0257 ns | 0.97 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 512 | 176.4697 ns | 0.0506 ns | 0.0473 ns | 0.90 | 0.00 | +| ArrayGetWithField | 512 | 195.4723 ns | 0.0439 ns | 0.0389 ns | 1.00 | 0.00 | +| SpanGet | 512 | 173.3604 ns | 0.0621 ns | 0.0551 ns | 0.89 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 1024 | 327.4840 ns | 0.0646 ns | 0.0540 ns | 0.85 | 0.00 | +| ArrayGetWithField | 1024 | 385.4179 ns | 0.4587 ns | 0.4066 ns | 1.00 | 0.00 | +| SpanGet | 1024 | 324.5606 ns | 0.4749 ns | 0.4210 ns | 0.84 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 2048 | 630.3876 ns | 0.4305 ns | 0.4027 ns | 0.82 | 0.00 | +| ArrayGetWithField | 2048 | 764.5638 ns | 1.1467 ns | 1.0726 ns | 1.00 | 0.00 | +| SpanGet | 2048 | 627.6572 ns | 0.7236 ns | 0.6768 ns | 0.82 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 4096 | 1,234.8919 ns | 0.1193 ns | 0.0996 ns | 0.81 | 0.00 | +| ArrayGetWithField | 4096 | 1,520.9689 ns | 0.7341 ns | 0.6508 ns | 1.00 | 0.00 | +| SpanGet | 4096 | 1,232.3784 ns | 0.6339 ns | 0.5293 ns | 0.81 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 8192 | 2,444.7565 ns | 0.4708 ns | 0.3931 ns | 0.81 | 0.00 | +| ArrayGetWithField | 8192 | 3,032.8116 ns | 0.4344 ns | 0.3391 ns | 1.00 | 0.00 | +| SpanGet | 8192 | 2,442.6178 ns | 0.5773 ns | 0.4821 ns | 0.81 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 1000000 | 295,415.8654 ns | 48.2387 ns | 42.7623 ns | 0.80 | 0.00 | +| ArrayGetWithField | 1000000 | 369,338.8544 ns | 163.6322 ns | 136.6403 ns | 1.00 | 0.00 | +| SpanGet | 1000000 | 295,432.4293 ns | 186.8233 ns | 145.8593 ns | 0.80 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 2147483591 | 635,510,534.2000 ns | 1,123,953.3384 ns | 1,051,346.6501 ns | 0.80 | 0.00 | +| ArrayGetWithField | 2147483591 | 792,820,286.0714 ns | 158,248.9330 ns | 140,283.5572 ns | 1.00 | 0.00 | +| SpanGet | 2147483591 | 634,263,796.3077 ns | 73,366.8521 ns | 61,264.6443 ns | 0.80 | 0.00 | + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (2.25 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (3.68 ns, 3.69 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (2.26 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (2.55 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (4.29 ns, 4.29 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed, 3 outliers were detected (2.82 ns, 2.83 ns, 2.83 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (3.44 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (4.82 ns..4.83 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (3.38 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (3.84 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (5.80 ns, 5.91 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.38 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (5.54 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (5.61 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (6.02 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.61 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed, 3 outliers were detected (5.79 ns, 5.79 ns, 5.79 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (6.29 ns..6.29 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (4.75 ns, 4.77 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (6.16 ns, 6.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (5.18 ns, 5.18 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (6.99 ns..8.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (5.62 ns, 5.62 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (7.02 ns, 7.02 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (5.96 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (7.75 ns..7.99 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (6.43 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (7.22 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (7.56 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (8.53 ns..8.54 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (8.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.68 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.99 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (10.44 ns..10.44 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (8.62 ns, 8.93 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (8.93 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (9.38 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 2 outliers were detected (12.35 ns, 12.51 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (13.46 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (19.70 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 4 outliers were detected (20.15 ns..20.19 ns, 20.29 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (21.29 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was detected (44.96 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed, 3 outliers were detected (47.84 ns, 48.47 ns, 48.59 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (32.39 ns, 32.48 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (34.93 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (32.13 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 3 outliers were detected (52.69 ns, 52.92 ns, 52.93 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (46.28 ns, 46.29 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (63.12 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (103.46 ns, 103.46 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (103.80 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (100.34 ns..100.90 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (202.86 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (176.24 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (330.17 ns, 330.82 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (394.09 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (328.45 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (1.24 us, 1.24 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (1.52 us, 1.53 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (2.45 us, 2.45 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 3 outliers were removed (3.04 us..3.04 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (295.35 us, 295.66 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (369.92 us, 370.16 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (296.57 us..306.32 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (793.21 ms) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (634.57 ms, 634.59 ms) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec) + +************************************************************************************************************************************************* +************************************************************************************************************************************************* +************************************************************************************************************************************************* + + BenchmarkDotNet=v0.13.3, OS=Windows 11 (10.0.22621.1555) +AMD Ryzen 7 4700U with Radeon Graphics, 1 CPU, 8 logical and 8 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestObjectCount | Mean | Error | StdDev | Median | Ratio | RatioSD | +|------------------ |---------------- |----------------------:|-------------------:|-------------------:|----------------------:|------:|--------:| +| ArraySetWithVar | 1 | 1.4697 ns | 0.0243 ns | 0.0227 ns | 1.4777 ns | 4.29 | 0.29 | +| ArraySetWithField | 1 | 0.3436 ns | 0.0225 ns | 0.0210 ns | 0.3492 ns | 1.00 | 0.00 | +| SpanSet | 1 | 0.5153 ns | 0.0077 ns | 0.0069 ns | 0.5159 ns | 1.50 | 0.09 | +| | | | | | | | | +| ArraySetWithVar | 2 | 1.7058 ns | 0.0124 ns | 0.0116 ns | 1.7081 ns | 2.44 | 0.02 | +| ArraySetWithField | 2 | 0.7001 ns | 0.0017 ns | 0.0013 ns | 0.7000 ns | 1.00 | 0.00 | +| SpanSet | 2 | 1.1681 ns | 0.0445 ns | 0.0417 ns | 1.1618 ns | 1.68 | 0.07 | +| | | | | | | | | +| ArraySetWithVar | 3 | 2.1158 ns | 0.0724 ns | 0.1589 ns | 2.1493 ns | 2.18 | 0.16 | +| ArraySetWithField | 3 | 0.9698 ns | 0.0024 ns | 0.0020 ns | 0.9697 ns | 1.00 | 0.00 | +| SpanSet | 3 | 1.5822 ns | 0.0164 ns | 0.0153 ns | 1.5835 ns | 1.63 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 4 | 2.2945 ns | 0.0055 ns | 0.0049 ns | 2.2936 ns | 1.80 | 0.00 | +| ArraySetWithField | 4 | 1.2779 ns | 0.0038 ns | 0.0029 ns | 1.2779 ns | 1.00 | 0.00 | +| SpanSet | 4 | 2.0907 ns | 0.0125 ns | 0.0111 ns | 2.0963 ns | 1.64 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 5 | 2.7734 ns | 0.0494 ns | 0.0507 ns | 2.7562 ns | 1.60 | 0.03 | +| ArraySetWithField | 5 | 1.7355 ns | 0.0096 ns | 0.0080 ns | 1.7390 ns | 1.00 | 0.00 | +| SpanSet | 5 | 2.8583 ns | 0.0867 ns | 0.1586 ns | 2.7817 ns | 1.65 | 0.10 | +| | | | | | | | | +| ArraySetWithVar | 6 | 3.8121 ns | 0.0173 ns | 0.0154 ns | 3.8119 ns | 1.82 | 0.02 | +| ArraySetWithField | 6 | 2.0951 ns | 0.0165 ns | 0.0146 ns | 2.0946 ns | 1.00 | 0.00 | +| SpanSet | 6 | 3.3108 ns | 0.0354 ns | 0.0331 ns | 3.3259 ns | 1.58 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 7 | 3.4567 ns | 0.0165 ns | 0.0129 ns | 3.4596 ns | 1.41 | 0.01 | +| ArraySetWithField | 7 | 2.4506 ns | 0.0052 ns | 0.0046 ns | 2.4511 ns | 1.00 | 0.00 | +| SpanSet | 7 | 3.6886 ns | 0.0219 ns | 0.0183 ns | 3.6959 ns | 1.51 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 8 | 3.8263 ns | 0.0097 ns | 0.0086 ns | 3.8244 ns | 1.40 | 0.01 | +| ArraySetWithField | 8 | 2.7420 ns | 0.0113 ns | 0.0106 ns | 2.7401 ns | 1.00 | 0.00 | +| SpanSet | 8 | 4.3303 ns | 0.0419 ns | 0.0392 ns | 4.3372 ns | 1.58 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 9 | 4.0671 ns | 0.0144 ns | 0.0120 ns | 4.0669 ns | 1.32 | 0.00 | +| ArraySetWithField | 9 | 3.0772 ns | 0.0066 ns | 0.0055 ns | 3.0759 ns | 1.00 | 0.00 | +| SpanSet | 9 | 4.7950 ns | 0.0458 ns | 0.0429 ns | 4.7805 ns | 1.56 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 10 | 5.0931 ns | 0.0349 ns | 0.0326 ns | 5.1000 ns | 1.41 | 0.02 | +| ArraySetWithField | 10 | 3.6005 ns | 0.0558 ns | 0.0522 ns | 3.6177 ns | 1.00 | 0.00 | +| SpanSet | 10 | 5.3014 ns | 0.1238 ns | 0.1158 ns | 5.2656 ns | 1.47 | 0.04 | +| | | | | | | | | +| ArraySetWithVar | 11 | 4.6538 ns | 0.1019 ns | 0.0953 ns | 4.6584 ns | 1.27 | 0.03 | +| ArraySetWithField | 11 | 3.6546 ns | 0.0123 ns | 0.0115 ns | 3.6556 ns | 1.00 | 0.00 | +| SpanSet | 11 | 5.6726 ns | 0.0291 ns | 0.0272 ns | 5.6684 ns | 1.55 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 12 | 5.0027 ns | 0.0697 ns | 0.0652 ns | 4.9778 ns | 1.25 | 0.02 | +| ArraySetWithField | 12 | 4.0128 ns | 0.0249 ns | 0.0233 ns | 4.0165 ns | 1.00 | 0.00 | +| SpanSet | 12 | 6.0483 ns | 0.0539 ns | 0.0421 ns | 6.0564 ns | 1.51 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 13 | 5.1638 ns | 0.0386 ns | 0.0342 ns | 5.1679 ns | 1.23 | 0.01 | +| ArraySetWithField | 13 | 4.1868 ns | 0.0080 ns | 0.0071 ns | 4.1877 ns | 1.00 | 0.00 | +| SpanSet | 13 | 6.8180 ns | 0.1338 ns | 0.1252 ns | 6.8105 ns | 1.63 | 0.03 | +| | | | | | | | | +| ArraySetWithVar | 14 | 5.3945 ns | 0.0084 ns | 0.0065 ns | 5.3939 ns | 1.17 | 0.00 | +| ArraySetWithField | 14 | 4.6050 ns | 0.0163 ns | 0.0136 ns | 4.6079 ns | 1.00 | 0.00 | +| SpanSet | 14 | 7.4360 ns | 0.0431 ns | 0.0360 ns | 7.4488 ns | 1.61 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 15 | 6.0572 ns | 0.1460 ns | 0.2046 ns | 6.0832 ns | 1.25 | 0.04 | +| ArraySetWithField | 15 | 4.8496 ns | 0.0054 ns | 0.0042 ns | 4.8491 ns | 1.00 | 0.00 | +| SpanSet | 15 | 7.9418 ns | 0.0164 ns | 0.0137 ns | 7.9376 ns | 1.64 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 16 | 6.4385 ns | 0.1580 ns | 0.2412 ns | 6.3191 ns | 1.26 | 0.05 | +| ArraySetWithField | 16 | 5.1288 ns | 0.0365 ns | 0.0324 ns | 5.1361 ns | 1.00 | 0.00 | +| SpanSet | 16 | 8.8905 ns | 0.0499 ns | 0.0466 ns | 8.8896 ns | 1.73 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 17 | 6.6477 ns | 0.1621 ns | 0.1665 ns | 6.6454 ns | 1.23 | 0.04 | +| ArraySetWithField | 17 | 5.3947 ns | 0.1035 ns | 0.0864 ns | 5.3538 ns | 1.00 | 0.00 | +| SpanSet | 17 | 8.5048 ns | 0.0249 ns | 0.0221 ns | 8.5075 ns | 1.58 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 18 | 6.6377 ns | 0.1267 ns | 0.1244 ns | 6.5944 ns | 1.18 | 0.02 | +| ArraySetWithField | 18 | 5.6304 ns | 0.0105 ns | 0.0093 ns | 5.6274 ns | 1.00 | 0.00 | +| SpanSet | 18 | 8.9234 ns | 0.0360 ns | 0.0319 ns | 8.9253 ns | 1.58 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 19 | 7.0242 ns | 0.0154 ns | 0.0120 ns | 7.0265 ns | 1.16 | 0.00 | +| ArraySetWithField | 19 | 6.0558 ns | 0.0153 ns | 0.0136 ns | 6.0505 ns | 1.00 | 0.00 | +| SpanSet | 19 | 9.6918 ns | 0.0267 ns | 0.0249 ns | 9.6928 ns | 1.60 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 20 | 7.3951 ns | 0.1098 ns | 0.1027 ns | 7.3463 ns | 1.17 | 0.01 | +| ArraySetWithField | 20 | 6.3081 ns | 0.0147 ns | 0.0123 ns | 6.3062 ns | 1.00 | 0.00 | +| SpanSet | 20 | 10.3341 ns | 0.0067 ns | 0.0056 ns | 10.3315 ns | 1.64 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 30 | 9.7678 ns | 0.0492 ns | 0.0460 ns | 9.7524 ns | 1.06 | 0.01 | +| ArraySetWithField | 30 | 9.2401 ns | 0.0386 ns | 0.0342 ns | 9.2511 ns | 1.00 | 0.00 | +| SpanSet | 30 | 15.2144 ns | 0.0390 ns | 0.0345 ns | 15.2198 ns | 1.65 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 32 | 11.5139 ns | 0.2307 ns | 0.2158 ns | 11.3850 ns | 1.19 | 0.02 | +| ArraySetWithField | 32 | 9.7155 ns | 0.0127 ns | 0.0112 ns | 9.7123 ns | 1.00 | 0.00 | +| SpanSet | 32 | 16.0872 ns | 0.0204 ns | 0.0170 ns | 16.0871 ns | 1.66 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 40 | 12.2375 ns | 0.0264 ns | 0.0247 ns | 12.2253 ns | 1.01 | 0.00 | +| ArraySetWithField | 40 | 12.1236 ns | 0.0144 ns | 0.0134 ns | 12.1221 ns | 1.00 | 0.00 | +| SpanSet | 40 | 19.9416 ns | 0.0273 ns | 0.0255 ns | 19.9322 ns | 1.64 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 50 | 14.6316 ns | 0.0303 ns | 0.0268 ns | 14.6241 ns | 0.94 | 0.00 | +| ArraySetWithField | 50 | 15.5678 ns | 0.0862 ns | 0.0764 ns | 15.5547 ns | 1.00 | 0.00 | +| SpanSet | 50 | 24.7167 ns | 0.0153 ns | 0.0119 ns | 24.7160 ns | 1.59 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 60 | 17.0088 ns | 0.0186 ns | 0.0165 ns | 17.0064 ns | 0.92 | 0.01 | +| ArraySetWithField | 60 | 18.5370 ns | 0.1463 ns | 0.1368 ns | 18.5426 ns | 1.00 | 0.00 | +| SpanSet | 60 | 29.5779 ns | 0.1183 ns | 0.1107 ns | 29.6144 ns | 1.60 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 80 | 24.1961 ns | 0.1555 ns | 0.1455 ns | 24.1733 ns | 0.91 | 0.01 | +| ArraySetWithField | 80 | 26.4982 ns | 0.1534 ns | 0.1435 ns | 26.4651 ns | 1.00 | 0.00 | +| SpanSet | 80 | 40.4041 ns | 0.4015 ns | 0.3755 ns | 40.3401 ns | 1.52 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 90 | 24.2325 ns | 0.0650 ns | 0.0577 ns | 24.2251 ns | 0.72 | 0.01 | +| ArraySetWithField | 90 | 33.6785 ns | 0.2283 ns | 0.2136 ns | 33.7178 ns | 1.00 | 0.00 | +| SpanSet | 90 | 43.9471 ns | 0.1354 ns | 0.1057 ns | 43.9477 ns | 1.30 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 100 | 34.6069 ns | 0.3842 ns | 0.3593 ns | 34.7487 ns | 0.97 | 0.01 | +| ArraySetWithField | 100 | 35.5776 ns | 0.1210 ns | 0.1131 ns | 35.5837 ns | 1.00 | 0.00 | +| SpanSet | 100 | 53.7344 ns | 0.1538 ns | 0.1439 ns | 53.7185 ns | 1.51 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 128 | 41.4045 ns | 0.4361 ns | 0.4079 ns | 41.5116 ns | 0.95 | 0.01 | +| ArraySetWithField | 128 | 43.6934 ns | 0.1310 ns | 0.1162 ns | 43.6749 ns | 1.00 | 0.00 | +| SpanSet | 128 | 66.5672 ns | 0.1397 ns | 0.1238 ns | 66.5548 ns | 1.52 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 256 | 70.9427 ns | 0.4782 ns | 0.4473 ns | 71.0490 ns | 0.89 | 0.01 | +| ArraySetWithField | 256 | 80.0742 ns | 0.2967 ns | 0.2631 ns | 80.0270 ns | 1.00 | 0.00 | +| SpanSet | 256 | 127.5915 ns | 0.4899 ns | 0.4343 ns | 127.3657 ns | 1.59 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 512 | 132.1828 ns | 0.3919 ns | 0.3474 ns | 132.2670 ns | 0.86 | 0.00 | +| ArraySetWithField | 512 | 154.2140 ns | 0.3738 ns | 0.3497 ns | 154.2198 ns | 1.00 | 0.00 | +| SpanSet | 512 | 250.3647 ns | 0.5664 ns | 0.5021 ns | 250.0886 ns | 1.62 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 1024 | 256.1749 ns | 1.2610 ns | 1.1179 ns | 255.8400 ns | 0.84 | 0.00 | +| ArraySetWithField | 1024 | 303.9608 ns | 0.7641 ns | 0.7148 ns | 303.6921 ns | 1.00 | 0.00 | +| SpanSet | 1024 | 471.4042 ns | 0.9878 ns | 0.9240 ns | 471.5121 ns | 1.55 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 2048 | 501.2071 ns | 1.4541 ns | 1.2890 ns | 500.8381 ns | 0.83 | 0.00 | +| ArraySetWithField | 2048 | 600.8989 ns | 2.7570 ns | 2.4440 ns | 600.8161 ns | 1.00 | 0.00 | +| SpanSet | 2048 | 935.1442 ns | 1.5942 ns | 1.4912 ns | 934.6576 ns | 1.56 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 4096 | 996.5799 ns | 0.9021 ns | 0.7997 ns | 996.5021 ns | 0.83 | 0.00 | +| ArraySetWithField | 4096 | 1,201.1907 ns | 4.7698 ns | 4.4616 ns | 1,202.4870 ns | 1.00 | 0.00 | +| SpanSet | 4096 | 1,890.3773 ns | 6.1361 ns | 5.7397 ns | 1,890.7349 ns | 1.57 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 8192 | 1,987.9107 ns | 8.2600 ns | 6.8975 ns | 1,986.8454 ns | 0.83 | 0.00 | +| ArraySetWithField | 8192 | 2,399.1963 ns | 3.6931 ns | 2.8833 ns | 2,398.9801 ns | 1.00 | 0.00 | +| SpanSet | 8192 | 3,785.9897 ns | 21.6577 ns | 18.0852 ns | 3,782.0320 ns | 1.58 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 1000000 | 1,095,660.7117 ns | 21,714.0404 ns | 33,806.1449 ns | 1,083,141.5039 ns | 0.98 | 0.03 | +| ArraySetWithField | 1000000 | 1,111,976.3359 ns | 20,906.9048 ns | 27,910.1174 ns | 1,101,771.0938 ns | 1.00 | 0.00 | +| SpanSet | 1000000 | 1,114,171.4583 ns | 16,801.4894 ns | 15,716.1236 ns | 1,117,650.9766 ns | 1.00 | 0.04 | +| | | | | | | | | +| ArraySetWithVar | 2147483591 | 2,211,687,771.4286 ns | 16,208,703.4177 ns | 14,368,593.3923 ns | 2,215,022,850.0000 ns | 0.99 | 0.01 | +| ArraySetWithField | 2147483591 | 2,234,781,915.3846 ns | 14,747,844.4235 ns | 12,315,118.0269 ns | 2,232,597,000.0000 ns | 1.00 | 0.00 | +| SpanSet | 2147483591 | 2,256,799,913.3333 ns | 29,236,562.1000 ns | 27,347,898.3280 ns | 2,241,684,800.0000 ns | 1.01 | 0.02 | + +// * Warnings * +MultimodalDistribution + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> It seems that the distribution is bimodal (mValue = 3.78) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> It seems that the distribution is bimodal (mValue = 3.5) + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were detected (2.93 ns, 2.96 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (1.81 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 2 outliers were detected (2.01 ns, 2.05 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was detected (3.19 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (2.22 ns..2.28 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (2.49 ns, 2.49 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was detected (3.06 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (3.82 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed, 4 outliers were detected (2.77 ns, 2.79 ns..2.82 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 3 outliers were detected (3.57 ns, 3.57 ns, 3.62 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 4 outliers were removed (4.57 ns..4.77 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed, 5 outliers were detected (3.23 ns..3.23 ns, 3.26 ns, 3.27 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (4.84 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (5.37 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (3.72 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 3 outliers were removed, 4 outliers were detected (4.92 ns, 4.99 ns..5.39 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (3.98 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed, 4 outliers were detected (5.15 ns, 5.17 ns, 5.24 ns, 5.25 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (5.47 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (5.63 ns, 5.69 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (4.62 ns, 4.67 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was detected (7.12 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (5.46 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 3 outliers were removed, 5 outliers were detected (7.48 ns, 7.48 ns, 7.69 ns..8.06 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (6.94 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (5.71 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 3 outliers were removed (6.93 ns..7.40 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed, 3 outliers were detected (6.07 ns, 6.14 ns, 6.14 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed, 5 outliers were detected (8.86 ns..8.92 ns, 8.99 ns, 9.00 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (6.37 ns..6.42 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (9.52 ns, 9.60 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (6.92 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (7.52 ns, 7.56 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 2 outliers were detected (9.95 ns, 10.07 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 3 outliers were removed (8.68 ns..8.91 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (7.16 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 2 outliers were detected (10.35 ns, 10.52 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 3 outliers were removed (8.69 ns..8.92 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (7.64 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was detected (11.12 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (7.96 ns, 7.97 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (11.87 ns, 11.88 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed, 3 outliers were detected (10.65 ns, 10.72 ns, 10.81 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (16.84 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (11.28 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed, 3 outliers were detected (17.55 ns, 17.65 ns, 17.65 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (16.20 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (18.37 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 3 outliers were removed (26.29 ns..26.37 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (18.58 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (27.68 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (26.02 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 3 outliers were removed (45.90 ns..45.93 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was detected (35.18 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (36.80 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was detected (41.98 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed, 2 outliers were detected (44.93 ns, 45.65 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (69.12 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (82.86 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (131.98 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (133.69 ns, 136.28 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (257.71 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (262.80 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (507.27 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed, 2 outliers were detected (598.19 ns, 612.01 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (1.00 us) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (1.19 us) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (2.04 us, 2.08 us) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (2.42 us..2.44 us) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (3.85 us, 3.94 us) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 4 outliers were removed (1.22 ms..1.35 ms) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were detected (1.08 ms, 1.08 ms) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (2.18 s, 2.26 s) SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (2.28 s, 2.28 s) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Median : Value separating the higher half of all measurements (50th percentile) + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec) From 85c92ee17d0e3f05c783470d5aaf89c312eff9b7 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 22:10:39 +0200 Subject: [PATCH 239/305] Benchmarks updated. --- .../Results/AllInOneBenchmarks.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt index 97593b9..bf3a53a 100644 --- a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt +++ b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt @@ -31,10 +31,12 @@ | RecyclableArrayList_SetItem | 2,547,059.6540 ns | 9,002.9953 ns | 7,980.9208 ns | 2,542,056.8359 ns | 2.170 | 0.01 | - | - | - | 2 B | 0.000 | | RecyclableList_SetItem | 2,326,771.9866 ns | 3,186.0942 ns | 2,824.3895 ns | 2,326,636.7188 ns | 1.981 | 0.01 | - | - | - | 2 B | 0.000 | -| List_Add | 6,507,973.8528 ns | 130,648.8061 ns | 374,855.9157 ns | 6,502,201.1719 ns | 5.457 | 0.23 | 156.2500 | 93.7500 | 93.7500 | 8389554 B | 2.097 | -| PooledList_Add | 3,525,004.4922 ns | 4,824.6367 ns | 4,028.7902 ns | 3,525,046.6797 ns | 3.002 | 0.01 | - | - | - | 60 B | 0.000 | -| RecyclableArrayList_Add | 3,727,375.5709 ns | 3,512.8985 ns | 2,933.4293 ns | 3,728,521.8750 ns | 3.175 | 0.01 | - | - | - | 1226 B | 0.000 | -| RecyclableList_Add | 2,951,985.0651 ns | 4,403.2060 ns | 4,118.7616 ns | 2,952,207.6172 ns | 2.514 | 0.01 | - | - | - | 730 B | 0.000 | +| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|------------------------------------------------------------ |----------:|----------:|----------:|------:|--------:|---------:|--------:|--------:|-----------:|------------:| +| List_Add | 11.002 ms | 0.2189 ms | 0.2996 ms | 1.36 | 0.04 | 140.6250 | 93.7500 | 93.7500 | 16778117 B | 236,311.51 | +| PooledList_Add | 8.117 ms | 0.0115 ms | 0.0102 ms | 1.00 | 0.00 | - | - | - | 71 B | 1.00 | +| RecyclableArrayList_Add | 8.771 ms | 0.0169 ms | 0.0141 ms | 1.08 | 0.00 | - | - | - | 1233 B | 17.37 | +| RecyclableList_Add | 6.024 ms | 0.1101 ms | 0.1030 ms | 0.74 | 0.01 | - | - | - | 3317 B | 46.72 | | List_Add_WithCapacity | 3,724,682.0668 ns | 88,719.4866 ns | 260,198.8962 ns | 3,717,873.0469 ns | 3.326 | 0.09 | 50.7813 | 50.7813 | 50.7813 | 4000368 B | 1.000 | | PooledList_Add_WithCapacity | 2,575,618.8337 ns | 5,277.9410 ns | 4,678.7572 ns | 2,575,805.2734 ns | 2.194 | 0.01 | - | - | - | 60 B | 0.000 | From 2d55e537d07a6a9967c851626be7d205a9f75b3e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 22:13:21 +0200 Subject: [PATCH 240/305] Tiny cleanup --- Recyclable.Collections/RecyclableList.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 0a00087..9f2527d 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -980,10 +980,11 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndex - : _longCount <= _blockSize - ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) - : checked((int)DoIndexOfParallel(item)); + ? ItemNotFoundIndex + //// Optimization for tiny collections to avoid any vars etc. + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOfParallel(item)); public void Insert(int index, T item) => throw new NotSupportedException(); From 3b73a2d87709f53b3ee6263c809b5c1a410d60c7 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 22:14:36 +0200 Subject: [PATCH 241/305] Benchmark results updated. --- .../POC/SpanVsArrayBenchmarks.txt | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt index 6fb1573..09b9d3d 100644 --- a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt +++ b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt @@ -991,3 +991,234 @@ Outliers Ratio : Mean of the ratio distribution ([Current]/[Baseline]) RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) 1 ns : 1 Nanosecond (0.000000001 sec) + +************************************************************************************************************************************************* +************************************************************************************************************************************************* +************************************************************************************************************************************************* + +BenchmarkDotNet=v0.13.3, OS=ubuntu 20.04 +Intel Xeon Platinum 8272CL CPU 2.60GHz, 1 CPU, 4 logical and 2 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestObjectCount | Mean | Error | StdDev | Median | Ratio | RatioSD | +|------------------ |---------------- |------------------------:|----------------------:|----------------------:|------------------------:|------:|--------:| +| ArraySetWithVar | 1 | 1.6005 ns | 0.0025 ns | 0.0022 ns | 1.6007 ns | 4.61 | 0.03 | +| ArraySetWithField | 1 | 0.3466 ns | 0.0027 ns | 0.0025 ns | 0.3478 ns | 1.00 | 0.00 | +| SpanSet | 1 | 0.7008 ns | 0.0007 ns | 0.0006 ns | 0.7008 ns | 2.02 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 2 | 1.9617 ns | 0.0028 ns | 0.0026 ns | 1.9621 ns | 2.97 | 0.01 | +| ArraySetWithField | 2 | 0.6612 ns | 0.0016 ns | 0.0013 ns | 0.6610 ns | 1.00 | 0.00 | +| SpanSet | 2 | 0.9224 ns | 0.0009 ns | 0.0009 ns | 0.9223 ns | 1.40 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 3 | 3.9180 ns | 0.0178 ns | 0.0166 ns | 3.9195 ns | 3.07 | 0.02 | +| ArraySetWithField | 3 | 1.2773 ns | 0.0067 ns | 0.0056 ns | 1.2749 ns | 1.00 | 0.00 | +| SpanSet | 3 | 1.6728 ns | 0.0065 ns | 0.0060 ns | 1.6733 ns | 1.31 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 4 | 3.0629 ns | 0.0035 ns | 0.0032 ns | 3.0636 ns | 1.66 | 0.00 | +| ArraySetWithField | 4 | 1.8461 ns | 0.0032 ns | 0.0030 ns | 1.8457 ns | 1.00 | 0.00 | +| SpanSet | 4 | 2.2457 ns | 0.0045 ns | 0.0039 ns | 2.2458 ns | 1.22 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 5 | 3.9794 ns | 0.0025 ns | 0.0022 ns | 3.9800 ns | 1.32 | 0.05 | +| ArraySetWithField | 5 | 3.0059 ns | 0.0939 ns | 0.1153 ns | 3.0882 ns | 1.00 | 0.00 | +| SpanSet | 5 | 3.1000 ns | 0.0028 ns | 0.0023 ns | 3.0997 ns | 1.03 | 0.04 | +| | | | | | | | | +| ArraySetWithVar | 6 | 3.5894 ns | 0.0089 ns | 0.0079 ns | 3.5899 ns | 1.49 | 0.02 | +| ArraySetWithField | 6 | 2.4166 ns | 0.0354 ns | 0.0331 ns | 2.4147 ns | 1.00 | 0.00 | +| SpanSet | 6 | 2.7727 ns | 0.0181 ns | 0.0161 ns | 2.7759 ns | 1.15 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 7 | 3.8705 ns | 0.0090 ns | 0.0084 ns | 3.8673 ns | 1.16 | 0.00 | +| ArraySetWithField | 7 | 3.3283 ns | 0.0074 ns | 0.0069 ns | 3.3268 ns | 1.00 | 0.00 | +| SpanSet | 7 | 3.1766 ns | 0.0102 ns | 0.0085 ns | 3.1786 ns | 0.95 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 8 | 4.1455 ns | 0.0030 ns | 0.0028 ns | 4.1448 ns | 1.30 | 0.00 | +| ArraySetWithField | 8 | 3.1821 ns | 0.0027 ns | 0.0024 ns | 3.1821 ns | 1.00 | 0.00 | +| SpanSet | 8 | 3.6841 ns | 0.0079 ns | 0.0074 ns | 3.6839 ns | 1.16 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 9 | 4.4378 ns | 0.0022 ns | 0.0019 ns | 4.4377 ns | 1.14 | 0.00 | +| ArraySetWithField | 9 | 3.8759 ns | 0.0029 ns | 0.0027 ns | 3.8764 ns | 1.00 | 0.00 | +| SpanSet | 9 | 4.1472 ns | 0.0158 ns | 0.0147 ns | 4.1470 ns | 1.07 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 10 | 4.7189 ns | 0.0038 ns | 0.0032 ns | 4.7184 ns | 1.15 | 0.00 | +| ArraySetWithField | 10 | 4.1148 ns | 0.0029 ns | 0.0024 ns | 4.1144 ns | 1.00 | 0.00 | +| SpanSet | 10 | 4.4050 ns | 0.0279 ns | 0.0233 ns | 4.3943 ns | 1.07 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 11 | 5.0961 ns | 0.0074 ns | 0.0062 ns | 5.0969 ns | 1.03 | 0.00 | +| ArraySetWithField | 11 | 4.9518 ns | 0.0044 ns | 0.0039 ns | 4.9520 ns | 1.00 | 0.00 | +| SpanSet | 11 | 4.9052 ns | 0.0329 ns | 0.0275 ns | 4.8949 ns | 0.99 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 12 | 5.2174 ns | 0.0074 ns | 0.0066 ns | 5.2167 ns | 0.92 | 0.00 | +| ArraySetWithField | 12 | 5.6778 ns | 0.0081 ns | 0.0076 ns | 5.6778 ns | 1.00 | 0.00 | +| SpanSet | 12 | 5.4086 ns | 0.0486 ns | 0.0455 ns | 5.4007 ns | 0.95 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 13 | 5.6888 ns | 0.0095 ns | 0.0084 ns | 5.6875 ns | 0.94 | 0.00 | +| ArraySetWithField | 13 | 6.0615 ns | 0.0056 ns | 0.0049 ns | 6.0600 ns | 1.00 | 0.00 | +| SpanSet | 13 | 5.7281 ns | 0.0331 ns | 0.0309 ns | 5.7292 ns | 0.94 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 14 | 5.8935 ns | 0.0120 ns | 0.0106 ns | 5.8901 ns | 0.93 | 0.00 | +| ArraySetWithField | 14 | 6.3079 ns | 0.0156 ns | 0.0146 ns | 6.3124 ns | 1.00 | 0.00 | +| SpanSet | 14 | 6.0179 ns | 0.0182 ns | 0.0170 ns | 6.0210 ns | 0.95 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 15 | 6.6929 ns | 0.0119 ns | 0.0112 ns | 6.6895 ns | 1.00 | 0.00 | +| ArraySetWithField | 15 | 6.6939 ns | 0.0056 ns | 0.0050 ns | 6.6924 ns | 1.00 | 0.00 | +| SpanSet | 15 | 6.5041 ns | 0.0192 ns | 0.0170 ns | 6.5039 ns | 0.97 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 16 | 6.9466 ns | 0.0123 ns | 0.0115 ns | 6.9451 ns | 0.99 | 0.00 | +| ArraySetWithField | 16 | 6.9918 ns | 0.0161 ns | 0.0126 ns | 6.9904 ns | 1.00 | 0.00 | +| SpanSet | 16 | 6.8335 ns | 0.0129 ns | 0.0115 ns | 6.8350 ns | 0.98 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 17 | 7.8803 ns | 0.1539 ns | 0.1364 ns | 7.8157 ns | 1.07 | 0.02 | +| ArraySetWithField | 17 | 7.3937 ns | 0.0072 ns | 0.0067 ns | 7.3923 ns | 1.00 | 0.00 | +| SpanSet | 17 | 7.3889 ns | 0.0295 ns | 0.0276 ns | 7.3834 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 18 | 8.0929 ns | 0.0084 ns | 0.0070 ns | 8.0907 ns | 1.04 | 0.00 | +| ArraySetWithField | 18 | 7.8115 ns | 0.0064 ns | 0.0057 ns | 7.8126 ns | 1.00 | 0.00 | +| SpanSet | 18 | 7.8917 ns | 0.1759 ns | 0.1646 ns | 7.8150 ns | 1.01 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 19 | 8.5985 ns | 0.0268 ns | 0.0237 ns | 8.5871 ns | 1.05 | 0.02 | +| ArraySetWithField | 19 | 8.2165 ns | 0.1507 ns | 0.1410 ns | 8.1337 ns | 1.00 | 0.00 | +| SpanSet | 19 | 8.4661 ns | 0.2031 ns | 0.2494 ns | 8.3056 ns | 1.04 | 0.03 | +| | | | | | | | | +| ArraySetWithVar | 20 | 8.8796 ns | 0.0080 ns | 0.0075 ns | 8.8776 ns | 1.03 | 0.01 | +| ArraySetWithField | 20 | 8.6620 ns | 0.0694 ns | 0.0615 ns | 8.6587 ns | 1.00 | 0.00 | +| SpanSet | 20 | 8.5786 ns | 0.0131 ns | 0.0123 ns | 8.5786 ns | 0.99 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 30 | 12.2340 ns | 0.0405 ns | 0.0359 ns | 12.2361 ns | 0.93 | 0.00 | +| ArraySetWithField | 30 | 13.0996 ns | 0.0613 ns | 0.0543 ns | 13.1167 ns | 1.00 | 0.00 | +| SpanSet | 30 | 12.9861 ns | 0.0075 ns | 0.0070 ns | 12.9860 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 32 | 13.0265 ns | 0.0452 ns | 0.0423 ns | 13.0312 ns | 0.81 | 0.01 | +| ArraySetWithField | 32 | 16.0182 ns | 0.1015 ns | 0.0949 ns | 15.9814 ns | 1.00 | 0.00 | +| SpanSet | 32 | 13.9318 ns | 0.0134 ns | 0.0126 ns | 13.9300 ns | 0.87 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 40 | 15.9772 ns | 0.0802 ns | 0.0750 ns | 15.9619 ns | 0.81 | 0.00 | +| ArraySetWithField | 40 | 19.8427 ns | 0.0528 ns | 0.0494 ns | 19.8337 ns | 1.00 | 0.00 | +| SpanSet | 40 | 18.5625 ns | 0.0147 ns | 0.0138 ns | 18.5616 ns | 0.94 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 50 | 19.0897 ns | 0.0472 ns | 0.0442 ns | 19.1012 ns | 0.80 | 0.00 | +| ArraySetWithField | 50 | 23.8520 ns | 0.0708 ns | 0.0662 ns | 23.8558 ns | 1.00 | 0.00 | +| SpanSet | 50 | 23.5046 ns | 0.0227 ns | 0.0201 ns | 23.4986 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 60 | 23.4799 ns | 0.1172 ns | 0.1096 ns | 23.4780 ns | 0.87 | 0.00 | +| ArraySetWithField | 60 | 26.8609 ns | 0.0311 ns | 0.0243 ns | 26.8626 ns | 1.00 | 0.00 | +| SpanSet | 60 | 29.1054 ns | 0.0204 ns | 0.0181 ns | 29.1026 ns | 1.08 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 80 | 50.2605 ns | 0.1258 ns | 0.1115 ns | 50.2455 ns | 1.07 | 0.00 | +| ArraySetWithField | 80 | 46.8658 ns | 0.0728 ns | 0.0645 ns | 46.8692 ns | 1.00 | 0.00 | +| SpanSet | 80 | 51.4484 ns | 0.0914 ns | 0.0855 ns | 51.4708 ns | 1.10 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 90 | 36.7051 ns | 0.1824 ns | 0.1706 ns | 36.7614 ns | 0.92 | 0.00 | +| ArraySetWithField | 90 | 39.6823 ns | 0.0930 ns | 0.0870 ns | 39.6612 ns | 1.00 | 0.00 | +| SpanSet | 90 | 43.4244 ns | 0.0195 ns | 0.0182 ns | 43.4271 ns | 1.09 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 100 | 51.3160 ns | 0.0568 ns | 0.0504 ns | 51.3058 ns | 1.00 | 0.00 | +| ArraySetWithField | 100 | 51.3570 ns | 0.0377 ns | 0.0353 ns | 51.3562 ns | 1.00 | 0.00 | +| SpanSet | 100 | 57.7251 ns | 0.0708 ns | 0.0662 ns | 57.7026 ns | 1.12 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 128 | 61.5201 ns | 0.0489 ns | 0.0434 ns | 61.5210 ns | 0.97 | 0.00 | +| ArraySetWithField | 128 | 63.5051 ns | 0.0859 ns | 0.0804 ns | 63.5097 ns | 1.00 | 0.00 | +| SpanSet | 128 | 71.3592 ns | 0.1269 ns | 0.1187 ns | 71.3527 ns | 1.12 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 256 | 105.8192 ns | 0.0383 ns | 0.0358 ns | 105.8335 ns | 0.88 | 0.00 | +| ArraySetWithField | 256 | 119.9394 ns | 0.0760 ns | 0.0711 ns | 119.9179 ns | 1.00 | 0.00 | +| SpanSet | 256 | 135.2443 ns | 0.1709 ns | 0.1599 ns | 135.2831 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 512 | 210.0185 ns | 0.1510 ns | 0.1339 ns | 209.9837 ns | 0.90 | 0.00 | +| ArraySetWithField | 512 | 233.1248 ns | 0.0516 ns | 0.0431 ns | 233.1158 ns | 1.00 | 0.00 | +| SpanSet | 512 | 263.0390 ns | 0.1591 ns | 0.1410 ns | 263.0316 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 1024 | 417.8774 ns | 0.2947 ns | 0.2613 ns | 417.9022 ns | 0.91 | 0.00 | +| ArraySetWithField | 1024 | 460.3398 ns | 0.1941 ns | 0.1516 ns | 460.3139 ns | 1.00 | 0.00 | +| SpanSet | 1024 | 518.9574 ns | 0.3053 ns | 0.2706 ns | 518.8852 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 2048 | 835.0463 ns | 0.5355 ns | 0.4747 ns | 835.1100 ns | 0.91 | 0.00 | +| ArraySetWithField | 2048 | 915.4347 ns | 0.6474 ns | 0.5406 ns | 915.4247 ns | 1.00 | 0.00 | +| SpanSet | 2048 | 1,031.4134 ns | 0.7092 ns | 0.6634 ns | 1,031.5475 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 4096 | 1,711.6639 ns | 0.6226 ns | 0.5824 ns | 1,711.7812 ns | 0.94 | 0.00 | +| ArraySetWithField | 4096 | 1,830.3076 ns | 0.4474 ns | 0.3966 ns | 1,830.2355 ns | 1.00 | 0.00 | +| SpanSet | 4096 | 2,061.5471 ns | 1.6995 ns | 1.5066 ns | 2,061.7563 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 8192 | 3,414.7095 ns | 1.2613 ns | 1.1798 ns | 3,414.6534 ns | 0.94 | 0.00 | +| ArraySetWithField | 8192 | 3,645.9164 ns | 1.4002 ns | 1.3097 ns | 3,645.9304 ns | 1.00 | 0.00 | +| SpanSet | 8192 | 4,107.2606 ns | 3.4648 ns | 3.2409 ns | 4,107.4933 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 1000000 | 420,702.1225 ns | 255.2887 ns | 226.3068 ns | 420,727.8545 ns | 0.95 | 0.00 | +| ArraySetWithField | 1000000 | 444,452.6199 ns | 402.2439 ns | 356.5787 ns | 444,416.0256 ns | 1.00 | 0.00 | +| SpanSet | 1000000 | 500,024.9671 ns | 1,037.9277 ns | 970.8782 ns | 500,189.8135 ns | 1.12 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 2147483591 | 238,492,391,914.8000 ns | 4,592,687,023.3708 ns | 4,296,002,291.1676 ns | 239,458,354,055.0000 ns | 0.97 | 0.03 | +| ArraySetWithField | 2147483591 | 244,774,507,284.3333 ns | 4,025,896,310.4222 ns | 3,765,825,906.6134 ns | 246,203,799,599.0000 ns | 1.00 | 0.00 | +| SpanSet | 2147483591 | 225,900,352,939.8750 ns | 4,408,307,941.2510 ns | 5,732,045,514.6553 ns | 224,187,746,770.5000 ns | 0.92 | 0.03 | + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (3.48 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (2.57 ns, 2.57 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (2.53 ns, 2.53 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (3.17 ns, 3.22 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (4.13 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (5.87 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (4.97 ns, 4.98 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (5.48 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were detected (4.20 ns, 4.23 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (4.76 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (5.06 ns, 5.08 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (5.10 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (6.31 ns, 6.32 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (6.60 ns, 6.60 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (5.99 ns, 6.00 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (6.41 ns, 6.45 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed, 3 outliers were detected (6.95 ns, 6.98 ns, 6.98 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (6.83 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (7.06 ns, 7.20 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (7.11 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (7.58 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (7.97 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (7.80 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (8.60 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (8.42 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (8.94 ns..9.13 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (8.74 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (10.15 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (9.99 ns, 10.00 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (9.70 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (10.55 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (10.72 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (14.22 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed, 4 outliers were detected (14.86 ns..14.89 ns, 15.08 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (25.53 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (25.45 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (28.86 ns..28.99 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (31.04 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (52.93 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (48.99 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (53.44 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (53.14 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (64.12 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (212.83 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (235.92 ns, 237.35 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 2 outliers were detected (265.18 ns, 265.83 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (421.48 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (463.76 ns..469.66 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (522.77 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (836.49 ns, 839.38 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (919.44 ns, 941.01 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (1.84 us) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (2.07 us) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was detected (4.10 us) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (420.17 us, 421.17 us) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (445.30 us) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (243.93 s) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Median : Value separating the higher half of all measurements (50th percentile) + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec) From 13fa23ac13b491e87eda82b6a02e4b19a0b68c61 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 22:21:58 +0200 Subject: [PATCH 242/305] RecyclableArrayList.AsArray() method converted to property for better performance. --- .../POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs | 4 ++-- Recyclable.Collections/RecyclableArrayList.cs | 2 +- Recyclable.Collections/RecyclableList.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index 5f4ebe2..da5f07c 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -498,7 +498,7 @@ namespace Recyclable.Collections.Benchmarks.POC targetBlockIndex = _nextItemBlockIndex, memoryBlockCount = _memoryBlocks.Length; - Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span itemsSpan = new(items.AsArray, 0, items.Count); Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); while (targetBlockArraySpan.Length <= itemsSpan.Length) @@ -1397,7 +1397,7 @@ namespace Recyclable.Collections.Benchmarks.POC targetBlockIndex = _nextItemBlockIndex, memoryBlockCount = _memoryBlocks.Length; - Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span itemsSpan = new(items.AsArray, 0, items.Count); Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); while (targetBlockArraySpan.Length <= itemsSpan.Length) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 155e7a1..8160579 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -368,7 +368,7 @@ namespace Recyclable.Collections _count = targetItemIdx; } - public T[] AsArray() => _memoryBlock; + public T[] AsArray => _memoryBlock; public void Clear() { diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 9f2527d..eaa3de5 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -681,7 +681,7 @@ namespace Recyclable.Collections targetBlockIndex = _nextItemBlockIndex, memoryBlockCount = _reservedBlockCount; - Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span itemsSpan = new(items.AsArray, 0, items.Count); Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); while (targetBlockArraySpan.Length <= itemsSpan.Length) From f934755d79e3ba1212b64f4e3ddf95566a6480d8 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 30 Apr 2023 23:59:38 +0200 Subject: [PATCH 243/305] RecyclableList.DoLongIndexOfParallel performance improvements. --- Recyclable.Collections/RecyclableList.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index eaa3de5..dffae2e 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -228,9 +228,7 @@ namespace Recyclable.Collections int blockSize = list._blockSize; int blockIndex = itemRange.BlockIndex; long itemsToSearchCount = itemRange.ItemsToSearchCount; - int startingItemIndex = itemRange.StartingItemIndex; - T[][] memoryBlocks = list._memoryBlocks; - int index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, startingItemIndex, (int)Math.Min(blockSize - startingItemIndex, itemsToSearchCount)); + int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); if (itemFoundSignal.IsSet) { return ItemNotFoundIndex; @@ -243,7 +241,7 @@ namespace Recyclable.Collections // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount // will always result in a negative result. - itemsToSearchCount -= blockSize - startingItemIndex; + itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; if (itemsToSearchCount <= 0) { return ItemNotFoundIndex; @@ -254,7 +252,7 @@ namespace Recyclable.Collections blockIndex++; while (itemsToSearchCount > blockSize) { - index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); if (itemFoundSignal.IsSet) { return ItemNotFoundIndex; @@ -271,8 +269,8 @@ namespace Recyclable.Collections index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData ? -1 : blockIndex == list._nextItemBlockIndex - ? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) - : Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); From c1326b8dea41a93be482670f35274e795c4c9535 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 1 May 2023 01:54:52 +0200 Subject: [PATCH 244/305] RecyclableList.ItemNotFoundIndexLong const added to limit value conversions. --- Recyclable.Collections/RecyclableList.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index dffae2e..a16c755 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -7,6 +7,7 @@ namespace Recyclable.Collections { public class RecyclableList : IDisposable, IList { + private const long ItemNotFoundIndexLong = -1L; private const int ItemNotFoundIndex = -1; private const double OptimalParallelSearchStep = 0.329; @@ -231,7 +232,7 @@ namespace Recyclable.Collections int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); if (itemFoundSignal.IsSet) { - return ItemNotFoundIndex; + return ItemNotFoundIndexLong; } else if (index >= 0) { @@ -244,7 +245,7 @@ namespace Recyclable.Collections itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; if (itemsToSearchCount <= 0) { - return ItemNotFoundIndex; + return ItemNotFoundIndexLong; } //T itemToFind = itemRange.ItemToFind; @@ -255,7 +256,7 @@ namespace Recyclable.Collections index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); if (itemFoundSignal.IsSet) { - return ItemNotFoundIndex; + return ItemNotFoundIndexLong; } else if (index >= 0) { @@ -267,14 +268,14 @@ namespace Recyclable.Collections } index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData - ? -1 + ? ItemNotFoundIndex : blockIndex == list._nextItemBlockIndex ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); - return index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndex; + return index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; } [MethodImpl(MethodImplOptions.AggressiveOptimization)] @@ -282,7 +283,7 @@ namespace Recyclable.Collections { using var itemFoundSignal = ManualResetEventSlimmer.Create(false); using var allDoneSignal = new Barrier(1); - long foundItemIndex = -1L; + long foundItemIndex = ItemNotFoundIndexLong; Exception? ex = null; // (long)(_longCount * 0.329) => most efficient step, based on benchmarks @@ -294,7 +295,7 @@ namespace Recyclable.Collections } ex.CaptureAndRethrow(); - return ItemNotFoundIndex; + return ItemNotFoundIndexLong; //int blockIndex = 0; //int itemIndex = 0; @@ -386,10 +387,10 @@ namespace Recyclable.Collections } else { - return ItemNotFoundIndex; + return ItemNotFoundIndexLong; } - return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndexLong; } /// @@ -988,7 +989,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public long LongIndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndex + ? ItemNotFoundIndexLong : _longCount <= _blockSize ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) : DoIndexOfParallel(item); From 8aebac97b5e1bb7515569e99bdd979870e0a9293 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 1 May 2023 01:56:50 +0200 Subject: [PATCH 245/305] Unnecessary arithmetic overflow checks removed. --- Recyclable.Collections/RecyclableList.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index a16c755..227708a 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -66,11 +66,11 @@ namespace Recyclable.Collections T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; int blockSize = list._blockSize; - int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2BitShift)); - int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); + int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2BitShift); + int sourceItemIndex = (int)((index + 1) & list._blockSizeMinus1); - int targetBlockIndex = checked((int)(index >> list._blockSizePow2BitShift)); - int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); + int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); + int targetItemIndex = (int)(index & list._blockSizeMinus1); int lastTakenBlockIndex = list.LastBlockWithData; while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) @@ -980,9 +980,9 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex - //// Optimization for tiny collections to avoid any vars etc. + // Optimization for tiny collections to avoid any vars etc. : _longCount <= _blockSize - ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) : checked((int)DoIndexOfParallel(item)); public void Insert(int index, T item) => throw new NotSupportedException(); From 948156b89768b5c36dc1037e70b1a974cc9e4cd0 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 1 May 2023 11:48:48 +0200 Subject: [PATCH 246/305] RecyclableList.IndexOf performance improvements. --- Recyclable.Collections/RecyclableList.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 227708a..c602722 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -275,10 +275,10 @@ namespace Recyclable.Collections //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); - return index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; + return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; } - [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] private long DoIndexOfParallel(T item) { using var itemFoundSignal = ManualResetEventSlimmer.Create(false); @@ -980,10 +980,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 ? ItemNotFoundIndex - // Optimization for tiny collections to avoid any vars etc. - : _longCount <= _blockSize - ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : checked((int)DoIndexOfParallel(item)); + : checked((int)DoIndexOfParallel(item)); public void Insert(int index, T item) => throw new NotSupportedException(); From b038bcfdc924bb0abafae5ae08476cb7a6030588 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 1 May 2023 12:39:23 +0200 Subject: [PATCH 247/305] Local function replaced with Lambda for readability in RecyclableList.DoIndexOfParallel. --- Recyclable.Collections/RecyclableList.cs | 45 +++++------------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index c602722..28c1c67 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -287,42 +287,7 @@ namespace Recyclable.Collections Exception? ex = null; // (long)(_longCount * 0.329) => most efficient step, based on benchmarks - ItemRangesIterator.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); - allDoneSignal.SignalAndWait(); - if (ex == null) - { - return foundItemIndex; - } - - ex.CaptureAndRethrow(); - return ItemNotFoundIndexLong; - - //int blockIndex = 0; - //int itemIndex = 0; - //long itemsCount = _longCount; - //while (itemsCount > step) - //{ - // if (!LocalIndexOf(new(blockIndex, itemIndex, step))) - // { - // break; - // } - - // itemsCount -= step; - // //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); - // blockIndex += (int)((itemIndex + step) >> _blockSizePow2BitShift); - // itemIndex = (int)((itemIndex + step) & _blockSizeMinus1); - // //itemIndex += step; - // //while (itemIndex >= blockSize) - // //{ - // // blockIndex++; - // // itemIndex -= blockSize; - // //} - //} - - //_ = LocalIndexOf(new(blockIndex, itemIndex, itemsCount)); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - bool LocalIndexOf(ItemRange itemRange) + ItemRangesIterator.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, itemRange => { _ = allDoneSignal.AddParticipant(); _ = Task.Factory.StartNew(() => @@ -362,7 +327,15 @@ namespace Recyclable.Collections //}); return !itemFoundSignal.IsSet; + }); + allDoneSignal.SignalAndWait(); + if (ex == null) + { + return foundItemIndex; } + + ex.CaptureAndRethrow(); + return ItemNotFoundIndexLong; } [MethodImpl(MethodImplOptions.AggressiveOptimization)] From dfb671aff76144fbd4216598898ad4bf1ec1022d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 1 May 2023 17:37:31 +0200 Subject: [PATCH 248/305] Several hints removed & code cleaned up. --- .../Benchmarks/Contains_LastItems.cs | 1 - .../Benchmarks/GetItem..cs | 3 - .../Benchmarks/IndexOf_AllItems.cs | 1 - .../Benchmarks/IndexOf_FirstItems.cs | 1 - .../Benchmarks/IndexOf_LastItems.cs | 1 - .../Benchmarks/LongCount.cs | 1 - .../Benchmarks/Remove_FirstItems.cs | 1 - .../Benchmarks/Remove_LastItems.cs | 1 - .../Core/DataGenerator.cs | 3 +- .../POC/BoolOrComparePoc.Benchmarks.cs | 6 +- .../POC/DelegateVsComparerPoc.Benchmarks.cs | 12 ++-- .../POC/ModuloPoc.Benchmarks.cs | 2 +- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 20 +++---- .../RecyclableListPoc.Benchmarks.IndexOf.cs | 38 ++++++------ .../POC/RefVsInstanceMemberPoc.Benchmarks.cs | 2 +- .../POC/WhenParallelSearchBenchmarks.cs | 5 +- Recyclable.Collections/MathUtils.cs | 14 ++--- Recyclable.Collections/RecyclableArrayList.cs | 44 +++++++------- Recyclable.Collections/RecyclableList.cs | 60 +++++++++---------- .../RecyclableListExtensions.cs | 2 +- Recyclable.Collections/RecyclableQueue.cs | 7 +-- .../RecyclableSortedList.cs | 4 +- .../EnumerableWithoutCount.cs | 2 +- .../ItemRangesIteratorTests.cs | 1 - .../RecyclableArrayListTests.cs | 9 +-- .../RecyclableListTestData.cs | 3 +- 26 files changed, 106 insertions(+), 138 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs index dc9da22..d0b50c9 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs @@ -63,6 +63,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.Contains(data[^(i + 1)])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index 27006d4..374abbd 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -78,7 +78,6 @@ namespace Recyclable.Collections.Benchmarks if (nextItemBlockIndex == data.LastBlockWithData) { - var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); for (var itemIndex = 0; itemIndex < memoryBlock.Length; itemIndex++) { @@ -119,8 +118,6 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_GetItem_AsForEach() { var data = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount; - foreach (var item in data) { DoNothing(item); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs index eff6950..1df873c 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs @@ -75,6 +75,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.LongIndexOf(data[i])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs index 412f4d6..a7bbe76 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs @@ -75,6 +75,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.LongIndexOf(data[i])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs index f12a329..22910c4 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs @@ -75,6 +75,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.LongIndexOf(data[^(i + 1)])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs index e299491..426cf3e 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs @@ -17,6 +17,5 @@ namespace Recyclable.Collections.Benchmarks var data = TestObjectsAsRecyclableList; DoNothing(data.LongCount); } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs index e53045a..c0e77b0 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs @@ -52,6 +52,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.Remove(data[i])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs index 571d56a..aeb7207 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs @@ -1,7 +1,6 @@ using BenchmarkDotNet.Attributes; using Collections.Pooled; - namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks diff --git a/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs b/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs index b4d8321..faaaf4e 100644 --- a/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs +++ b/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections.Benchmarks.Core { - public class DataGenerator + public static class DataGenerator { public static long[] EnumerateTestObjects(long testObjectCount) { @@ -15,6 +15,5 @@ namespace Recyclable.Collections.Benchmarks.Core return result; } - } } \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs index 61b0c8d..51e002c 100644 --- a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs @@ -12,14 +12,14 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.Bool(); benchmark.Compare(); } - + private static void DoNothing(T result, [CallerMemberName] string? callerName = null) { //Console.WriteLine($"{callerName}: result = {result}"); } - private bool _testBool = true; - private int _blockSize = 8; + private readonly bool _testBool = true; + private readonly int _blockSize = 8; [Benchmark(Baseline = false)] public void Bool() diff --git a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs index 4cd9e75..5b7e462 100644 --- a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs @@ -64,13 +64,13 @@ namespace Recyclable.Collections.Benchmarks.POC private const int _objectCount = 1_000_000; private static readonly IEnumerable _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x); - private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static x => new MyComparer().Compare); - private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(x => (IComparer)new MyComparer()); - private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(x => new Compare((x, y) => new MyComparer().Compare(x, y))); + private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static _ => new MyComparer().Compare); + private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(_ => (IComparer)new MyComparer()); + private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(_ => new Compare((x, y) => new MyComparer().Compare(x, y))); private static readonly IEnumerable> _testListOfLocalFunctions = GetLocalFunctions(_testObjects); - private static readonly IEnumerable _testListOfClasses = _testObjects.Select(x => new MyComparer()); - private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(x => new MyComparerStruct()); - private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(x => (IComparer)new MyComparerStruct()); + private static readonly IEnumerable _testListOfClasses = _testObjects.Select(_ => new MyComparer()); + private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(_ => new MyComparerStruct()); + private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(_ => (IComparer)new MyComparerStruct()); private static IEnumerable _testListOfUnboxedStructs() where TComparer : IComparer, new() { foreach (var item in _testObjects) diff --git a/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs index d5f4ddb..2fb2154 100644 --- a/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.XDivYMulX(); benchmark.XDivYMulXWithLocalVar(); } - + private long _cutoff; private Vector128 _increment; private long _lastCaseIndex; diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index da5f07c..182aa61 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -586,7 +586,6 @@ namespace Recyclable.Collections.Benchmarks.POC { _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; @@ -872,7 +871,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - #endregion #region RecyclableList v2 @@ -1117,10 +1115,11 @@ namespace Recyclable.Collections.Benchmarks.POC /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . - /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. - /// - /// This method checks for integral overflow. + /// + /// This method checks for integral overflow. /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -1485,7 +1484,6 @@ namespace Recyclable.Collections.Benchmarks.POC { _nextItemIndex = checked((int)(targetCapacity & (blockSize - 1))); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; @@ -2089,10 +2087,11 @@ namespace Recyclable.Collections.Benchmarks.POC /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . - /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. - /// - /// This method checks for integral overflow. + /// + /// This method checks for integral overflow. /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -2446,7 +2445,6 @@ namespace Recyclable.Collections.Benchmarks.POC { _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; @@ -2804,7 +2802,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } } - + #endregion public partial class RecyclableListPocBenchmarks diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs index 3ae38da..5f89df1 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs @@ -404,10 +404,11 @@ namespace Recyclable.Collections.Benchmarks.POC /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . - /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. - /// - /// This method checks for integral overflow. + /// + /// This method checks for integral overflow. /// /// /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance @@ -457,7 +458,7 @@ namespace Recyclable.Collections.Benchmarks.POC } blockIndex = sourceBlockCount; - do + while (true) { newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); if (blockIndex + 1 < requiredBlockCount) @@ -468,24 +469,24 @@ namespace Recyclable.Collections.Benchmarks.POC { break; } - } while (true); + }; } else { blockIndex = sourceBlockCount; - do - { - newMemoryBlocks[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } while (true); - } + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } } list._reservedBlockCount = requiredBlockCount; @@ -773,7 +774,6 @@ namespace Recyclable.Collections.Benchmarks.POC { _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; diff --git a/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs index 3c9c429..bad353e 100644 --- a/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs @@ -26,7 +26,7 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.RefToInt2(); benchmark.RefToInt3(); } - + private readonly TestCase _testCase = new(); private static void DoNothing(int count) { diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs index 5e474ec..1a33945 100644 --- a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -22,12 +22,11 @@ namespace Recyclable.Collections.Benchmarks.POC //[Params(329_000, 512_000, 750_000, 1_000_000, 3_290_000)] //public int Step { get; set; } = 329_000; // This was the fastest so far - private static readonly int _numberOfProcessors = Environment.ProcessorCount; private RecyclableList? _testData; public object? ItemToFind { get; set; } - [GlobalSetup] + [GlobalSetup] public void Setup() { _testData = Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableList(BlockSize); @@ -40,7 +39,6 @@ namespace Recyclable.Collections.Benchmarks.POC _testData = default; } - [Benchmark(Baseline = true)] public void IndexOfSequentially() { @@ -262,7 +260,6 @@ namespace Recyclable.Collections.Benchmarks.POC // } - // if (threadIndex + 1 < numberOfRanges && blockIndex <= _testData.LastBlockWithData) // { // threadIndex++; diff --git a/Recyclable.Collections/MathUtils.cs b/Recyclable.Collections/MathUtils.cs index 40f2141..aebd3d6 100644 --- a/Recyclable.Collections/MathUtils.cs +++ b/Recyclable.Collections/MathUtils.cs @@ -5,13 +5,13 @@ namespace Recyclable.Collections { public static class MathUtils { - private static readonly long[] Pow2 = new long[] - { - 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, - 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, - 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, - 536870912, 1073741824, 2147483648 - }; + // private static readonly long[] Pow2 = new long[] + // { + // 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, + // 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, + // 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, + // 536870912, 1073741824, 2147483648 + // }; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPow2(long value) => (value != 0) && ((value & (value - 1)) == 0); diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 8160579..3ab98d1 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -8,7 +8,6 @@ namespace Recyclable.Collections { private const int ItemNotFound = -1; private static readonly ArrayPool _arrayPool = ArrayPool.Create(); - private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; protected static readonly bool NeedsClearing = !typeof(T).IsValueType; @@ -129,7 +128,7 @@ namespace Recyclable.Collections } protected int _capacity; - public int Capacity + public int Capacity { get => _capacity; set @@ -340,30 +339,26 @@ namespace Recyclable.Collections if (enumerator.MoveNext()) { int available = capacity - targetItemIdx; - while (true) - { - if (targetItemIdx + growByCount > capacity) - { + do + { + if (targetItemIdx + growByCount > capacity) + { capacity = EnsureCapacity(capacity + growByCount); - memorySpan = new(_memoryBlock); - available = capacity - targetItemIdx; - } + memorySpan = new(_memoryBlock); + available = capacity - targetItemIdx; + } - for (i = 0; i < available; i++) - { - memorySpan[targetItemIdx++] = enumerator.Current; - if (!enumerator.MoveNext()) - { - break; - } - } - - if (i < available) - { - break; - } - } - } + for (i = 0; i < available; i++) + { + memorySpan[targetItemIdx++] = enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + } + } + while (i >= available); + } _count = targetItemIdx; } @@ -459,6 +454,7 @@ namespace Recyclable.Collections } public IEnumerator GetEnumerator() => Enumerate(this).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Enumerate(this).GetEnumerator(); public void Dispose() diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 28c1c67..a09658e 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -373,10 +373,11 @@ namespace Recyclable.Collections /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . - /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. - /// - /// This method checks for integral overflow. + /// + /// This method checks for integral overflow. /// /// /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance @@ -426,35 +427,35 @@ namespace Recyclable.Collections } blockIndex = sourceBlockCount; - do - { - newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } while (true); - } + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } else { blockIndex = sourceBlockCount; - do - { - newMemoryBlocks[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } while (true); - } + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } } list._reservedBlockCount = requiredBlockCount; @@ -742,7 +743,6 @@ namespace Recyclable.Collections { _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index d7c3f9d..2823cf7 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -112,7 +112,7 @@ namespace Recyclable.Collections return scannedCount; } - scannedCount++; + scannedCount++; if (scannedCount == itemsCount) { return -1; diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 0b48c3a..f8ff04a 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -193,19 +193,14 @@ namespace Recyclable.Collections } } - protected void Dispose(bool disposing) + public void Dispose() { if (!_disposedValue) { Clear(); _disposedValue = true; } - } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); GC.SuppressFinalize(this); } } diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs index 1c64e82..c3e63bd 100644 --- a/Recyclable.Collections/RecyclableSortedList.cs +++ b/Recyclable.Collections/RecyclableSortedList.cs @@ -27,7 +27,7 @@ QuickSort(); } - public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize) + public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize) : base(blockSize) { } @@ -73,7 +73,7 @@ finally { EndUpdate(false); - } + } } } } \ No newline at end of file diff --git a/Recyclable.CollectionsTests/EnumerableWithoutCount.cs b/Recyclable.CollectionsTests/EnumerableWithoutCount.cs index b6c08b3..34df3e9 100644 --- a/Recyclable.CollectionsTests/EnumerableWithoutCount.cs +++ b/Recyclable.CollectionsTests/EnumerableWithoutCount.cs @@ -4,7 +4,7 @@ namespace Recyclable.CollectionsTests { public class EnumerableWithoutCount : IEnumerable { - private IEnumerable _testData; + private readonly IEnumerable _testData; public EnumerableWithoutCount(IEnumerable testData) { diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs index 59715f8..fb9b33c 100644 --- a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs +++ b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs @@ -127,7 +127,6 @@ namespace Recyclable.CollectionsTests (9, 0, 1), } }, - }; } } diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 7d236d8..66d6c01 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -217,7 +217,6 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be(0); _ = list.Should().BeEmpty(); - } [Theory] @@ -350,10 +349,7 @@ namespace Recyclable.CollectionsTests // Act var yieldedItems = new List((int)itemsCount); - foreach (var item in list) - { - yieldedItems.Add(item); - } + yieldedItems.AddRange(list); // Validate _ = yieldedItems.Count.Should().Be((int)itemsCount); @@ -549,8 +545,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); - } } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index 5f8b179..b4d17e3 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -54,7 +54,6 @@ namespace Recyclable.CollectionsTests CreateTestData(itemsCount).ToRecyclableList(sourceBlockSize), itemsCount }; - } yield return new object[] { $"IList(itemsCount: {itemsCount})", new CustomIList(CreateTestData(itemsCount)), itemsCount }; @@ -64,7 +63,7 @@ namespace Recyclable.CollectionsTests } public static IEnumerable SourceDataVariants => CreateSourceDataVariants().ToArray(); - + private static IEnumerable CreateSourceEmptyDataVariants() => new[] { new object[] { "int[]", Array.Empty() }, From 52dc663ada9fb0422b2df2b2d344e573c8f0bd2b Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 1 May 2023 17:37:31 +0200 Subject: [PATCH 249/305] Several hints removed & code cleaned up. --- .../Benchmarks/Contains_LastItems.cs | 1 - .../Benchmarks/GetItem..cs | 3 - .../Benchmarks/IndexOf_AllItems.cs | 1 - .../Benchmarks/IndexOf_FirstItems.cs | 1 - .../Benchmarks/IndexOf_LastItems.cs | 1 - .../Benchmarks/LongCount.cs | 1 - .../Benchmarks/Remove_FirstItems.cs | 1 - .../Benchmarks/Remove_LastItems.cs | 1 - .../Core/DataGenerator.cs | 3 +- .../POC/BoolOrComparePoc.Benchmarks.cs | 6 +- .../POC/DelegateVsComparerPoc.Benchmarks.cs | 12 ++-- .../POC/ModuloPoc.Benchmarks.cs | 2 +- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 20 +++---- .../RecyclableListPoc.Benchmarks.IndexOf.cs | 40 ++++++------- .../POC/RefVsInstanceMemberPoc.Benchmarks.cs | 2 +- .../POC/WhenParallelSearchBenchmarks.cs | 5 +- Recyclable.Collections/MathUtils.cs | 14 ++--- Recyclable.Collections/RecyclableArrayList.cs | 44 +++++++------- Recyclable.Collections/RecyclableList.cs | 60 +++++++++---------- .../RecyclableListExtensions.cs | 2 +- Recyclable.Collections/RecyclableQueue.cs | 7 +-- .../RecyclableSortedList.cs | 4 +- .../EnumerableWithoutCount.cs | 2 +- .../ItemRangesIteratorTests.cs | 1 - .../RecyclableArrayListTests.cs | 9 +-- .../RecyclableListTestData.cs | 3 +- 26 files changed, 107 insertions(+), 139 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs index dc9da22..d0b50c9 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs @@ -63,6 +63,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.Contains(data[^(i + 1)])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index 27006d4..374abbd 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -78,7 +78,6 @@ namespace Recyclable.Collections.Benchmarks if (nextItemBlockIndex == data.LastBlockWithData) { - var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); for (var itemIndex = 0; itemIndex < memoryBlock.Length; itemIndex++) { @@ -119,8 +118,6 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableList_GetItem_AsForEach() { var data = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount; - foreach (var item in data) { DoNothing(item); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs index eff6950..1df873c 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs @@ -75,6 +75,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.LongIndexOf(data[i])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs index 412f4d6..a7bbe76 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs @@ -75,6 +75,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.LongIndexOf(data[i])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs index f12a329..22910c4 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs @@ -75,6 +75,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.LongIndexOf(data[^(i + 1)])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs index e299491..426cf3e 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs @@ -17,6 +17,5 @@ namespace Recyclable.Collections.Benchmarks var data = TestObjectsAsRecyclableList; DoNothing(data.LongCount); } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs index e53045a..c0e77b0 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs @@ -52,6 +52,5 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list.Remove(data[i])); } } - } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs index 571d56a..aeb7207 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs @@ -1,7 +1,6 @@ using BenchmarkDotNet.Attributes; using Collections.Pooled; - namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks diff --git a/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs b/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs index b4d8321..faaaf4e 100644 --- a/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs +++ b/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections.Benchmarks.Core { - public class DataGenerator + public static class DataGenerator { public static long[] EnumerateTestObjects(long testObjectCount) { @@ -15,6 +15,5 @@ namespace Recyclable.Collections.Benchmarks.Core return result; } - } } \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs index 61b0c8d..51e002c 100644 --- a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs @@ -12,14 +12,14 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.Bool(); benchmark.Compare(); } - + private static void DoNothing(T result, [CallerMemberName] string? callerName = null) { //Console.WriteLine($"{callerName}: result = {result}"); } - private bool _testBool = true; - private int _blockSize = 8; + private readonly bool _testBool = true; + private readonly int _blockSize = 8; [Benchmark(Baseline = false)] public void Bool() diff --git a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs index 4cd9e75..5b7e462 100644 --- a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs @@ -64,13 +64,13 @@ namespace Recyclable.Collections.Benchmarks.POC private const int _objectCount = 1_000_000; private static readonly IEnumerable _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x); - private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static x => new MyComparer().Compare); - private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(x => (IComparer)new MyComparer()); - private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(x => new Compare((x, y) => new MyComparer().Compare(x, y))); + private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static _ => new MyComparer().Compare); + private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(_ => (IComparer)new MyComparer()); + private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(_ => new Compare((x, y) => new MyComparer().Compare(x, y))); private static readonly IEnumerable> _testListOfLocalFunctions = GetLocalFunctions(_testObjects); - private static readonly IEnumerable _testListOfClasses = _testObjects.Select(x => new MyComparer()); - private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(x => new MyComparerStruct()); - private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(x => (IComparer)new MyComparerStruct()); + private static readonly IEnumerable _testListOfClasses = _testObjects.Select(_ => new MyComparer()); + private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(_ => new MyComparerStruct()); + private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(_ => (IComparer)new MyComparerStruct()); private static IEnumerable _testListOfUnboxedStructs() where TComparer : IComparer, new() { foreach (var item in _testObjects) diff --git a/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs index d5f4ddb..2fb2154 100644 --- a/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.XDivYMulX(); benchmark.XDivYMulXWithLocalVar(); } - + private long _cutoff; private Vector128 _increment; private long _lastCaseIndex; diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index da5f07c..182aa61 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -586,7 +586,6 @@ namespace Recyclable.Collections.Benchmarks.POC { _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; @@ -872,7 +871,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - #endregion #region RecyclableList v2 @@ -1117,10 +1115,11 @@ namespace Recyclable.Collections.Benchmarks.POC /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . - /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. - /// - /// This method checks for integral overflow. + /// + /// This method checks for integral overflow. /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -1485,7 +1484,6 @@ namespace Recyclable.Collections.Benchmarks.POC { _nextItemIndex = checked((int)(targetCapacity & (blockSize - 1))); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; @@ -2089,10 +2087,11 @@ namespace Recyclable.Collections.Benchmarks.POC /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . - /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. - /// - /// This method checks for integral overflow. + /// + /// This method checks for integral overflow. /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -2446,7 +2445,6 @@ namespace Recyclable.Collections.Benchmarks.POC { _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; @@ -2804,7 +2802,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } } - + #endregion public partial class RecyclableListPocBenchmarks diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs index 3ae38da..6741414 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { - #region RecyclableList IndexOf + #region RecyclableList IndexOf v1 internal class RecyclableListIndexOfV1 : IDisposable, IList { @@ -404,10 +404,11 @@ namespace Recyclable.Collections.Benchmarks.POC /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . - /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. - /// - /// This method checks for integral overflow. + /// + /// This method checks for integral overflow. /// /// /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance @@ -457,7 +458,7 @@ namespace Recyclable.Collections.Benchmarks.POC } blockIndex = sourceBlockCount; - do + while (true) { newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); if (blockIndex + 1 < requiredBlockCount) @@ -468,24 +469,24 @@ namespace Recyclable.Collections.Benchmarks.POC { break; } - } while (true); + }; } else { blockIndex = sourceBlockCount; - do - { - newMemoryBlocks[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } while (true); - } + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } } list._reservedBlockCount = requiredBlockCount; @@ -773,7 +774,6 @@ namespace Recyclable.Collections.Benchmarks.POC { _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; diff --git a/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs index 3c9c429..bad353e 100644 --- a/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs @@ -26,7 +26,7 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.RefToInt2(); benchmark.RefToInt3(); } - + private readonly TestCase _testCase = new(); private static void DoNothing(int count) { diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs index 5e474ec..1a33945 100644 --- a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -22,12 +22,11 @@ namespace Recyclable.Collections.Benchmarks.POC //[Params(329_000, 512_000, 750_000, 1_000_000, 3_290_000)] //public int Step { get; set; } = 329_000; // This was the fastest so far - private static readonly int _numberOfProcessors = Environment.ProcessorCount; private RecyclableList? _testData; public object? ItemToFind { get; set; } - [GlobalSetup] + [GlobalSetup] public void Setup() { _testData = Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableList(BlockSize); @@ -40,7 +39,6 @@ namespace Recyclable.Collections.Benchmarks.POC _testData = default; } - [Benchmark(Baseline = true)] public void IndexOfSequentially() { @@ -262,7 +260,6 @@ namespace Recyclable.Collections.Benchmarks.POC // } - // if (threadIndex + 1 < numberOfRanges && blockIndex <= _testData.LastBlockWithData) // { // threadIndex++; diff --git a/Recyclable.Collections/MathUtils.cs b/Recyclable.Collections/MathUtils.cs index 40f2141..aebd3d6 100644 --- a/Recyclable.Collections/MathUtils.cs +++ b/Recyclable.Collections/MathUtils.cs @@ -5,13 +5,13 @@ namespace Recyclable.Collections { public static class MathUtils { - private static readonly long[] Pow2 = new long[] - { - 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, - 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, - 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, - 536870912, 1073741824, 2147483648 - }; + // private static readonly long[] Pow2 = new long[] + // { + // 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, + // 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, + // 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, + // 536870912, 1073741824, 2147483648 + // }; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPow2(long value) => (value != 0) && ((value & (value - 1)) == 0); diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 8160579..3ab98d1 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -8,7 +8,6 @@ namespace Recyclable.Collections { private const int ItemNotFound = -1; private static readonly ArrayPool _arrayPool = ArrayPool.Create(); - private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; protected static readonly bool NeedsClearing = !typeof(T).IsValueType; @@ -129,7 +128,7 @@ namespace Recyclable.Collections } protected int _capacity; - public int Capacity + public int Capacity { get => _capacity; set @@ -340,30 +339,26 @@ namespace Recyclable.Collections if (enumerator.MoveNext()) { int available = capacity - targetItemIdx; - while (true) - { - if (targetItemIdx + growByCount > capacity) - { + do + { + if (targetItemIdx + growByCount > capacity) + { capacity = EnsureCapacity(capacity + growByCount); - memorySpan = new(_memoryBlock); - available = capacity - targetItemIdx; - } + memorySpan = new(_memoryBlock); + available = capacity - targetItemIdx; + } - for (i = 0; i < available; i++) - { - memorySpan[targetItemIdx++] = enumerator.Current; - if (!enumerator.MoveNext()) - { - break; - } - } - - if (i < available) - { - break; - } - } - } + for (i = 0; i < available; i++) + { + memorySpan[targetItemIdx++] = enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + } + } + while (i >= available); + } _count = targetItemIdx; } @@ -459,6 +454,7 @@ namespace Recyclable.Collections } public IEnumerator GetEnumerator() => Enumerate(this).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Enumerate(this).GetEnumerator(); public void Dispose() diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 28c1c67..a09658e 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -373,10 +373,11 @@ namespace Recyclable.Collections /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . - /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. - /// - /// This method checks for integral overflow. + /// + /// This method checks for integral overflow. /// /// /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance @@ -426,35 +427,35 @@ namespace Recyclable.Collections } blockIndex = sourceBlockCount; - do - { - newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } while (true); - } + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } else { blockIndex = sourceBlockCount; - do - { - newMemoryBlocks[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } while (true); - } + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } } list._reservedBlockCount = requiredBlockCount; @@ -742,7 +743,6 @@ namespace Recyclable.Collections { _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); _nextItemBlockIndex = targetBlockIndex; - } _longCount = targetCapacity; diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index d7c3f9d..2823cf7 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -112,7 +112,7 @@ namespace Recyclable.Collections return scannedCount; } - scannedCount++; + scannedCount++; if (scannedCount == itemsCount) { return -1; diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 0b48c3a..f8ff04a 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -193,19 +193,14 @@ namespace Recyclable.Collections } } - protected void Dispose(bool disposing) + public void Dispose() { if (!_disposedValue) { Clear(); _disposedValue = true; } - } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); GC.SuppressFinalize(this); } } diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs index 1c64e82..c3e63bd 100644 --- a/Recyclable.Collections/RecyclableSortedList.cs +++ b/Recyclable.Collections/RecyclableSortedList.cs @@ -27,7 +27,7 @@ QuickSort(); } - public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize) + public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize) : base(blockSize) { } @@ -73,7 +73,7 @@ finally { EndUpdate(false); - } + } } } } \ No newline at end of file diff --git a/Recyclable.CollectionsTests/EnumerableWithoutCount.cs b/Recyclable.CollectionsTests/EnumerableWithoutCount.cs index b6c08b3..34df3e9 100644 --- a/Recyclable.CollectionsTests/EnumerableWithoutCount.cs +++ b/Recyclable.CollectionsTests/EnumerableWithoutCount.cs @@ -4,7 +4,7 @@ namespace Recyclable.CollectionsTests { public class EnumerableWithoutCount : IEnumerable { - private IEnumerable _testData; + private readonly IEnumerable _testData; public EnumerableWithoutCount(IEnumerable testData) { diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs index 59715f8..fb9b33c 100644 --- a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs +++ b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs @@ -127,7 +127,6 @@ namespace Recyclable.CollectionsTests (9, 0, 1), } }, - }; } } diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 7d236d8..66d6c01 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -217,7 +217,6 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be(0); _ = list.Should().BeEmpty(); - } [Theory] @@ -350,10 +349,7 @@ namespace Recyclable.CollectionsTests // Act var yieldedItems = new List((int)itemsCount); - foreach (var item in list) - { - yieldedItems.Add(item); - } + yieldedItems.AddRange(list); // Validate _ = yieldedItems.Count.Should().Be((int)itemsCount); @@ -549,8 +545,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); - } } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index 5f8b179..b4d17e3 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -54,7 +54,6 @@ namespace Recyclable.CollectionsTests CreateTestData(itemsCount).ToRecyclableList(sourceBlockSize), itemsCount }; - } yield return new object[] { $"IList(itemsCount: {itemsCount})", new CustomIList(CreateTestData(itemsCount)), itemsCount }; @@ -64,7 +63,7 @@ namespace Recyclable.CollectionsTests } public static IEnumerable SourceDataVariants => CreateSourceDataVariants().ToArray(); - + private static IEnumerable CreateSourceEmptyDataVariants() => new[] { new object[] { "int[]", Array.Empty() }, From 3db10d760297b4620eae5fbab3d5241f01d20b01 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 3 May 2023 12:36:37 +0200 Subject: [PATCH 250/305] RecyclableList.IndexOf parallel performance improvements. --- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 1 + .../RecyclableListPoc.Benchmarks.IndexOf.cs | 3562 ++++++++++++++++- .../POC/RecyclableListPoc.Benchmarks.cs | 33 +- Recyclable.Collections/ItemRange.cs | 32 + Recyclable.Collections/ItemRangesIterator.cs | 236 +- Recyclable.Collections/MemoryBucket.cs | 22 + Recyclable.Collections/OneSizeArrayPool.cs | 179 - .../Parallel/BarrierSlimmer.cs | 117 + .../Parallel/ManualResetEventSlimmer.cs | 37 + .../Parallel/ManualResetEventSlimmerPool.cs | 29 + .../ParallelSynchronizationContext.cs | 38 + .../ParallelSynchronizationContextPool.cs | 50 + Recyclable.Collections/Pools/ItemRangePool.cs | 27 + .../Pools/OneSizeArrayPool.cs | 86 + .../Pools/RecyclableArrayPool.cs | 35 + Recyclable.Collections/RecyclableDefaults.cs | 2 +- Recyclable.Collections/RecyclableList.cs | 252 +- .../RecyclableListExtensions.cs | 2 + Recyclable.Collections/SearchConfiguration.cs | 29 + .../ItemRangesIteratorTests.cs | 22 +- .../RecyclableListTestData.cs | 2 +- 21 files changed, 4282 insertions(+), 511 deletions(-) create mode 100644 Recyclable.Collections/ItemRange.cs create mode 100644 Recyclable.Collections/MemoryBucket.cs delete mode 100644 Recyclable.Collections/OneSizeArrayPool.cs create mode 100644 Recyclable.Collections/Parallel/BarrierSlimmer.cs create mode 100644 Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs create mode 100644 Recyclable.Collections/Parallel/ManualResetEventSlimmerPool.cs create mode 100644 Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs create mode 100644 Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs create mode 100644 Recyclable.Collections/Pools/ItemRangePool.cs create mode 100644 Recyclable.Collections/Pools/OneSizeArrayPool.cs create mode 100644 Recyclable.Collections/Pools/RecyclableArrayPool.cs create mode 100644 Recyclable.Collections/SearchConfiguration.cs diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index 182aa61..c8ff64a 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Attributes; +using Recyclable.Collections.Pools; using System.Buffers; using System.Collections; using System.Numerics; diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs index 6741414..4a646f3 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs @@ -1,4 +1,7 @@ using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.ObjectPool; +using Recyclable.Collections.Parallel; +using Recyclable.Collections.Pools; using System.Buffers; using System.Collections; using System.Numerics; @@ -6,6 +9,94 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { + #region ItemRangesIterator V1 + + internal sealed class ItemRangeV1 : IDisposable + { + public int BlockIndex; + public int StartingItemIndex; + public long ItemsToSearchCount; + internal ObjectPool? _assignedPool; + + public ItemRangeV1() + { + + } + + public ItemRangeV1(int blockIndex, int startingItemIndex, long itemsToSearchCount) + { + BlockIndex = blockIndex; + ItemsToSearchCount = itemsToSearchCount; + StartingItemIndex = startingItemIndex; + } + + public void Dispose() + { + if (_assignedPool != null) + { + var pool = _assignedPool; + _assignedPool = null; + pool!.Return(this); + } + + GC.SuppressFinalize(this); + } + } + + internal static class ItemRangePoolV1 + { + [ThreadStatic] + private static readonly ObjectPool _shared; + public static ObjectPool Shared => _shared; + + static ItemRangePoolV1() + { + _shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + } + + public static ItemRangeV1 Create(int blockIndex, int startingItemIndex, long itemsToSearchCount) + { + var itemRange = _shared.Get(); + itemRange.BlockIndex = blockIndex; + itemRange.ItemsToSearchCount = itemsToSearchCount; + itemRange.StartingItemIndex = startingItemIndex; + return itemRange; + } + } + + internal static class ItemRangesIteratorV1 + { + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] + public static void Iterate(int blockIndex, int blockSize, byte blockSizePow2BitShift, long itemsCount, long step, Func action) + { + int itemIndex = 0; + ItemRangeV1 itemRange; + while (itemsCount > step) + { + itemRange = ItemRangePoolV1.Create(blockIndex, itemIndex, step); + if (!action(itemRange)) + { + break; + } + + itemsCount -= step; + blockIndex += (int)((itemIndex + step) >> blockSizePow2BitShift); + itemIndex = (int)((itemIndex + step) & (blockSize - 1)); + //itemIndex += step; + //while (itemIndex >= blockSize) + //{ + // blockIndex++; + // itemIndex -= blockSize; + //} + } + + itemRange = ItemRangePoolV1.Create(blockIndex, itemIndex, itemsCount); + _ = action(itemRange); + } + } + + #endregion + #region RecyclableList IndexOf v1 internal class RecyclableListIndexOfV1 : IDisposable, IList @@ -226,7 +317,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static long DoLongIndexOfParallel(in RecyclableListIndexOfV1 list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + private static long DoLongIndexOfParallel(in RecyclableListIndexOfV1 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) { int blockSize = list._blockSize; int blockIndex = itemRange.BlockIndex; @@ -285,14 +376,15 @@ namespace Recyclable.Collections.Benchmarks.POC [MethodImpl(MethodImplOptions.AggressiveOptimization)] private long DoIndexOfParallel(T item) { - using var itemFoundSignal = ManualResetEventSlimmer.Create(false); + var itemFoundSignal = ManualResetEventSlimmerPool.Create(false); using var allDoneSignal = new Barrier(1); //BarrierSlimmer.Create(1); long foundItemIndex = -1L; Exception? ex = null; // (long)(_longCount * 0.329) => most efficient step, based on benchmarks - ItemRangesIterator.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); + ItemRangesIteratorV1.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); allDoneSignal.SignalAndWait(); + itemFoundSignal.Dispose(); if (ex == null) { return foundItemIndex; @@ -326,7 +418,7 @@ namespace Recyclable.Collections.Benchmarks.POC //_ = LocalIndexOf(new(blockIndex, itemIndex, itemsCount)); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - bool LocalIndexOf(ItemRange itemRange) + bool LocalIndexOf(ItemRangeV1 itemRange) { _ = allDoneSignal.AddParticipant(); _ = Task.Factory.StartNew(() => @@ -1152,9 +1244,3421 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion + #region RecyclableList IndexOf v2 + + internal class RecyclableListIndexOfV2 : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; + + 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 int _blockSize; + private byte _blockSizePow2BitShift; + private int _blockSizeMinus1; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCountIndexOfStep; + protected long _longCount; + + public long LongCount + { + get => _longCount; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableListIndexOfV2 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2BitShift)); + int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2BitShift)); + int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private static long DoLongIndexOfParallel(in RecyclableListIndexOfV2 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndex; + } + + //T itemToFind = itemRange.ItemToFind; + //int lastBlockWithData = itemRange.LastBlockWithData; + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData + ? -1 + : blockIndex == list._nextItemBlockIndex + ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) + //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); + + return index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private long DoIndexOfParallel(T item) + { + var itemFoundSignal = ManualResetEventSlimmerPool.Create(false); + using var allDoneSignal = new Barrier(1); + long foundItemIndex = -1L; + Exception? ex = null; + + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + ItemRangesIteratorV1.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); + allDoneSignal.SignalAndWait(); + itemFoundSignal.Dispose(); + if (ex == null) + { + return foundItemIndex; + } + + ex.CaptureAndRethrow(); + return ItemNotFoundIndex; + + //int blockIndex = 0; + //int itemIndex = 0; + //long itemsCount = _longCount; + //while (itemsCount > step) + //{ + // if (!LocalIndexOf(new(blockIndex, itemIndex, step))) + // { + // break; + // } + + // itemsCount -= step; + // //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); + // blockIndex += (int)((itemIndex + step) >> _blockSizePow2BitShift); + // itemIndex = (int)((itemIndex + step) & _blockSizeMinus1); + // //itemIndex += step; + // //while (itemIndex >= blockSize) + // //{ + // // blockIndex++; + // // itemIndex -= blockSize; + // //} + //} + + //_ = LocalIndexOf(new(blockIndex, itemIndex, itemsCount)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + bool LocalIndexOf(ItemRangeV1 itemRange) + { + _ = allDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal); + if (index >= 0) + { + itemFoundSignal.Set(); + foundItemIndex = index; + } + } + catch (Exception e) + { + //_ = Interlocked.Exchange(ref ex, e); + lock (allDoneSignal) + { + ex = e; + } + } + finally + { + itemRange.Dispose(); + allDoneSignal.RemoveParticipant(); + } + }); + //.ContinueWith(x => + //{ + // if (x.Exception != null) + // { + // lock (allDoneSignal) + // { + // ex = x.Exception; + // } + // } + //}); + + return !itemFoundSignal.IsSet; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableListIndexOfV2 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + else + { + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableListIndexOfV2 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableListIndexOfV2 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListIndexOfV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListIndexOfV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListIndexOfV2 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableListIndexOfV2 sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + _longCountIndexOfStep = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 + ? ItemNotFoundIndex + //// Optimization for tiny collections to avoid any vars etc. + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOfParallel(item)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListIndexOfV2() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + #region RecyclableList IndexOf v3 + + internal class RecyclableListIndexOfV3 : IDisposable, IList + { + private const long ItemNotFoundIndexLong = -1L; + private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; + + 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 int _blockSize; + private byte _blockSizePow2BitShift; + private int _blockSizeMinus1; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCountIndexOfStep; + protected long _longCount; + + public long LongCount + { + get => _longCount; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableListIndexOfV3 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2BitShift); + int sourceItemIndex = (int)((index + 1) & list._blockSizeMinus1); + + int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); + int targetItemIndex = (int)(index & list._blockSizeMinus1); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private static long DoLongIndexOfParallel(in RecyclableListIndexOfV3 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndexLong; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndexLong; + } + + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndexLong; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData + ? ItemNotFoundIndex + : blockIndex == list._nextItemBlockIndex + ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + + return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private long DoIndexOfParallel(T item) + { + var itemFoundSignal = ManualResetEventSlimmerPool.Create(false); + using var allDoneSignal = new Barrier(1); + long foundItemIndex = ItemNotFoundIndexLong; + Exception? ex = null; + + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + ItemRangesIteratorV1.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); + allDoneSignal.SignalAndWait(); + itemFoundSignal.Dispose(); + if (ex == null) + { + return foundItemIndex; + } + + ex.CaptureAndRethrow(); + return ItemNotFoundIndexLong; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + bool LocalIndexOf(ItemRangeV1 itemRange) + { + _ = allDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal); + if (index >= 0) + { + itemFoundSignal.Set(); + foundItemIndex = index; + } + } + catch (Exception e) + { + //_ = Interlocked.Exchange(ref ex, e); + lock (allDoneSignal) + { + ex = e; + } + } + finally + { + itemRange.Dispose(); + allDoneSignal.RemoveParticipant(); + } + }); + //.ContinueWith(x => + //{ + // if (x.Exception != null) + // { + // lock (allDoneSignal) + // { + // ex = x.Exception; + // } + // } + //}); + + return !itemFoundSignal.IsSet; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndexLong; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndexLong; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableListIndexOfV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + else + { + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableListIndexOfV3 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableListIndexOfV3 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListIndexOfV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListIndexOfV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListIndexOfV3 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + _longCountIndexOfStep = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 + ? ItemNotFoundIndex + : checked((int)DoIndexOfParallel(item)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndexLong + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListIndexOfV3() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + #region RecyclableList IndexOf v4 + + internal static class RecyclableListIndexOfV4Extensions + { + private const long ItemNotFoundIndexLong = -1L; + private const int ItemNotFoundIndex = -1; + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public static long DoLongIndexOfParallel(this RecyclableListIndexOfV4 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndexLong; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndexLong; + } + + //T itemToFind = itemRange.ItemToFind; + //int lastBlockWithData = itemRange.LastBlockWithData; + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndexLong; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 + ? ItemNotFoundIndex + : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + + return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; + } + } + + internal sealed class ParallelSynchronizationContextV4 : IDisposable + { + public readonly ManualResetEventSlimmer ItemFoundSignal; + public readonly Barrier AllDoneSignal; + public long FoundItemIndex; + public Exception? Exception; + + public ParallelSynchronizationContextV4(int participantCount) + { + ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); + AllDoneSignal = new(participantCount); + } + + public void Dispose() + { + ItemFoundSignal.Dispose(); + AllDoneSignal.Dispose(); + + GC.SuppressFinalize(this); + } + } + + internal class RecyclableListIndexOfV4 : IDisposable, IList + { + private const long ItemNotFoundIndexLong = -1L; + private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; + + 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]; + + internal int _blockSize; + internal byte _blockSizePow2BitShift; + internal int _blockSizeMinus1; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + internal T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + internal long _longCountIndexOfStep; + internal long _longCount; + + public long LongCount + { + get => _longCount; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableListIndexOfV4 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2BitShift); + int sourceItemIndex = (int)((index + 1) & list._blockSizeMinus1); + + int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); + int targetItemIndex = (int)(index & list._blockSizeMinus1); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private long DoIndexOfParallel(T item) + { + var synchronizationContext = new ParallelSynchronizationContextV4(1); + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + Iterate(synchronizationContext, this, item, ScheduleIndexOfTasks); + + synchronizationContext.AllDoneSignal.SignalAndWait(); + if (synchronizationContext.Exception == null) + { + return synchronizationContext.FoundItemIndex; + } + + synchronizationContext.Exception.CaptureAndRethrow(); + return ItemNotFoundIndexLong; + } + + private static bool ScheduleIndexOfTasks(ParallelSynchronizationContextV4 context, RecyclableListIndexOfV4 list, T itemToFind, ItemRangeV1 itemRange) + { + _ = context.AllDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = list.DoLongIndexOfParallel(itemRange, itemToFind, context.ItemFoundSignal); + if (index >= 0) + { + context.ItemFoundSignal.Set(); + _ = Interlocked.Exchange(ref context.FoundItemIndex, index); + } + } + catch (Exception e) + { + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.AllDoneSignal.RemoveParticipant(); + } + }); + + return !context.ItemFoundSignal.IsSet; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] + public static void Iterate(in ParallelSynchronizationContextV4 context, RecyclableListIndexOfV4 list, in T itemToFind, Func, T, ItemRangeV1, bool> action) + { + int itemIndex = 0; + ItemRangeV1 itemRange; + int blockIndex = 0; + long itemsCount = list._longCount; + long step = list._longCountIndexOfStep; + while (itemsCount > step) + { + itemRange = new(blockIndex, itemIndex, step); + if (!action(context, list, itemToFind, itemRange)) + { + break; + } + + itemsCount -= step; + //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); + blockIndex += (int)((itemIndex + step) >> list._blockSizePow2BitShift); + itemIndex = (int)((itemIndex + step) & list._blockSizeMinus1); + //itemIndex += step; + //while (itemIndex >= blockSize) + //{ + // blockIndex++; + // itemIndex -= blockSize; + //} + } + + itemRange = new(blockIndex, itemIndex, itemsCount); + _ = action(context, list, itemToFind, itemRange); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndexLong; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndexLong; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableListIndexOfV4 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + else + { + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableListIndexOfV4 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableListIndexOfV4 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListIndexOfV4(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableListIndexOfV4(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListIndexOfV4 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + _longCountIndexOfStep = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], 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) => _longCount == 0 + ? ItemNotFoundIndex + : checked((int)DoIndexOfParallel(item)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndexLong + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListIndexOfV4() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + #endregion + public partial class RecyclableListPocBenchmarks { - [Benchmark(Baseline = true)] + // [Benchmark(Baseline = true)] public void RecyclableList_IndexOfV1() { var data = TestObjects; @@ -1166,6 +4670,54 @@ namespace Recyclable.Collections.Benchmarks.POC } } + // [Benchmark(Baseline = true)] + public void PooledList_IndexOf() + { + var data = TestObjects; + var list = TestPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + // [Benchmark(Baseline = false)] + public void RecyclableList_IndexOfV2() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableListIndexOfV2; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + // [Benchmark(Baseline = false)] + public void RecyclableList_IndexOfV3() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableListIndexOfV3; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + // [Benchmark(Baseline = true)] + public void RecyclableList_IndexOfV4() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableListIndexOfV4; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + [Benchmark(Baseline = false)] public void RecyclableList_IndexOf() { diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs index ca2013c..69e381e 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; +using Collections.Pooled; using Recyclable.Collections.Benchmarks.Core; namespace Recyclable.Collections.Benchmarks.POC @@ -10,9 +11,17 @@ namespace Recyclable.Collections.Benchmarks.POC private long[]? _testArray; public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); private RecyclableList? _testRecyclableList; - public RecyclableList TestObjectsAsRecyclableList => _testRecyclableList ?? throw new NullReferenceException("Something is wrong and RecyclableList is null"); + public RecyclableList TestObjectsAsRecyclableList => _testRecyclableList ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableList)} is null"); private RecyclableListIndexOfV1? _testRecyclableListIndexOfV1; - internal RecyclableListIndexOfV1 TestObjectsAsRecyclableListIndexOfV1 => _testRecyclableListIndexOfV1 ?? throw new NullReferenceException("Something is wrong and RecyclableListIndexOfV1 is null"); + internal RecyclableListIndexOfV1 TestObjectsAsRecyclableListIndexOfV1 => _testRecyclableListIndexOfV1 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV1)} is null"); + private RecyclableListIndexOfV2? _testRecyclableListIndexOfV2; + internal RecyclableListIndexOfV2 TestObjectsAsRecyclableListIndexOfV2 => _testRecyclableListIndexOfV2 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV2)} is null"); + private RecyclableListIndexOfV3? _testRecyclableListIndexOfV3; + internal RecyclableListIndexOfV3 TestObjectsAsRecyclableListIndexOfV3 => _testRecyclableListIndexOfV3 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV3)} is null"); + private RecyclableListIndexOfV4? _testRecyclableListIndexOfV4; + internal RecyclableListIndexOfV4 TestObjectsAsRecyclableListIndexOfV4 => _testRecyclableListIndexOfV4 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV4)} is null"); + private PooledList? _testPooledList; + internal PooledList TestPooledList => _testPooledList ?? throw new NullReferenceException($"Something is wrong and {nameof(PooledList)} is null"); private static readonly MethodInfo _ensureCapacityNewFunc; private static readonly MethodInfo _ensureCapacityV1Func; @@ -47,6 +56,9 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.RecyclableList_EnsureCapacityV3_ByBlockSize(); benchmark.RecyclableList_IndexOf(); benchmark.RecyclableList_IndexOfV1(); + benchmark.RecyclableList_IndexOfV2(); + benchmark.RecyclableList_IndexOfV3(); + benchmark.RecyclableList_IndexOfV4(); } [GlobalSetup] @@ -55,6 +67,10 @@ namespace Recyclable.Collections.Benchmarks.POC _testArray = DataGenerator.EnumerateTestObjects(TestObjectCount); _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); _testRecyclableListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testRecyclableListIndexOfV2 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testRecyclableListIndexOfV3 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testRecyclableListIndexOfV4 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testPooledList = new PooledList(TestObjects, ClearMode.Auto); // Warm up memory pools for fair comparison with 1st run RecyclableList_EnsureCapacity_ByPowOf2(); @@ -63,6 +79,10 @@ namespace Recyclable.Collections.Benchmarks.POC RecyclableList_EnsureCapacityV3_ByPowOf2(); RecyclableList_IndexOf(); RecyclableList_IndexOfV1(); + RecyclableList_IndexOfV2(); + RecyclableList_IndexOfV3(); + RecyclableList_IndexOfV4(); + PooledList_IndexOf(); } [GlobalCleanup] @@ -73,6 +93,15 @@ namespace Recyclable.Collections.Benchmarks.POC _testRecyclableList = null; _testRecyclableListIndexOfV1?.Dispose(); _testRecyclableListIndexOfV1 = null; + _testRecyclableListIndexOfV2?.Dispose(); + _testRecyclableListIndexOfV2 = null; + _testRecyclableListIndexOfV3?.Dispose(); + _testRecyclableListIndexOfV3 = null; + _testRecyclableListIndexOfV4?.Dispose(); + _testRecyclableListIndexOfV4 = null; + _testPooledList?.Dispose(); + _testPooledList = null; + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); } } } diff --git a/Recyclable.Collections/ItemRange.cs b/Recyclable.Collections/ItemRange.cs new file mode 100644 index 0000000..4a1d273 --- /dev/null +++ b/Recyclable.Collections/ItemRange.cs @@ -0,0 +1,32 @@ +using Recyclable.Collections.Pools; + +namespace Recyclable.Collections +{ + public sealed class ItemRange + { + public int BlockIndex; + public int StartingItemIndex; + public long ItemsToSearchCount; + internal bool _returned; + + public ItemRange() + { + } + + public ItemRange(int blockIndex, int startingItemIndex, long itemsToSearchCount) + { + BlockIndex = blockIndex; + ItemsToSearchCount = itemsToSearchCount; + StartingItemIndex = startingItemIndex; + } + + public void Dispose() + { + if (!_returned) + { + _returned = true; + ItemRangePool.Shared.Return(this); + } + } + } +} diff --git a/Recyclable.Collections/ItemRangesIterator.cs b/Recyclable.Collections/ItemRangesIterator.cs index 685f0b4..74c7d9e 100644 --- a/Recyclable.Collections/ItemRangesIterator.cs +++ b/Recyclable.Collections/ItemRangesIterator.cs @@ -1,237 +1,29 @@ -using Microsoft.Extensions.ObjectPool; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; +using Recyclable.Collections.Parallel; namespace Recyclable.Collections { - public sealed class ItemRange : IDisposable - { - private static readonly ObjectPool _defaultPool = ItemRangePool.Shared; - - public int BlockIndex; - public int StartingItemIndex; - public long ItemsToSearchCount; - private ObjectPool? _assignedPool; - - public ItemRange() - { - - } - - public ItemRange(int blockIndex, int startingItemIndex, long itemsToSearchCount) - { - BlockIndex = blockIndex; - ItemsToSearchCount = itemsToSearchCount; - StartingItemIndex = startingItemIndex; - } - - public void Dispose() - { - if (_assignedPool != null) - { - var assignedPool = _assignedPool; - _assignedPool = null; - assignedPool.Return(this); - } - - GC.SuppressFinalize(this); - } - - public static ItemRange Create(int blockIndex, int startingItemIndex, long itemsToSearchCount) - { - var itemRange = _defaultPool.Get(); - itemRange.BlockIndex = blockIndex; - itemRange.ItemsToSearchCount = itemsToSearchCount; - itemRange.StartingItemIndex = startingItemIndex; - return itemRange; - } - } - - public static class ItemRangePool - { - [ThreadStatic] - private static ObjectPool _shared; - public static ObjectPool Shared => _shared; - - static ItemRangePool() - { - _shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - } - } - - public sealed class BarrierSlimmer : IDisposable - { - - private static readonly ObjectPool _defaultPool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - private ObjectPool? _assignedPool; - private readonly object _lock = new(); - - public bool HasParticipants; - public int Participants; - - public void AddParticipant() - { - Monitor.Enter(_lock); - if (++Participants == 1) - { - HasParticipants = true; - } - - Monitor.Exit(_lock); - } - - public void RemoveParticipant() - { - Monitor.Enter(_lock); - if (Participants == 0) - { - Monitor.Exit(_lock); - ThrowNoMoreParticipants(); - } - - if (--Participants == 0) - { - HasParticipants = false; - } - - Monitor.Exit(_lock); - } - - private static void ThrowNoMoreParticipants() - { - throw new InvalidOperationException("There are no more participants. The operation is not allowed"); - } - - public BarrierSlimmer() - { - } - - public BarrierSlimmer(int participants) - { - Participants = participants; - HasParticipants = participants > 0; - } - - public static BarrierSlimmer Create(int participants) - { - var result = _defaultPool.Get(); - result.Participants = participants; - result.HasParticipants = participants > 0; - return result; - } - - public void Dispose() - { - var assignedPool = _assignedPool; - _assignedPool = null; - assignedPool?.Return(this); - - GC.SuppressFinalize(this); - } - - public void SignalAndWait() - { - RemoveParticipant(); - - if (!HasParticipants) - { - return; - } - - if (!HasParticipants) - { - return; - } - - if (!HasParticipants) - { - return; - } - - var waitSpinner = new SpinWait(); - while (HasParticipants) - { - waitSpinner.SpinOnce(); - } - } - } - - public sealed class ManualResetEventSlimmer : IDisposable - { - private static readonly ObjectPool _defaultPool = ManualResetEventSlimmerPool.Shared; - - private bool _isSet; - public bool IsSet => _isSet; - private ObjectPool? _assignedPool; - - public ManualResetEventSlimmer(bool initialState) - { - _isSet = initialState; - } - - public ManualResetEventSlimmer() - { - - } - - public void Set() - { - _isSet = true; - } - - public void Reset() - { - _isSet = false; - } - - public void Dispose() - { - var assignedPool = _assignedPool; - _assignedPool = null; - assignedPool?.Return(this); - - GC.SuppressFinalize(this); - } - - public static ManualResetEventSlimmer Create(bool initialState) - { - ManualResetEventSlimmer result = _defaultPool.Get(); - result._assignedPool = _defaultPool; - result._isSet = initialState; - return result; - } - } - - public static class ManualResetEventSlimmerPool - { - [ThreadStatic] - private static ObjectPool _shared; - public static ObjectPool Shared => _shared; - - static ManualResetEventSlimmerPool() - { - _shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - } - } - - public static class ItemRangesIterator + public static class ItemRangesIterator { [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] - public static void Iterate(int blockIndex, int blockSize, byte blockSizePow2BitShift, long itemsCount, long step, Func action) + public static void Iterate(in ParallelSynchronizationContext context, in SearchConfiguration search, Func, ItemRange, bool> action) { int itemIndex = 0; ItemRange itemRange; - while (itemsCount > step) + int blockIndex = search.StartingBlockIndex; + long itemsCount = search.ItemsCount; + while (itemsCount > search.Step) { - itemRange = ItemRange.Create(blockIndex, itemIndex, step); - if (!action(itemRange)) + itemRange = new(blockIndex, itemIndex, search.Step); + if (!action(context, search, itemRange)) { break; } - itemsCount -= step; + itemsCount -= search.Step; //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); - blockIndex += (int)((itemIndex + step) >> blockSizePow2BitShift); - itemIndex = (int)((itemIndex + step) & (blockSize - 1)); + blockIndex += (int)((itemIndex + search.Step) >> search.BlockSizePow2BitShift); + itemIndex = (int)((itemIndex + search.Step) & search.BlockSizeMinus1); //itemIndex += step; //while (itemIndex >= blockSize) //{ @@ -240,8 +32,8 @@ namespace Recyclable.Collections //} } - itemRange = ItemRange.Create(blockIndex, itemIndex, itemsCount); - _ = action(itemRange); + itemRange = new(blockIndex, itemIndex, itemsCount); + _ = action(context, search, itemRange); } } } diff --git a/Recyclable.Collections/MemoryBucket.cs b/Recyclable.Collections/MemoryBucket.cs new file mode 100644 index 0000000..d786d1a --- /dev/null +++ b/Recyclable.Collections/MemoryBucket.cs @@ -0,0 +1,22 @@ +namespace Recyclable.Collections +{ + public class MemoryBucket + { + public MemoryBucket(T[] memory, MemoryBucket nextBucket) + { + _memory = memory; + _nextBucket = nextBucket; + } + + public MemoryBucket(T[] memory) + { + _memory = memory; + } + + private T[] _memory; + public T[] Memory { get => _memory; set => _memory = value; } + + private MemoryBucket? _nextBucket; + public MemoryBucket? NextBucket { get => _nextBucket; set => _nextBucket = value; } + } +} diff --git a/Recyclable.Collections/OneSizeArrayPool.cs b/Recyclable.Collections/OneSizeArrayPool.cs deleted file mode 100644 index 3f54b31..0000000 --- a/Recyclable.Collections/OneSizeArrayPool.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System.Buffers; -using System.Collections.Concurrent; - -namespace Recyclable.Collections -{ - public class MemoryBucket - { - public MemoryBucket(T[] memory, MemoryBucket nextBucket) - { - _memory = memory; - _nextBucket = nextBucket; - } - - public MemoryBucket(T[] memory) - { - _memory = memory; - } - - private T[] _memory; - public T[] Memory { get => _memory; set => _memory = value; } - - private MemoryBucket? _nextBucket; - public MemoryBucket? NextBucket { get => _nextBucket; set => _nextBucket = value; } - } - - public static class RecyclableArrayPool - { - public static readonly OneSizeArrayPool Null = new(0, 0); - - private static readonly ConcurrentDictionary> _pools = new(); - - public static ArrayPool Shared(int blockSize) - { - //lock (_lock) - { - if (!_pools.TryGetValue(blockSize, out ArrayPool? pool)) - { - pool = new OneSizeArrayPool(blockSize, 100); - pool = _pools.TryAdd(blockSize, pool) ? pool : _pools[blockSize]; - } - - return pool; - } - - //_lock.Wait(); - //try - //{ - //} - //finally - //{ - // _lock.Release(); - //} - } - } - - public class OneSizeArrayPool : ArrayPool - { - private readonly int _blockSize; - private readonly int _minBlockCount; - private int _blockCount; - private MemoryBucket? _currentBucket; - //SemaphoreSlim _lock = new(1); - //ManualResetEventSlim _lock = new(true, 1); - private readonly object _updateLock = new(); - //private Task? _bufferFillUpTask; - - //private Task CreateAdditionalBucket(int minimumLength, int blockCount) - //{ - // return Task.Factory.StartNew(() => - // { - // MemoryBucket firstBucket = new(new T[minimumLength]); - // MemoryBucket currentBucket = firstBucket; - // for (var i = 0; i < blockCount; i++) - // { - // currentBucket = new(new T[minimumLength], currentBucket); - // } - - // lock (_updateLock) - // //_lock.Wait(); - // //try - // { - // currentBucket.NextBucket = _currentBucket; - // _currentBucket = firstBucket; - // _blockCount += blockCount; - // //_bufferFillUpTask = null; - // } - // //finally - // //{ - // // _ = _lock.Release(); - // // //_lock.Set(); - // //} - // }); - //} - - public OneSizeArrayPool(int blockSize, int minBlockCount) - { - _blockSize = blockSize; - _minBlockCount = minBlockCount; - } - - public override T[] Rent(int minimumLength) - { - if (minimumLength != _blockSize) - { - ThrowWrongRentedArraySizeException(minimumLength, _blockSize); - } - - MemoryBucket? bucket; - lock (_updateLock) - //_lock.Wait(); - //try - { - if (_currentBucket != null) - { - _blockCount--; - bucket = _currentBucket; - _currentBucket = _currentBucket.NextBucket; - } - else - { - bucket = null; - //if (_bufferFillUpTask == null) - //{ - // _bufferFillUpTask = CreateAdditionalBucket(minimumLength, _minBlockCount); - //} - } - } - //finally - //{ - // _ = _lock.Release(); - // //_lock.Set(); - //} - - return bucket != null ? bucket.Memory : new T[_blockSize]; - } - - public override void Return(T[] array, bool clearArray = false) - { - if (array.Length != _blockSize) - { - ThrowWrongReturnedArraySizeException(array.Length, _blockSize); - } - - MemoryBucket bucket = new(array); - if (clearArray) - { - Array.Clear(array, 0, array.Length); - } - - lock (_updateLock) - //lock (_lock) - //_lock.Wait(); - //try - { - if (_blockCount < _minBlockCount) - { - bucket.NextBucket = _currentBucket; - _currentBucket = bucket; - _blockCount++; - } - } - //finally - //{ - // _ = _lock.Release(); - // //_lock.Set(); - //} - } - - private static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize) - { - throw new InvalidOperationException($"The array size {returnedLength} doesn't match the size {blockSize} of the pool buffer and cannot be returned to this pool."); - } - - private static void ThrowWrongRentedArraySizeException(int requestedSize, int blockSize) - { - throw new InvalidOperationException($"The requested size {requestedSize} doesn't match the size {blockSize} of the pool buffer and cannot be taken from this pool."); - } - } -} diff --git a/Recyclable.Collections/Parallel/BarrierSlimmer.cs b/Recyclable.Collections/Parallel/BarrierSlimmer.cs new file mode 100644 index 0000000..2c5e594 --- /dev/null +++ b/Recyclable.Collections/Parallel/BarrierSlimmer.cs @@ -0,0 +1,117 @@ +using Microsoft.Extensions.ObjectPool; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Parallel +{ + public sealed class BarrierSlimmer : IDisposable + { + private static readonly ObjectPool _defaultPool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + private ObjectPool? _assignedPool; + private readonly SpinLock _lock = new(false); + + public bool HasParticipants; + public int Participants; + + [MethodImpl(MethodImplOptions.NoInlining)] + public void AddParticipant() + { + bool isLocked = false; + _lock.Enter(ref isLocked); + + Participants++; + if (Participants == 1) + { + HasParticipants = true; + } + + if (isLocked) + { + _lock.Exit(false); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void RemoveParticipant() + { + if (!HasParticipants) + { + ThrowNoMoreParticipants(); + } + + bool isLocked = false; + _lock.Enter(ref isLocked); + + Participants--; + if (Participants == 0) + { + HasParticipants = false; + } + + if (isLocked) + { + _lock.Exit(false); + } + } + + private static void ThrowNoMoreParticipants() + { + throw new InvalidOperationException("There are no more participants. The operation is not allowed"); + } + + public BarrierSlimmer() + { + _lock = new(false); + } + + public BarrierSlimmer(int participants) + { + _lock = new(false); + Participants = participants; + HasParticipants = participants > 0; + } + + public static BarrierSlimmer Create(int participants) + { + var result = _defaultPool.Get(); + result.Participants = participants; + result.HasParticipants = participants > 0; + return result; + } + + public void Dispose() + { + var assignedPool = _assignedPool; + _assignedPool = null; + assignedPool?.Return(this); + + GC.SuppressFinalize(this); + } + + public void SignalAndWait() + { + RemoveParticipant(); + + //if (!HasParticipants) + //{ + // return; + //} + + //if (!HasParticipants) + //{ + // return; + //} + + //if (!HasParticipants) + //{ + // return; + //} + + // SpinWait.SpinUntil(() => HasParticipants); + var waitSpinner = new SpinWait(); + while (HasParticipants) + { + waitSpinner.SpinOnce(0); + } + } + } +} diff --git a/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs b/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs new file mode 100644 index 0000000..c56260c --- /dev/null +++ b/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs @@ -0,0 +1,37 @@ +namespace Recyclable.Collections.Parallel +{ + public sealed class ManualResetEventSlimmer + { + internal bool _isSet; + public bool IsSet => _isSet; + internal bool _returned; + + public ManualResetEventSlimmer() + { + } + + public ManualResetEventSlimmer(bool initialState) + { + _isSet = initialState; + } + + public void Set() + { + _isSet = true; + } + + public void Reset() + { + _isSet = false; + } + + public void Dispose() + { + if (!_returned) + { + _returned = true; + ManualResetEventSlimmerPool.Shared.Return(this); + } + } + } +} diff --git a/Recyclable.Collections/Parallel/ManualResetEventSlimmerPool.cs b/Recyclable.Collections/Parallel/ManualResetEventSlimmerPool.cs new file mode 100644 index 0000000..5cee73e --- /dev/null +++ b/Recyclable.Collections/Parallel/ManualResetEventSlimmerPool.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.ObjectPool; + +namespace Recyclable.Collections.Parallel +{ + public static class ManualResetEventSlimmerPool + { + public static readonly SpinLock _updateLock = new(false); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + public static ManualResetEventSlimmer Create(bool initialState) + { + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + ManualResetEventSlimmer result = Shared.Get(); + if (lockTaken) + { + _updateLock.Exit(false); + } + + result._returned = false; + if (result._isSet != initialState) + { + result._isSet = initialState; + } + + return result; + } + } +} diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs new file mode 100644 index 0000000..649027c --- /dev/null +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs @@ -0,0 +1,38 @@ +namespace Recyclable.Collections.Parallel +{ + public sealed class ParallelSynchronizationContext : IDisposable + { + public readonly ManualResetEventSlimmer ItemFoundSignal; + public readonly Barrier AllDoneSignal; + public long FoundItemIndex; + public Exception? Exception; + internal bool _returned; + + public ParallelSynchronizationContext() + { + ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); + AllDoneSignal = new(0); + } + + public ParallelSynchronizationContext(int participantCount) + { + ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); + AllDoneSignal = new(participantCount); + } + + public void Dispose() + { + if (!_returned) + { + _returned = true; + ParallelSynchronizationContextPool.Shared.Return(this); + } + else + { + ItemFoundSignal.Dispose(); + AllDoneSignal.Dispose(); + GC.SuppressFinalize(this); + } + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs new file mode 100644 index 0000000..88fa317 --- /dev/null +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.ObjectPool; + +namespace Recyclable.Collections.Parallel +{ + public static class ParallelSynchronizationContextPool + { + private static readonly SpinLock _updateLock = new(false); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + public static ParallelSynchronizationContext GetWithOneParticipant() + { + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + var context = Shared.Get(); + if (lockTaken) + { + _updateLock.Exit(false); + } + + if (context.ItemFoundSignal._isSet) + { + context.ItemFoundSignal.Reset(); + } + + switch (context.AllDoneSignal.ParticipantsRemaining) + { + case > 1: + context.AllDoneSignal.RemoveParticipants(context.AllDoneSignal.ParticipantsRemaining - 1); + break; + + case 0: + context.AllDoneSignal.AddParticipant(); + break; + } + + if (context.Exception != null) + { + context.Exception = null; + } + + if (context.FoundItemIndex >= 0) + { + context.FoundItemIndex = -1L; + } + + context._returned = false; + return context; + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections/Pools/ItemRangePool.cs b/Recyclable.Collections/Pools/ItemRangePool.cs new file mode 100644 index 0000000..532988f --- /dev/null +++ b/Recyclable.Collections/Pools/ItemRangePool.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.ObjectPool; + +namespace Recyclable.Collections.Pools +{ + public static class ItemRangePool + { + private static readonly SpinLock _updateLock = new(false); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + public static ItemRange Create(int blockIndex, int startingItemIndex, long itemsToSearchCount) + { + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + var itemRange = Shared.Get(); + if (lockTaken) + { + _updateLock.Exit(false); + } + + itemRange._returned = false; + itemRange.BlockIndex = blockIndex; + itemRange.ItemsToSearchCount = itemsToSearchCount; + itemRange.StartingItemIndex = startingItemIndex; + return itemRange; + } + } +} diff --git a/Recyclable.Collections/Pools/OneSizeArrayPool.cs b/Recyclable.Collections/Pools/OneSizeArrayPool.cs new file mode 100644 index 0000000..11859c5 --- /dev/null +++ b/Recyclable.Collections/Pools/OneSizeArrayPool.cs @@ -0,0 +1,86 @@ +using System.Buffers; + +namespace Recyclable.Collections.Pools +{ + public class OneSizeArrayPool : ArrayPool + { + private readonly int _blockSize; + private readonly int _minBlockCount; + private int _blockCount; + private MemoryBucket? _currentBucket; + private readonly SpinLock _updateLock = new(false); + + public OneSizeArrayPool(int blockSize, int minBlockCount) + { + _blockSize = blockSize; + _minBlockCount = minBlockCount; + } + + public override T[] Rent(int minimumLength) + { + if (minimumLength != _blockSize) + { + ThrowWrongRentedArraySizeException(minimumLength, _blockSize); + } + + MemoryBucket? bucket; + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + if (_currentBucket != null) + { + _blockCount--; + bucket = _currentBucket; + _currentBucket = _currentBucket.NextBucket; + } + else + { + bucket = null; + } + + if (lockTaken) + { + _updateLock.Exit(false); + } + + return bucket != null ? bucket.Memory : new T[_blockSize]; + } + + public override void Return(T[] array, bool clearArray = false) + { + if (array.Length != _blockSize) + { + ThrowWrongReturnedArraySizeException(array.Length, _blockSize); + } + + MemoryBucket bucket = new(array); + if (clearArray) + { + Array.Clear(array, 0, array.Length); + } + + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + if (_blockCount < _minBlockCount) + { + bucket.NextBucket = _currentBucket; + _currentBucket = bucket; + _blockCount++; + } + + if (lockTaken) + { + _updateLock.Exit(false); + } + } + + private static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize) + { + throw new InvalidOperationException($"The array size {returnedLength} doesn't match the size {blockSize} of the pool buffer and cannot be returned to this pool."); + } + + private static void ThrowWrongRentedArraySizeException(int requestedSize, int blockSize) + { + throw new InvalidOperationException($"The requested size {requestedSize} doesn't match the size {blockSize} of the pool buffer and cannot be taken from this pool."); + } + } +} diff --git a/Recyclable.Collections/Pools/RecyclableArrayPool.cs b/Recyclable.Collections/Pools/RecyclableArrayPool.cs new file mode 100644 index 0000000..3e44463 --- /dev/null +++ b/Recyclable.Collections/Pools/RecyclableArrayPool.cs @@ -0,0 +1,35 @@ +using System.Buffers; +using System.Collections.Concurrent; + +namespace Recyclable.Collections.Pools +{ + public static class RecyclableArrayPool + { + public static readonly OneSizeArrayPool Null = new(0, 0); + + private static readonly ConcurrentDictionary> _pools = new(); + + public static ArrayPool Shared(int blockSize) + { + //lock (_lock) + { + if (!_pools.TryGetValue(blockSize, out ArrayPool? pool)) + { + pool = new OneSizeArrayPool(blockSize, 100); + pool = _pools.TryAdd(blockSize, pool) ? pool : _pools[blockSize]; + } + + return pool; + } + + //_lock.Wait(); + //try + //{ + //} + //finally + //{ + // _lock.Release(); + //} + } + } +} diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 16c4468..03154ca 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections { - public class RecyclableDefaults + public static class RecyclableDefaults { public const int BlockSize = 16_384; public const int Capacity = 1; diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index a09658e..7e64195 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -2,6 +2,10 @@ using System.Collections; using System.Numerics; using System.Runtime.CompilerServices; +using Recyclable.Collections.Parallel; +using Recyclable.Collections.Pools; + +[assembly:InternalsVisibleTo("Recyclable.Collections.Benchmarks")] namespace Recyclable.Collections { @@ -9,16 +13,16 @@ namespace Recyclable.Collections { private const long ItemNotFoundIndexLong = -1L; private const int ItemNotFoundIndex = -1; - private const double OptimalParallelSearchStep = 0.329; + private const double OptimalParallelSearchStep = 0.1;// 0.329; 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 int _blockSize; - private byte _blockSizePow2BitShift; - private int _blockSizeMinus1; + internal int _blockSize; + internal byte _blockSizePow2BitShift; + internal int _blockSizeMinus1; private int _nextItemBlockIndex; private int _nextItemIndex; private int _reservedBlockCount; @@ -27,7 +31,7 @@ namespace Recyclable.Collections protected ArrayPool _memoryBlocksPool; protected ArrayPool _blockArrayPool; - protected T[][] _memoryBlocks; + internal T[][] _memoryBlocks; private long _capacity; public long Capacity @@ -40,8 +44,8 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); - protected long _longCountIndexOfStep; - protected long _longCount; + internal long _longCountIndexOfStep; + internal long _longCount; public long LongCount { @@ -56,7 +60,6 @@ namespace Recyclable.Collections public int ReservedBlockCount => _reservedBlockCount; public int BlockSize => _blockSize; public byte BlockSizePow2BitShift => _blockSizePow2BitShift; - public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -220,12 +223,13 @@ namespace Recyclable.Collections _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; - return; } - [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static long DoLongIndexOfParallel(in RecyclableList list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private static long DoLongIndexOfParallel(RecyclableList list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) { + // & WAS SLOWER without int blockSize = list._blockSize; int blockIndex = itemRange.BlockIndex; long itemsToSearchCount = itemRange.ItemsToSearchCount; @@ -267,77 +271,76 @@ namespace Recyclable.Collections blockIndex++; } - index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData + index = itemsToSearchCount <= 0 //|| blockIndex > list.LastBlockWithData ? ItemNotFoundIndex - : blockIndex == list._nextItemBlockIndex - ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + // : blockIndex == list._nextItemBlockIndex + // ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); - //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) + // ? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; } - [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private long DoIndexOfParallel(T item) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private long DoIndexOfParallel(in T item) { - using var itemFoundSignal = ManualResetEventSlimmer.Create(false); - using var allDoneSignal = new Barrier(1); - long foundItemIndex = ItemNotFoundIndexLong; - Exception? ex = null; - - // (long)(_longCount * 0.329) => most efficient step, based on benchmarks - ItemRangesIterator.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, itemRange => + if (_longCount == 0) { - _ = allDoneSignal.AddParticipant(); - _ = Task.Factory.StartNew(() => - { - try - { - var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal); - if (index >= 0) - { - itemFoundSignal.Set(); - foundItemIndex = index; - } - } - catch (Exception e) - { - //_ = Interlocked.Exchange(ref ex, e); - lock (allDoneSignal) - { - ex = e; - } - } - finally - { - itemRange.Dispose(); - allDoneSignal.RemoveParticipant(); - } - }); - //.ContinueWith(x => - //{ - // if (x.Exception != null) - // { - // lock (allDoneSignal) - // { - // ex = x.Exception; - // } - // } - //}); - - return !itemFoundSignal.IsSet; - }); - allDoneSignal.SignalAndWait(); - if (ex == null) - { - return foundItemIndex; + return ItemNotFoundIndex; } - ex.CaptureAndRethrow(); + using var context = ParallelSynchronizationContextPool.GetWithOneParticipant(); + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + Iterate(context, item); + context.AllDoneSignal.SignalAndWait(); + if (context.Exception == null) + { + return context.FoundItemIndex; + } + + context.Exception.CaptureAndRethrow(); return ItemNotFoundIndexLong; } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] + private static bool ScheduleIndexOfTask(ParallelSynchronizationContext context, RecyclableList list, T itemToFind, ItemRange itemRange) + { + _ = context.AllDoneSignal.AddParticipant(); + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + _ = Task.Factory.StartNew(() => + { + try + { + if (context.ItemFoundSignal.IsSet) + { + return; + } + + var index = DoLongIndexOfParallel(list, itemRange, itemToFind, context.ItemFoundSignal); + if (index >= 0) + { + context.ItemFoundSignal.Set(); + _ = Interlocked.Exchange(ref context.FoundItemIndex, index); + } + } + catch (Exception e) + { + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.AllDoneSignal.RemoveParticipant(); + itemRange.Dispose(); + } + }); + + return !context.ItemFoundSignal.IsSet; + } + [MethodImpl(MethodImplOptions.AggressiveOptimization)] protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) { @@ -427,35 +430,35 @@ namespace Recyclable.Collections } blockIndex = sourceBlockCount; - while (true) - { - newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } - } + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } else { blockIndex = sourceBlockCount; - while (true) - { - newMemoryBlocks[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } - } + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } } list._reservedBlockCount = requiredBlockCount; @@ -471,6 +474,63 @@ namespace Recyclable.Collections return minBlockSize; } + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.NoInlining)] + private void Iterate(in ParallelSynchronizationContext context, in T itemToFind) + { + // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. + + // & WAS SLOWER without + ItemRange itemRange; + int blockIndex = 0; + // & WAS SLOWER + // int blockSize = _blockSize; + long itemIndex = 0; + long itemsCount = _longCount; + // & WAS SLOWER + // int blockSize = _blockSize; + // long step = list._longCountIndexOfStep; + // & WAS SLOWER + // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) + // & WAS SLOWER + // long longCountIndexOfStep = _longCountIndexOfStep; + while (itemsCount > _longCountIndexOfStep) + { + // At this point itemIndex is limited to blockSize range - int. + itemRange = ItemRangePool.Create(blockIndex, (int)itemIndex, _longCountIndexOfStep); + if (!ScheduleIndexOfTask(context, this, itemToFind, itemRange)) + { + break; + } + + itemsCount -= _longCountIndexOfStep; + + // & WAS SLOWER + // blockIndex = (int)Math.DivRem(itemIndex + step, step, out itemIndex); + // & WAS SLOWER + // blockIndex += (int)((itemIndex + list._longCountIndexOfStep) >> list._blockSizePow2BitShift); + // itemIndex = (int)((itemIndex + list._longCountIndexOfStep) & list._blockSizeMinus1); + + itemIndex += _longCountIndexOfStep; + while (itemIndex >= _blockSize) + { + blockIndex++; + itemIndex -= _blockSize; + } + } + + // & WAS SLOWER + // itemRange = new(blockIndex, (int)itemIndex, list._longCount - (blockIndex << list._blockSizePow2BitShift) - itemIndex); + itemRange = ItemRangePool.Create(blockIndex, (int)itemIndex, itemsCount); + _ = ScheduleIndexOfTask(context, this, itemToFind, itemRange); + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) { @@ -951,9 +1011,7 @@ namespace Recyclable.Collections public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndex - : checked((int)DoIndexOfParallel(item)); + public int IndexOf(T item) => checked((int)DoIndexOfParallel(item)); public void Insert(int index, T item) => throw new NotSupportedException(); diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 2823cf7..fedf113 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -6,6 +6,8 @@ namespace Recyclable.Collections public static class RecyclableListExtensions { private const int _minPooledArraySize = RecyclableDefaults.MinPooledArrayLength; + private const long ItemNotFoundIndexLong = -1L; + private const int ItemNotFoundIndex = -1; public static void CopyItems(this T[] memory, long startingIndex, long count, ref T[] destArray, long offset = 1) { diff --git a/Recyclable.Collections/SearchConfiguration.cs b/Recyclable.Collections/SearchConfiguration.cs new file mode 100644 index 0000000..35c8a2c --- /dev/null +++ b/Recyclable.Collections/SearchConfiguration.cs @@ -0,0 +1,29 @@ +namespace Recyclable.Collections +{ + public readonly struct SearchConfiguration + { + public TItem ItemToFind { get; init; } + public TList List { get; init; } + public int BlockSizeMinus1 { get; init; } + public byte BlockSizePow2BitShift { get; init; } + public long ItemsCount { get; init; } + public int StartingBlockIndex { get; init; } + public long Step { get; init; } + + // public SearchConfiguration(TList list, in TItem itemToFind) + // { + // ItemToFind = itemToFind; + // List = list; + // } + + public static SearchConfiguration, TItem> CreateFrom(RecyclableList list, in TItem itemToFind) => new() + { + List = list, + ItemToFind = itemToFind, + BlockSizeMinus1 = list._blockSizeMinus1, + BlockSizePow2BitShift = list._blockSizePow2BitShift, + ItemsCount = list._longCount, + Step = list._longCountIndexOfStep + }; + } +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs index fb9b33c..586fbae 100644 --- a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs +++ b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Recyclable.Collections; +using Recyclable.Collections.Parallel; using System.Numerics; namespace Recyclable.CollectionsTests @@ -11,18 +12,31 @@ namespace Recyclable.CollectionsTests public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, int StartingItemIndex, long ItemsCount)> expected) { // Prepare + var mockList = new RecyclableList(); byte blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); - var actual = new List<(int BlockIndex, int StartingItemIndex, long ItemsCount)>(); + + var synchronizationContext = new ParallelSynchronizationContext(1); + var search = new SearchConfiguration, long>() + { + List = mockList, + ItemToFind = 0, + BlockSizeMinus1 = blockSize - 1, + BlockSizePow2BitShift = blockSizePow2BitShift, + ItemsCount = itemsCount, + Step = step, + }; + + var actualItemRanges = new List<(int BlockIndex, int StartingItemIndex, long ItemsCount)>(); // Act - ItemRangesIterator.Iterate(startingBlockIndex, blockSize, blockSizePow2BitShift, itemsCount, step, (searchInfo) => + ItemRangesIterator.Iterate(synchronizationContext, search, (context, search, itemRange) => { - actual.Add((searchInfo.BlockIndex, searchInfo.StartingItemIndex, searchInfo.ItemsToSearchCount)); + actualItemRanges.Add((itemRange.BlockIndex, itemRange.StartingItemIndex, itemRange.ItemsToSearchCount)); return true; }); // Validate - _ = actual.Should().HaveCount(expected.Count()).And.ContainInConsecutiveOrder(expected); + _ = actualItemRanges.Should().HaveCount(expected.Count()).And.ContainInConsecutiveOrder(expected); } public static IEnumerable TestCases => new[] diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index b4d17e3..ba0f50b 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -2,7 +2,7 @@ namespace Recyclable.CollectionsTests { - public class RecyclableListTestData + public static class RecyclableListTestData { private static IEnumerable DoCreateTestData(long itemsCount) { From 98cac9a563a797d700e124232f6231b8563d0836 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 3 May 2023 12:48:36 +0200 Subject: [PATCH 251/305] Draft RecyclableArrayList performance improvements ported from RecyclableList. --- Recyclable.Collections/RecyclableArrayList.cs | 90 ++++++++++++------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 3ab98d1..850a465 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -18,21 +18,23 @@ namespace Recyclable.Collections throw new ArgumentOutOfRangeException(message, (Exception?)null); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[] SetNewLength(in T[]? source, in int newSize) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static T[] Resize(in T[]? source, int newSize) { ArrayPool arrayPool = _arrayPool; - var sourceLength = source?.Length ?? 0; T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength ? arrayPool.Rent(newSize) : new T[newSize]; - if (sourceLength > 0) + if (source?.Length > 0) { - var sourceSpan = new Memory(source); - var newArraySpan = new Memory(newMemoryBlock); - sourceSpan.CopyTo(newArraySpan); - if (sourceLength >= RecyclableDefaults.MinPooledArrayLength) + Array.Copy(source!, 0, newMemoryBlock, 0, source!.Length); + //var sourceSpan = new Memory(source); + //var newArraySpan = new Memory(newMemoryBlock); + + //sourceSpan.CopyTo(newArraySpan); + + if (source!.Length >= RecyclableDefaults.MinPooledArrayLength) { arrayPool.Return(source!, NeedsClearing); } @@ -41,19 +43,17 @@ namespace Recyclable.Collections return newMemoryBlock; } - protected int EnsureCapacity(in int requestedCapacity) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static int EnsureCapacity(RecyclableArrayList list, int requestedCapacity) { - int oldCapacity = _capacity; - ref T[] memory = ref _memoryBlock; - int newCapacity; - switch (oldCapacity > 0) + switch (list._capacity > 0) { case true: - newCapacity = oldCapacity; + newCapacity = list._capacity; while (newCapacity < requestedCapacity) { - newCapacity *= 2; + newCapacity <<= 1; } break; @@ -63,9 +63,9 @@ namespace Recyclable.Collections break; } - memory = SetNewLength(memory, newCapacity); - newCapacity = memory.Length; - _capacity = newCapacity; + list._memoryBlock = Resize(list._memoryBlock, newCapacity); + newCapacity = list._memoryBlock.Length; + list._capacity = newCapacity; return newCapacity; } @@ -81,7 +81,7 @@ namespace Recyclable.Collections { if (initialCapacity > 0) { - _memoryBlock = SetNewLength(_memoryBlock, initialCapacity); + _memoryBlock = Resize(_memoryBlock, initialCapacity); _capacity = _memoryBlock.Length; } } @@ -116,15 +116,18 @@ namespace Recyclable.Collections public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { - _memoryBlock = SetNewLength(_memoryBlock, initialCapacity); + _memoryBlock = Resize(_memoryBlock, initialCapacity); _capacity = _memoryBlock.Length; AddRange(source); } public T this[int index] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _memoryBlock[index]; - set => new Span(_memoryBlock)[index] = value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _memoryBlock[index] = value; } protected int _capacity; @@ -135,7 +138,7 @@ namespace Recyclable.Collections { if (_capacity != value) { - _memoryBlock = SetNewLength(_memoryBlock, value); + _memoryBlock = Resize(_memoryBlock, value); _capacity = _memoryBlock.Length; } } @@ -144,7 +147,10 @@ namespace Recyclable.Collections protected int _count; public int Count { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] set => _count = value; } @@ -156,12 +162,13 @@ namespace Recyclable.Collections int requiredCapacity = _count + 1; if (_capacity < requiredCapacity) { - _ = EnsureCapacity(requiredCapacity); + _ = EnsureCapacity(this, requiredCapacity); } _memoryBlock[_count++] = item; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(in T[] items) { if (items.LongLength == 0) @@ -173,7 +180,7 @@ namespace Recyclable.Collections int targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } var targetSpan = new Span(_memoryBlock, _count, sourceItemsCount); @@ -182,6 +189,7 @@ namespace Recyclable.Collections _count = targetCapacity; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(List items) { if (items.Count == 0) @@ -193,13 +201,14 @@ namespace Recyclable.Collections var targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } items.CopyTo(_memoryBlock, _count); _count = targetCapacity; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(IList items) { if (items.Count == 0) @@ -211,13 +220,14 @@ namespace Recyclable.Collections var targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } items.CopyTo(_memoryBlock, _count); _count = targetCapacity; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(RecyclableArrayList items) { if (items.Count == 0) @@ -229,7 +239,7 @@ namespace Recyclable.Collections var targetCapacity = _count + sourceItemsCount; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } var targetSpan = new Span(_memoryBlock, _count, sourceItemsCount); @@ -238,6 +248,7 @@ namespace Recyclable.Collections _count = targetCapacity; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(RecyclableList items) { if (items.LongCount == 0) @@ -259,7 +270,7 @@ namespace Recyclable.Collections int targetCapacity = _count + (int)sourceItemsCount; if (_capacity < targetCapacity) { - _ = EnsureCapacity(targetCapacity); + _ = EnsureCapacity(this, targetCapacity); } Span targetSpan = new Span(_memoryBlock)[_count..], @@ -281,7 +292,7 @@ namespace Recyclable.Collections itemsSpan = new(items.AsArray[blockIndex], 0, (int)sourceItemsCount); itemsSpan.CopyTo(targetSpan); } - else if (blockIndex <= items.LastTakenBlockIndex) + else if (blockIndex <= items.LastBlockWithData) { itemsSpan = new(items.AsArray[blockIndex], 0, items.NextItemIndex); itemsSpan.CopyTo(targetSpan); @@ -290,6 +301,7 @@ namespace Recyclable.Collections _count = targetCapacity; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { if (source is T[] sourceArray) @@ -320,7 +332,7 @@ namespace Recyclable.Collections Span memorySpan; if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) { - _ = EnsureCapacity(targetItemIdx + requiredAdditionalCapacity); + _ = EnsureCapacity(this, targetItemIdx + requiredAdditionalCapacity); memorySpan = new(_memoryBlock); foreach (var item in source) { @@ -343,7 +355,7 @@ namespace Recyclable.Collections { if (targetItemIdx + growByCount > capacity) { - capacity = EnsureCapacity(capacity + growByCount); + capacity = EnsureCapacity(this, capacity + growByCount); memorySpan = new(_memoryBlock); available = capacity - targetItemIdx; } @@ -365,13 +377,21 @@ namespace Recyclable.Collections public T[] AsArray => _memoryBlock; + [MethodImpl(MethodImplOptions.NoInlining)] public void Clear() { + if (NeedsClearing) + { + Array.Clear(_memoryBlock, 0, _count); + } + _count = 0; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(T item) => _count > 0 && Array.IndexOf(_memoryBlock, item, 0, _count) >= 0; + [MethodImpl(MethodImplOptions.NoInlining)] public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_memoryBlock, 0, array, arrayIndex, _count); protected static IEnumerable Enumerate(RecyclableArrayList list) @@ -384,15 +404,17 @@ namespace Recyclable.Collections } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T itemToFind) => _count > 0 ? Array.IndexOf(_memoryBlock, itemToFind, 0, _count) : ItemNotFound; + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void Insert(int index, T item) { int oldCount = _count; int requestedCapacity = oldCount + 1; if (_capacity < requestedCapacity) { - _ = EnsureCapacity(requestedCapacity); + _ = EnsureCapacity(this, requestedCapacity); } if (oldCount > 0) @@ -434,7 +456,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void RemoveAt(int index) { - if (index > _count - 1) + if (index >= _count) { ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range"); } @@ -453,8 +475,10 @@ namespace Recyclable.Collections } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public IEnumerator GetEnumerator() => Enumerate(this).GetEnumerator(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() => Enumerate(this).GetEnumerator(); public void Dispose() From 73d3e32107f4e259da45303a062da277e184d2aa Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 3 May 2023 15:19:56 +0200 Subject: [PATCH 252/305] Benchmark performance improvements - pattern for all test cases prepared. --- .../POC/PocBenchmarkBase.cs | 4 +- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 8 -- .../RecyclableListPoc.Benchmarks.IndexOf.cs | 7 +- .../POC/RecyclableListPoc.Benchmarks.cs | 86 +++++++++++++++---- 4 files changed, 70 insertions(+), 35 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 689c121..254f92d 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -27,8 +27,8 @@ namespace Recyclable.Collections.Benchmarks.POC public int BlockSize => TestObjectCount switch { 0 => 1, - > 0 and <= 10 => (int)TestObjectCount, - _ => TestObjectCount / Divider > 0 ? checked((int)(TestObjectCount / Divider)) : checked((int)TestObjectCount) + > 0 and <= 10 => TestObjectCount, + _ => TestObjectCount / Divider > 0 ? TestObjectCount / Divider : TestObjectCount }; public byte BlockSizePow2BitShift => checked((byte)(31 - BitOperations.LeadingZeroCount((uint)BlockSize))); diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index c8ff64a..6a2b9da 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -2808,56 +2808,48 @@ namespace Recyclable.Collections.Benchmarks.POC public partial class RecyclableListPocBenchmarks { - [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV1_ByPowOf2() { using var list = new RecyclableListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityV1Func.Invoke(list, new object[] { TestObjectCount })); } - [Benchmark(Baseline = true)] public void RecyclableList_EnsureCapacityV2_ByPowOf2() { using var list = new RecyclableListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityV2Func.Invoke(null, new object[] { list, TestObjectCount })); } - [Benchmark(Baseline = true)] public void RecyclableList_EnsureCapacityV3_ByPowOf2() { using var list = new RecyclableListV3(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); } - [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacity_ByPowOf2() { using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityNewFunc.Invoke(null, new object[] { list, TestObjectCount })); } - [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV1_ByBlockSize() { using var list = new RecyclableListV1(); DoNothing(_ensureCapacityV1Func.Invoke(list, new object[] { TestObjectCount })); } - [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV2_ByBlockSize() { using var list = new RecyclableListV2(); DoNothing(_ensureCapacityV2Func.Invoke(null, new object[] { list, TestObjectCount })); } - [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacityV3_ByBlockSize() { using var list = new RecyclableListV3(); DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); } - [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacity_ByBlockSize() { using var list = new RecyclableList(); diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs index 4a646f3..3e34a5c 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs @@ -4654,11 +4654,11 @@ namespace Recyclable.Collections.Benchmarks.POC } } } + #endregion public partial class RecyclableListPocBenchmarks { - // [Benchmark(Baseline = true)] public void RecyclableList_IndexOfV1() { var data = TestObjects; @@ -4670,7 +4670,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - // [Benchmark(Baseline = true)] public void PooledList_IndexOf() { var data = TestObjects; @@ -4682,7 +4681,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - // [Benchmark(Baseline = false)] public void RecyclableList_IndexOfV2() { var data = TestObjects; @@ -4694,7 +4692,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - // [Benchmark(Baseline = false)] public void RecyclableList_IndexOfV3() { var data = TestObjects; @@ -4706,7 +4703,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - // [Benchmark(Baseline = true)] public void RecyclableList_IndexOfV4() { var data = TestObjects; @@ -4718,7 +4714,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark(Baseline = false)] public void RecyclableList_IndexOf() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs index 69e381e..0387c74 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -1,14 +1,35 @@ using System.Reflection; -using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using Collections.Pooled; using Recyclable.Collections.Benchmarks.Core; namespace Recyclable.Collections.Benchmarks.POC { + public enum RecyclableListPocBenchmarkType + { + IndexOfV1, + IndexOfV2, + IndexOfV3, + IndexOfV4, + IndexOf_PooledList, + IndexOf, + EnsureCapacity_ByPowOf2, + EnsureCapacityV1_ByPowOf2, + EnsureCapacityV2_ByPowOf2, + EnsureCapacityV3_ByPowOf2, + } + public partial class RecyclableListPocBenchmarks : PocBenchmarkBase { - private long[]? _testArray; + [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.PooledList, BenchmarkType.RecyclableArrayList, BenchmarkType.RecyclableList)] + // [Params(RecyclableListPocBenchmarkType.IndexOf)] + public RecyclableListPocBenchmarkType _benchmarkType; + private Action? _testMethod; + + [Benchmark] + public void RunWhen() => _testMethod!.Invoke(); + + private long[]? _testArray; public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); private RecyclableList? _testRecyclableList; public RecyclableList TestObjectsAsRecyclableList => _testRecyclableList ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableList)} is null"); @@ -64,25 +85,52 @@ namespace Recyclable.Collections.Benchmarks.POC [GlobalSetup] public void Setup() { + Console.WriteLine($"Setting up exptected results data"); _testArray = DataGenerator.EnumerateTestObjects(TestObjectCount); - _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testRecyclableListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testRecyclableListIndexOfV2 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testRecyclableListIndexOfV3 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testRecyclableListIndexOfV4 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testPooledList = new PooledList(TestObjects, ClearMode.Auto); + Console.WriteLine($"Setting up test case for benchmark {{{_benchmarkType}}}"); + switch (_benchmarkType) + { + case RecyclableListPocBenchmarkType.IndexOfV1: + _testRecyclableListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testMethod = () => RecyclableList_IndexOfV1(); + break; + case RecyclableListPocBenchmarkType.IndexOfV2: + _testRecyclableListIndexOfV2 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testMethod = () => RecyclableList_IndexOfV2(); + break; + case RecyclableListPocBenchmarkType.IndexOfV3: + _testRecyclableListIndexOfV3 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testMethod = () => RecyclableList_IndexOfV3(); + break; + case RecyclableListPocBenchmarkType.IndexOfV4: + _testRecyclableListIndexOfV4 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testMethod = () => RecyclableList_IndexOfV4(); + break; + case RecyclableListPocBenchmarkType.IndexOf_PooledList: + _testPooledList = new PooledList(TestObjects, ClearMode.Auto); + _testMethod = () => PooledList_IndexOf(); + break; + case RecyclableListPocBenchmarkType.IndexOf: + _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + _testMethod = () => RecyclableList_IndexOf(); + break; + case RecyclableListPocBenchmarkType.EnsureCapacity_ByPowOf2: + _testMethod = () => RecyclableList_EnsureCapacity_ByPowOf2(); + break; + case RecyclableListPocBenchmarkType.EnsureCapacityV1_ByPowOf2: + _testMethod = () => RecyclableList_EnsureCapacityV1_ByPowOf2(); + break; + case RecyclableListPocBenchmarkType.EnsureCapacityV2_ByPowOf2: + _testMethod = () => RecyclableList_EnsureCapacityV2_ByPowOf2(); + break; + case RecyclableListPocBenchmarkType.EnsureCapacityV3_ByPowOf2: + _testMethod = () => RecyclableList_EnsureCapacityV3_ByPowOf2(); + break; + default: + throw new InvalidOperationException($"Unknown benchmark type {{{_benchmarkType}}}"); + } - // Warm up memory pools for fair comparison with 1st run - RecyclableList_EnsureCapacity_ByPowOf2(); - RecyclableList_EnsureCapacityV1_ByPowOf2(); - RecyclableList_EnsureCapacityV2_ByPowOf2(); - RecyclableList_EnsureCapacityV3_ByPowOf2(); - RecyclableList_IndexOf(); - RecyclableList_IndexOfV1(); - RecyclableList_IndexOfV2(); - RecyclableList_IndexOfV3(); - RecyclableList_IndexOfV4(); - PooledList_IndexOf(); + Console.WriteLine("Data prepared"); } [GlobalCleanup] From bb878caa394af31dc761d939249a926cd3585feb Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 7 May 2023 14:52:37 +0200 Subject: [PATCH 253/305] Benchmarks reworked to save time on test data creation & have more comparable results against baseline. --- .../Benchmarks/Add.cs | 7 +- .../Benchmarks/AddRangeWhenSourceIsArray.cs | 7 +- .../AddRangeWhenSourceIsIEnumerable.cs | 7 +- .../Benchmarks/AddRangeWhenSourceIsIList.cs | 7 +- .../Benchmarks/AddRangeWhenSourceIsList.cs | 7 +- .../AddRangeWhenSourceIsSameType.cs | 6 +- .../Benchmarks/AddRange_WithCapacity.cs | 7 +- .../Benchmarks/Add_WithCapacity.cs | 7 +- .../Benchmarks/Contains_FirstItems.cs | 9 +- .../Benchmarks/Contains_LastItems.cs | 9 +- .../Benchmarks/Count.cs | 10 +- .../Benchmarks/Create.cs | 7 +- .../Benchmarks/Create_WithCapacity.cs | 8 +- .../Benchmarks/GetItem..cs | 13 +- .../Benchmarks/IndexOf_AllItems.cs | 10 +- .../Benchmarks/IndexOf_FirstItems.cs | 10 +- .../Benchmarks/IndexOf_LastItems.cs | 10 +- .../Benchmarks/LongCount.cs | 6 +- .../RecyclableCollections.Benchmarks.cs | 88 +++++--- .../Benchmarks/RemoveAt_FirstItems.cs | 7 +- .../Benchmarks/RemoveAt_LastItems.cs | 7 +- .../Benchmarks/Remove_FirstItems.cs | 7 +- .../Benchmarks/Remove_LastItems.cs | 7 +- .../Benchmarks/SetItem.cs | 10 +- .../POC/ArraySizeLimitPoc.Benchmarks.cs | 19 +- .../POC/BoolOrComparePoc.Benchmarks.cs | 53 +++-- .../POC/DelegateVsComparerPoc.Benchmarks.cs | 191 +++++++++++++----- .../POC/PocBenchmarkBase.cs | 75 ++++++- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 3 +- .../RecyclableListPoc.Benchmarks.IndexOf.cs | 5 +- .../POC/RecyclableListPoc.Benchmarks.cs | 138 ++++++------- .../POC/RoundBenchmarks.cs | 26 ++- .../POC/SpanVsArrayBenchmarks.cs | 44 +++- .../POC/WhenParallelSearchBenchmarks.cs | 110 +++++----- ...markBase.cs => RecyclableBenchmarkBase.cs} | 67 +++++- 35 files changed, 586 insertions(+), 418 deletions(-) rename Recyclable.Collections.Benchmarks/{BenchmarkBase.cs => RecyclableBenchmarkBase.cs} (75%) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs index e881a81..32173a2 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_Add() { var data = TestObjects; @@ -17,7 +15,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_Add() { var data = TestObjects; @@ -29,7 +26,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -41,7 +37,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_Add() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs index f0ea17a..5bc8b09 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -13,7 +11,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = true)] public void PooledList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -21,7 +18,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -29,7 +25,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsArray() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs index aebf532..51866dd 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -13,7 +11,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = true)] public void PooledList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -21,7 +18,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; @@ -29,7 +25,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs index a7bff76..15ea875 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -13,7 +11,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = true)] public void PooledList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsPooledList; @@ -21,7 +18,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; @@ -29,7 +25,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableList; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs index 92178ec..f1c07d2 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -13,7 +11,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = true)] public void PooledList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -21,7 +18,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -29,7 +25,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs index 6133b30..3eed14a 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = true)] public void PooledList_AddRangeWhenSourceIsPooledList() { var data = TestObjectsAsPooledList; @@ -13,7 +11,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; @@ -21,7 +18,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs index 70c2080..1694833 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -14,7 +12,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = true)] public void PooledList_AddRange_WithCapacity() { var data = TestObjects; @@ -23,7 +20,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_AddRange_WithCapacity() { var data = TestObjects; @@ -32,7 +28,6 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark(Baseline = false)] public void RecyclableList_AddRange_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs index 1509649..d727255 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -17,7 +15,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_Add_WithCapacity() { var data = TestObjects; @@ -29,7 +26,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; @@ -41,7 +37,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs index 98e7362..bdfa8dc 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs @@ -1,10 +1,7 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void Array_Contains_FirstItems() { var data = TestObjects; @@ -16,7 +13,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void List_Contains_FirstItems() { var data = TestObjects; @@ -28,7 +24,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_Contains_FirstItems() { var data = TestObjects; @@ -40,7 +35,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Contains_FirstItems() { var data = TestObjects; @@ -52,7 +46,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_Contains_FirstItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs index d0b50c9..68b9e3a 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs @@ -1,10 +1,7 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void Array_Contains_LastItems() { var data = TestObjects; @@ -16,7 +13,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void List_Contains_LastItems() { var data = TestObjects; @@ -28,7 +24,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_Contains_LastItems() { var data = TestObjects; @@ -40,7 +35,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Contains_LastItems() { var data = TestObjects; @@ -52,7 +46,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_Contains_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs index 1803de3..0a45e1b 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs @@ -1,39 +1,31 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void Array_Count() { var data = TestObjects; DoNothing(data.Length); } - //[Benchmark(Baseline = false)] public void List_Count() { var data = TestObjectsAsList; DoNothing(data.Count); } - //[Benchmark(Baseline = true)] public void PooledList_Count() { var data = TestObjectsAsPooledList; DoNothing(data.Count); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Count() { var data = TestObjectsAsRecyclableArrayList; DoNothing(data.Count); } - //[Benchmark(Baseline = false)] public void RecyclableList_Count() { var data = TestObjectsAsRecyclableList; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs index 6574f42..6c8e98b 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs @@ -1,32 +1,27 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_Create() { var list = new List(); DoNothing(list); } - //[Benchmark(Baseline = true)] public void PooledList_Create() { using var list = new PooledList(ClearMode.Auto); DoNothing(list); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Create() { using var list = new RecyclableArrayList(); DoNothing(list); } - //[Benchmark(Baseline = false)] public void RecyclableList_Create() { using var list = new RecyclableList(); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs index 7d43835..ba87e09 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs @@ -1,39 +1,33 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void Array_Create_WithCapacity() { var data = new long[TestObjectCount]; DoNothing(data); } - //[Benchmark(Baseline = false)] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); DoNothing(list); } - //[Benchmark(Baseline = true)] public void PooledList_Create_WithCapacity() { using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); DoNothing(list); } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Create_WithCapacity() { using var list = new RecyclableArrayList(TestObjectCount); DoNothing(list); } - //[Benchmark(Baseline = false)] public void RecyclableList_Create_WithCapacity() { using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index 374abbd..26c5828 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -1,10 +1,7 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - [Benchmark(Baseline = false)] public void Array_GetItem() { var data = TestObjects; @@ -15,7 +12,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void List_GetItem() { var data = TestObjectsAsList; @@ -26,7 +22,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] public void PooledList_GetItem() { var data = TestObjectsAsPooledList; @@ -37,7 +32,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableArrayList_GetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -48,7 +42,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableList_GetItem() { var data = TestObjectsAsRecyclableList; @@ -59,7 +52,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsSpan() { var data = TestObjectsAsRecyclableList; @@ -86,7 +78,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsArray() { var data = TestObjectsAsRecyclableList; @@ -114,7 +105,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsForEach() { var data = TestObjectsAsRecyclableList; @@ -124,7 +114,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableList_GetItem_AsWhile() { var data = TestObjectsAsRecyclableList; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs index 1df873c..2e9dd95 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs @@ -1,10 +1,7 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - [Benchmark(Baseline = false)] public void Array_IndexOf_AllItems() { var data = TestObjects; @@ -16,7 +13,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void List_IndexOf_AllItems() { var data = TestObjects; @@ -28,7 +24,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] public void PooledList_IndexOf_AllItems() { var data = TestObjects; @@ -40,7 +35,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableArrayList_IndexOf_AllItems() { var data = TestObjects; @@ -52,7 +46,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableList_IndexOf_AllItems() { var data = TestObjects; @@ -64,7 +57,6 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = false)] public void RecyclableList_LongIndexOf_AllItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs index a7bbe76..15e5443 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs @@ -1,10 +1,7 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void Array_IndexOf_FirstItems() { var data = TestObjects; @@ -16,7 +13,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void List_IndexOf_FirstItems() { var data = TestObjects; @@ -28,7 +24,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_IndexOf_FirstItems() { var data = TestObjects; @@ -40,7 +35,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_IndexOf_FirstItems() { var data = TestObjects; @@ -52,7 +46,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_IndexOf_FirstItems() { var data = TestObjects; @@ -64,7 +57,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_LongIndexOf_FirstItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs index 22910c4..e0661ad 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs @@ -1,10 +1,7 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void Array_IndexOf_LastItems() { var data = TestObjects; @@ -16,7 +13,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void List_IndexOf_LastItems() { var data = TestObjects; @@ -28,7 +24,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_IndexOf_LastItems() { var data = TestObjects; @@ -40,7 +35,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_IndexOf_LastItems() { var data = TestObjects; @@ -52,7 +46,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_IndexOf_LastItems() { var data = TestObjects; @@ -64,7 +57,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_LongIndexOf_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs index 426cf3e..2870f4d 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs @@ -1,17 +1,13 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void Array_LongCount() { var data = TestObjects; DoNothing(data.LongLength); } - //[Benchmark(Baseline = false)] public void RecyclableList_LongCount() { var data = TestObjectsAsRecyclableList; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index a927053..b0b78a5 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -1,40 +1,66 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using System.Reflection; +using BenchmarkDotNet.Attributes; namespace Recyclable.Collections.Benchmarks { - [MemoryDiagnoser] - public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + public enum RecyclableCollectionsBenchmarkType { - private long[]? _testArray; - private RecyclableArrayList? _testRecyclableArrayList; - private RecyclableList? _testRecyclableList; - private PooledList? _testPooledList; + Add_WithCapacity, Add, AddRange_WithCapacity, AddRangeWhenSourceIsArray, AddRangeWhenSourceIsIEnumerable, + AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, Contains_FirstItems, + Contains_LastItems, Count, Create_WithCapacity, Create, GetItem, IndexOf_AllItems, IndexOf_FirstItems, + IndexOf_LastItems, LongCount, Remove_FirstItems, Remove_LastItems, RemoveAt_FirstItems, RemoveAt_LastItems, + SetItem, + } - public PooledList TestObjectsAsPooledList => _testPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); + public partial class RecyclableCollectionsBenchmarks : RecyclableBenchmarkBase + { + [Params + ( + RecyclableCollectionsBenchmarkType.Add_WithCapacity, + RecyclableCollectionsBenchmarkType.Add, + RecyclableCollectionsBenchmarkType.AddRange_WithCapacity, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsArray, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIEnumerable, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIList, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsList, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsSameType, + RecyclableCollectionsBenchmarkType.Contains_FirstItems, + RecyclableCollectionsBenchmarkType.Contains_LastItems, + RecyclableCollectionsBenchmarkType.Count, + RecyclableCollectionsBenchmarkType.Create_WithCapacity, + RecyclableCollectionsBenchmarkType.Create, + RecyclableCollectionsBenchmarkType.GetItem, + RecyclableCollectionsBenchmarkType.IndexOf_AllItems, + RecyclableCollectionsBenchmarkType.IndexOf_FirstItems, + RecyclableCollectionsBenchmarkType.IndexOf_LastItems, + RecyclableCollectionsBenchmarkType.LongCount, + RecyclableCollectionsBenchmarkType.Remove_FirstItems, + RecyclableCollectionsBenchmarkType.Remove_LastItems, + RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems, + RecyclableCollectionsBenchmarkType.RemoveAt_LastItems, + RecyclableCollectionsBenchmarkType.SetItem + )] - public override void Setup() - { - base.Setup(); - _testRecyclableArrayList = new(TestObjects, initialCapacity: TestObjectCount); - _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testArray = TestObjects.ToArray(); - _testObjectsAsList = TestObjects.ToList(); - _testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList(); - _testObjectsAsRecyclableList = TestObjects.ToRecyclableList(); - _testPooledList = new PooledList(TestObjects, ClearMode.Always); - } + public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_AllItems; - public override void Cleanup() - { - _testRecyclableArrayList?.Dispose(); - _testRecyclableList?.Dispose(); - _testPooledList?.Dispose(); - TestObjectsAsRecyclableArrayList?.Dispose(); - _testObjectsAsRecyclableArrayList = default; - _testObjectsAsRecyclableList = default; - _testObjectsAsList = default; - base.Cleanup(); - } + [Params + ( + RecyclableCollectionsBenchmarkSource.Array, + RecyclableCollectionsBenchmarkSource.List, + RecyclableCollectionsBenchmarkSource.PooledList, + RecyclableCollectionsBenchmarkSource.RecyclableArrayList, + RecyclableCollectionsBenchmarkSource.RecyclableList + )] + public override RecyclableCollectionsBenchmarkSource BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + [Params + ( + RecyclableCollectionsBenchmarkSource.PooledList + )] + public override RecyclableCollectionsBenchmarkSource BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + protected override Action GetTestMethod(RecyclableCollectionsBenchmarkSource benchmarkType) + => (Action?) GetType().GetMethod($"{benchmarkType}_{TestCase}", BindingFlags.Instance | BindingFlags.Public)?.CreateDelegate(typeof(Action), this) + ?? throw CreateMethodNotFoundException($"{benchmarkType}_{TestCase}", GetType().FullName); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs index ddb0f98..5bb65cb 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_RemoveAt_FirstItems() { var data = TestObjects; @@ -17,7 +15,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_RemoveAt_FirstItems() { var data = TestObjects; @@ -29,7 +26,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_RemoveAt_FirstItems() { var data = TestObjects; @@ -41,7 +37,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_RemoveAt_FirstItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs index 9d84783..f4b808e 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_RemoveAt_LastItems() { var data = TestObjects; @@ -17,7 +15,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_RemoveAt_LastItems() { var data = TestObjects; @@ -29,7 +26,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_RemoveAt_LastItems() { var data = TestObjects; @@ -41,7 +37,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_RemoveAt_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs index c0e77b0..3d61a1c 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_Remove_FirstItems() { var data = TestObjects; @@ -17,7 +15,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_Remove_FirstItems() { var data = TestObjects; @@ -29,7 +26,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Remove_FirstItems() { var data = TestObjects; @@ -41,7 +37,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_Remove_FirstItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs index aeb7207..374a722 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs @@ -1,11 +1,9 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; +using Collections.Pooled; namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void List_Remove_LastItems() { var data = TestObjects; @@ -17,7 +15,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_Remove_LastItems() { var data = TestObjects; @@ -29,7 +26,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_Remove_LastItems() { var data = TestObjects; @@ -41,7 +37,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_Remove_LastItems() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs index ce4eaef..689a5e1 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs @@ -1,11 +1,7 @@ -using BenchmarkDotNet.Attributes; -using Collections.Pooled; - -namespace Recyclable.Collections.Benchmarks +namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - //[Benchmark(Baseline = false)] public void Array_SetItem() { var data = TestObjects; @@ -17,7 +13,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void List_SetItem() { var data = TestObjectsAsList; @@ -28,7 +23,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = true)] public void PooledList_SetItem() { var data = TestObjectsAsPooledList; @@ -39,7 +33,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableArrayList_SetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -50,7 +43,6 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark(Baseline = false)] public void RecyclableList_SetItem() { var data = TestObjectsAsRecyclableList; diff --git a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs index 222a4a9..960456e 100644 --- a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs @@ -3,20 +3,29 @@ using System.Buffers; namespace Recyclable.Collections.Benchmarks.POC { - public class ArraySizeLimitPocBenchmarks : PocBenchmarkBase + public enum ArraySizeLimitPocBenchmarkType + { + ArraySizeLimit + } + + public class ArraySizeLimitPocBenchmarks : PocBenchmarkBase { public static void Run() { var benchmark = new ArraySizeLimitPocBenchmarks(); + benchmark.Setup(); benchmark.FindArraySizeLimit(); + benchmark.Cleanup(); } - private const int _maxArraySize = 2_147_483_605; + [Params(2_147_483_605)] + public override int TestObjectCount { get => base.TestObjectCount; set => base.TestObjectCount = value; } [Benchmark(OperationsPerInvoke = 1)] public void Max_Allowed_Size_2_147_483_605() { - var arr = ArrayPool.Shared.Rent(_maxArraySize); + long[] arr = ArrayPool.Shared.Rent(TestObjectCount); + DoNothing(arr); } [Benchmark(OperationsPerInvoke = 1)] @@ -43,7 +52,7 @@ namespace Recyclable.Collections.Benchmarks.POC } catch (Exception) { - Console.WriteLine($"error"); + Console.WriteLine("error"); var nextValue = (bufferSize - oldValue) >> 1; if (nextValue == 0) { @@ -57,5 +66,5 @@ namespace Recyclable.Collections.Benchmarks.POC Console.WriteLine("All done"); throw new Exception($"Everything was successful. You can create {oldValue} byte big arrays. Exception is logged to prevent more benchmarks."); } - } + } } diff --git a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs index 51e002c..ea60943 100644 --- a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs @@ -1,50 +1,63 @@ using BenchmarkDotNet.Attributes; -using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { - [MemoryDiagnoser] - public class BoolOrComparePocBenchmarks + public enum BoolOrComparePocBenchmarkType { + Bool, + Compare, + } + + [MemoryDiagnoser] + public class BoolOrComparePocBenchmarks : PocBenchmarkBase + { + [Params(BoolOrComparePocBenchmarkType.Bool, BoolOrComparePocBenchmarkType.Compare)] + public override BoolOrComparePocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + [Params(8)] + public override int TestObjectCount { get => base.TestObjectCount; set => base.TestObjectCount = value; } + + [Params(true)] + public bool TestBool = true; + public static void Run() { var benchmark = new BoolOrComparePocBenchmarks(); + benchmark.Setup(); benchmark.Bool(); benchmark.Compare(); + benchmark.Cleanup(); } - private static void DoNothing(T result, [CallerMemberName] string? callerName = null) - { - //Console.WriteLine($"{callerName}: result = {result}"); - } - - private readonly bool _testBool = true; - private readonly int _blockSize = 8; - - [Benchmark(Baseline = false)] public void Bool() { - if (_testBool) + if (TestBool) { - DoNothing(_testBool); + DoNothing(TestBool); } else { - DoNothing(_testBool); + DoNothing(TestBool); } } - [Benchmark(Baseline = false)] public void Compare() { - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (TestObjectCount >= RecyclableDefaults.MinPooledArrayLength) { - DoNothing(_testBool); + DoNothing(TestBool); } else { - DoNothing(_testBool); + DoNothing(TestBool); } } - } + + protected override Action? GetTestMethod(BoolOrComparePocBenchmarkType benchmarkType) => benchmarkType switch + { + BoolOrComparePocBenchmarkType.Bool => Bool, + BoolOrComparePocBenchmarkType.Compare => Compare, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + } } \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs index 5b7e462..c926998 100644 --- a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs @@ -3,9 +3,46 @@ using MiscUtil; namespace Recyclable.Collections.Benchmarks.POC { + public enum DelegateVsComparerPocBenchmarkType + { + Comparer, + Delegate, + Func, + ListOfBoxedStructs, + ListOfClasses, + ListOfComparers, + ListOfDelegates, + ListOfFuncs, + ListOfLocalFuncs, + ListOfStructs, + ListOfUnboxedReadOnlyStructs, + ListOfUnboxedStructs, + StaticFunc, + } + [MemoryDiagnoser] - public class DelegateVsComparerPocBenchmarks : PocBenchmarkBase + public class DelegateVsComparerPocBenchmarks : PocBenchmarkBase { + [Params + ( + DelegateVsComparerPocBenchmarkType.Delegate, + DelegateVsComparerPocBenchmarkType.Func, + DelegateVsComparerPocBenchmarkType.ListOfBoxedStructs, + DelegateVsComparerPocBenchmarkType.ListOfClasses, + DelegateVsComparerPocBenchmarkType.ListOfComparers, + DelegateVsComparerPocBenchmarkType.ListOfDelegates, + DelegateVsComparerPocBenchmarkType.ListOfFuncs, + DelegateVsComparerPocBenchmarkType.ListOfLocalFuncs, + DelegateVsComparerPocBenchmarkType.ListOfStructs, + DelegateVsComparerPocBenchmarkType.ListOfUnboxedReadOnlyStructs, + DelegateVsComparerPocBenchmarkType.ListOfUnboxedStructs, + DelegateVsComparerPocBenchmarkType.StaticFunc + )] + public override DelegateVsComparerPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + [Params(DelegateVsComparerPocBenchmarkType.Comparer)] + public override DelegateVsComparerPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + #region Dummy classes private delegate T Compare(T x, T y); @@ -45,6 +82,7 @@ namespace Recyclable.Collections.Benchmarks.POC public static void Run() { var benchmark = new DelegateVsComparerPocBenchmarks(); + benchmark.Setup(); benchmark.Comparer(); benchmark.Delegate(); benchmark.Func(); @@ -58,22 +96,21 @@ namespace Recyclable.Collections.Benchmarks.POC benchmark.ListOfUnboxedReadOnlyStructs(); benchmark.ListOfUnboxedStructs(); benchmark.StaticFunc(); + benchmark.Cleanup(); } private static readonly Comparer _comparer = Comparer.Default; - - private const int _objectCount = 1_000_000; - private static readonly IEnumerable _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x); - private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static _ => new MyComparer().Compare); - private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(_ => (IComparer)new MyComparer()); - private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(_ => new Compare((x, y) => new MyComparer().Compare(x, y))); - private static readonly IEnumerable> _testListOfLocalFunctions = GetLocalFunctions(_testObjects); - private static readonly IEnumerable _testListOfClasses = _testObjects.Select(_ => new MyComparer()); - private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(_ => new MyComparerStruct()); - private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(_ => (IComparer)new MyComparerStruct()); - private static IEnumerable _testListOfUnboxedStructs() where TComparer : IComparer, new() + private IEnumerable? _testObjects; + private IEnumerable>? _testListOfFunctions; + private IEnumerable>? _testListOfComparers; + private IEnumerable>? _testListOfDelegates; + private IEnumerable>? _testListOfLocalFunctions; + private IEnumerable? _testListOfClasses; + private IEnumerable>? _testListOfStructs; + private IEnumerable>? _testListOfBoxedStructs; + private IEnumerable TestListOfUnboxedStructs() where TComparer : IComparer, new() { - foreach (var item in _testObjects) + foreach (var _ in _testObjects!) { yield return new TComparer(); } @@ -91,37 +128,44 @@ namespace Recyclable.Collections.Benchmarks.POC }; } - foreach (var item in testObjects) + foreach (var _ in testObjects) { yield return compareFunc; } } - private static void TestWithComparer(IComparer comparer) + private void TestWithComparer(IComparer comparer) { - var testData = _testObjects.ToArray(); + var testData = _testObjects!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) { _ = comparer.Compare(testData[itemIdx], testData[itemIdx + 1]); } } - [Benchmark(Baseline = true)] - public void Comparer() + private void TestWithDelegate(Compare compareFunc) { - TestWithComparer(_comparer); - } - - private static void TestWithFunc(Func compareFunc) - { - var testData = _testObjects.ToArray(); + var testData = _testObjects!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) { _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); } } - [Benchmark] + private void TestWithFunc(Func compareFunc) + { + var testData = _testObjects!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); + } + } + + public void Comparer() + { + TestWithComparer(_comparer); + } + public void Func() { TestWithFunc((x, y) => (x - y) switch @@ -132,7 +176,6 @@ namespace Recyclable.Collections.Benchmarks.POC }); } - [Benchmark] public void StaticFunc() { TestWithFunc(static (x, y) => (x - y) switch @@ -143,16 +186,6 @@ namespace Recyclable.Collections.Benchmarks.POC }); } - private static void TestWithDelegate(Compare compareFunc) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); - } - } - - [Benchmark] public void Delegate() { TestWithDelegate((x, y) => (x - y) switch @@ -163,11 +196,10 @@ namespace Recyclable.Collections.Benchmarks.POC }); } - [Benchmark] public void ListOfFuncs() { const long x = 2, y = 5; - var testData = _testListOfFunctions.ToArray(); + var testData = _testListOfFunctions!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var compareFunc = testData[itemIdx]; @@ -175,11 +207,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ListOfLocalFuncs() { const long x = 2, y = 5; - var testData = _testListOfLocalFunctions.ToArray(); + var testData = _testListOfLocalFunctions!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var compareFunc = testData[itemIdx]; @@ -187,11 +218,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ListOfComparers() { const long x = 2, y = 5; - var testData = _testListOfComparers.ToArray(); + var testData = _testListOfComparers!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var comparer = testData[itemIdx]; @@ -199,11 +229,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ListOfClasses() { const long x = 2, y = 5; - var testData = _testListOfClasses.ToArray(); + var testData = _testListOfClasses!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var comparer = testData[itemIdx]; @@ -211,11 +240,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ListOfStructs() { const long x = 2, y = 5; - var testData = _testListOfStructs.ToArray(); + var testData = _testListOfStructs!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var comparer = testData[itemIdx]; @@ -223,11 +251,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ListOfBoxedStructs() { const long x = 2, y = 5; - var testData = _testListOfBoxedStructs.ToArray(); + var testData = _testListOfBoxedStructs!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var comparer = testData[itemIdx]; @@ -235,11 +262,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ListOfUnboxedStructs() { const long x = 2, y = 5; - var testData = _testListOfUnboxedStructs, long>().ToArray(); + var testData = TestListOfUnboxedStructs, long>().ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var comparer = testData[itemIdx]; @@ -247,11 +273,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ListOfUnboxedReadOnlyStructs() { const long x = 2, y = 5; - var testData = _testListOfUnboxedStructs, long>().ToArray(); + var testData = TestListOfUnboxedStructs, long>().ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var comparer = testData[itemIdx]; @@ -259,16 +284,74 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ListOfDelegates() { const long x = 2, y = 5; - var testData = _testListOfDelegates.ToArray(); + var testData = _testListOfDelegates!.ToArray(); for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) { var compareDelegate = testData[itemIdx]; _ = compareDelegate(x, y); } } + + protected override Action? GetTestMethod(DelegateVsComparerPocBenchmarkType benchmarkType) => benchmarkType switch + { + DelegateVsComparerPocBenchmarkType.Comparer => Comparer, + DelegateVsComparerPocBenchmarkType.Delegate => Delegate, + DelegateVsComparerPocBenchmarkType.Func => Func, + DelegateVsComparerPocBenchmarkType.ListOfBoxedStructs => ListOfBoxedStructs, + DelegateVsComparerPocBenchmarkType.ListOfClasses => ListOfClasses, + DelegateVsComparerPocBenchmarkType.ListOfComparers => ListOfComparers, + DelegateVsComparerPocBenchmarkType.ListOfDelegates => ListOfDelegates, + DelegateVsComparerPocBenchmarkType.ListOfFuncs => ListOfFuncs, + DelegateVsComparerPocBenchmarkType.ListOfLocalFuncs => ListOfLocalFuncs, + DelegateVsComparerPocBenchmarkType.ListOfStructs => ListOfStructs, + DelegateVsComparerPocBenchmarkType.ListOfUnboxedReadOnlyStructs => ListOfUnboxedReadOnlyStructs, + DelegateVsComparerPocBenchmarkType.ListOfUnboxedStructs => ListOfUnboxedStructs, + DelegateVsComparerPocBenchmarkType.StaticFunc => StaticFunc, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + public override void Setup() + { + Console.WriteLine("******* SETTING UP COMMON TEST DATA *******"); + _testObjects = Enumerable.Range(1, TestObjectCount).Select(x => (long)x); + base.Setup(); + } + + public override void Cleanup() + { + _testObjects = null; + base.Cleanup(); + } + + protected override void PrepareData(T benchmarkType) + { + switch (benchmarkType) + { + case DelegateVsComparerPocBenchmarkType.ListOfBoxedStructs: + _testListOfBoxedStructs = _testObjects!.Select(_ => (IComparer)new MyComparerStruct()); + break; + case DelegateVsComparerPocBenchmarkType.ListOfClasses: + _testListOfClasses = _testObjects!.Select(_ => new MyComparer()); + break; + case DelegateVsComparerPocBenchmarkType.ListOfComparers: + _testListOfComparers = _testObjects!.Select(_ => (IComparer)new MyComparer()); + break; + case DelegateVsComparerPocBenchmarkType.ListOfDelegates: + _testListOfDelegates = _testObjects!.Select(_ => new Compare((x, y) => new MyComparer().Compare(x, y))); + break; + case DelegateVsComparerPocBenchmarkType.ListOfFuncs: + _testListOfFunctions = _testObjects!.Select>(static _ => new MyComparer().Compare); + break; + case DelegateVsComparerPocBenchmarkType.ListOfLocalFuncs: + _testListOfLocalFunctions = GetLocalFunctions(_testObjects!); + break; + case DelegateVsComparerPocBenchmarkType.ListOfStructs: + _testListOfStructs = _testObjects!.Select(_ => new MyComparerStruct()); + break; + } + } } } diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 254f92d..97d4c77 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -4,9 +4,32 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { - public class PocBenchmarkBase + public abstract class PocBenchmarkBase { - // 0 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 + public virtual TBenchmarkType? BenchmarkType { get; set; } + public virtual TBenchmarkType? BaselineBenchmarkType { get; set; } + + private Action? _testMethod; + protected Action TestMethod + { + get => _testMethod ?? throw new OperationCanceledException("Skip - Custom benchmark run in progress"); + set => _testMethod = value; + } + + private Action? _baselineMethod; + protected Action BaselineMethod + { + get => _baselineMethod ?? throw new OperationCanceledException("Skip - No baseline defined"); + set => _baselineMethod = value; + } + + [Benchmark] + public void RunWhen() => TestMethod.Invoke(); + + [Benchmark(Baseline = true)] + public void Baseline() => BaselineMethod.Invoke(); + + // 0 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524_288 // 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 // 1073741824 2147483648 @@ -17,7 +40,7 @@ namespace Recyclable.Collections.Benchmarks.POC //[Params(1_000_000, 10_000_000, 100_000_000, 1_000_000_000)] //[Params(1, 8, 16, 128, 192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000)] //[Params(RecyclableDefaults.MaxPooledBlockSize)] - public int TestObjectCount = 1_000_000; + public virtual int TestObjectCount { get; set; }= 524_288; //131_072; //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] //[Params(10, 20, 50, 100)] @@ -38,5 +61,51 @@ namespace Recyclable.Collections.Benchmarks.POC { _ = item; } + + protected static Exception CreateUnknownBenchmarkTypeException(T benchmarkType) + { + return new InvalidOperationException($"******* UNKNOWN BENCHMARK TYPE {{{benchmarkType}}} *******"); + } + + protected static Exception CreateMethodNotFoundException(in string methodName, in string? className) + { + return new MethodAccessException($"******* METHOD {{{methodName}}} NOT FOUND IN CLASS {{{className}}} *******"); + } + + protected virtual void PrepareData(T benchmarkType) { } + protected virtual Action? GetTestMethod(TBenchmarkType? benchmarkType) => null; + + [GlobalCleanup] + public virtual void Cleanup() + { + _baselineMethod = null; + _testMethod = null; + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + } + + [GlobalSetup] + public virtual void Setup() + { + Console.WriteLine($"******* SETTING UP TEST CASE FOR BENCHMARK {{{BenchmarkType}}} *******"); + + if (BaselineBenchmarkType != null) + { + Console.WriteLine($"******* SETTING UP BASELINE DATA *******"); + PrepareData(BaselineBenchmarkType); + _baselineMethod = GetTestMethod(BaselineBenchmarkType); + } + + //~ We don't want to prepare data if baseline data is the same is benchmark data. + //~ One would override another - waste of time & resources. + if (!EqualityComparer.Default.Equals(BaselineBenchmarkType, BenchmarkType)) + { + Console.WriteLine($"******* SETTING UP TEST CASE DATA *******"); + //~ If BaselineBenchmarkType == null, then we'll come here only if BaselineBenchmarkType != null. + PrepareData(BenchmarkType!); + _testMethod = GetTestMethod(BenchmarkType); + } + + Console.WriteLine("******* DATA PREPARED *******"); + } } } diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index 6a2b9da..8c320c8 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -1,5 +1,4 @@ -using BenchmarkDotNet.Attributes; -using Recyclable.Collections.Pools; +using Recyclable.Collections.Pools; using System.Buffers; using System.Collections; using System.Numerics; diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs index 3e34a5c..61cab10 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs @@ -1,5 +1,4 @@ -using BenchmarkDotNet.Attributes; -using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.ObjectPool; using Recyclable.Collections.Parallel; using Recyclable.Collections.Pools; using System.Buffers; @@ -4654,7 +4653,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } } - + #endregion public partial class RecyclableListPocBenchmarks diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs index 0387c74..581622a 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -5,7 +5,7 @@ using Recyclable.Collections.Benchmarks.Core; namespace Recyclable.Collections.Benchmarks.POC { - public enum RecyclableListPocBenchmarkType + public enum RecyclableListPocBenchmarkType { IndexOfV1, IndexOfV2, @@ -19,23 +19,24 @@ namespace Recyclable.Collections.Benchmarks.POC EnsureCapacityV3_ByPowOf2, } - public partial class RecyclableListPocBenchmarks : PocBenchmarkBase + [MemoryDiagnoser] + public partial class RecyclableListPocBenchmarks : PocBenchmarkBase { - [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.PooledList, BenchmarkType.RecyclableArrayList, BenchmarkType.RecyclableList)] - // [Params(RecyclableListPocBenchmarkType.IndexOf)] - public RecyclableListPocBenchmarkType _benchmarkType; - private Action? _testMethod; + [Params(RecyclableListPocBenchmarkType.IndexOf)] + public override RecyclableListPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } - [Benchmark] - public void RunWhen() => _testMethod!.Invoke(); + // [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.RecyclableArrayList, BenchmarkType.RecyclableList)] + [Params(RecyclableListPocBenchmarkType.IndexOf_PooledList)] + public override RecyclableListPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } private long[]? _testArray; public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); - private RecyclableList? _testRecyclableList; + public RecyclableList TestObjectsAsRecyclableList => _testRecyclableList ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableList)} is null"); private RecyclableListIndexOfV1? _testRecyclableListIndexOfV1; internal RecyclableListIndexOfV1 TestObjectsAsRecyclableListIndexOfV1 => _testRecyclableListIndexOfV1 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV1)} is null"); private RecyclableListIndexOfV2? _testRecyclableListIndexOfV2; + private RecyclableList? _testRecyclableList; internal RecyclableListIndexOfV2 TestObjectsAsRecyclableListIndexOfV2 => _testRecyclableListIndexOfV2 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV2)} is null"); private RecyclableListIndexOfV3? _testRecyclableListIndexOfV3; internal RecyclableListIndexOfV3 TestObjectsAsRecyclableListIndexOfV3 => _testRecyclableListIndexOfV3 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV3)} is null"); @@ -67,74 +68,32 @@ namespace Recyclable.Collections.Benchmarks.POC public static void Run() { var benchmark = new RecyclableListPocBenchmarks(); - benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); - benchmark.RecyclableList_EnsureCapacityV1_ByPowOf2(); - benchmark.RecyclableList_EnsureCapacityV2_ByPowOf2(); - benchmark.RecyclableList_EnsureCapacityV3_ByPowOf2(); - benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); - benchmark.RecyclableList_EnsureCapacityV1_ByBlockSize(); - benchmark.RecyclableList_EnsureCapacityV2_ByBlockSize(); - benchmark.RecyclableList_EnsureCapacityV3_ByBlockSize(); + benchmark.Setup(); + // benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); + // benchmark.RecyclableList_EnsureCapacityV1_ByPowOf2(); + // benchmark.RecyclableList_EnsureCapacityV2_ByPowOf2(); + // benchmark.RecyclableList_EnsureCapacityV3_ByPowOf2(); + // benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); + // benchmark.RecyclableList_EnsureCapacityV1_ByBlockSize(); + // benchmark.RecyclableList_EnsureCapacityV2_ByBlockSize(); + // benchmark.RecyclableList_EnsureCapacityV3_ByBlockSize(); benchmark.RecyclableList_IndexOf(); benchmark.RecyclableList_IndexOfV1(); benchmark.RecyclableList_IndexOfV2(); benchmark.RecyclableList_IndexOfV3(); benchmark.RecyclableList_IndexOfV4(); + benchmark.Cleanup(); } - [GlobalSetup] - public void Setup() + public override void Setup() { - Console.WriteLine($"Setting up exptected results data"); + Console.WriteLine("******* SETTING UP EXPECTED RESULTS DATA *******"); _testArray = DataGenerator.EnumerateTestObjects(TestObjectCount); - Console.WriteLine($"Setting up test case for benchmark {{{_benchmarkType}}}"); - switch (_benchmarkType) - { - case RecyclableListPocBenchmarkType.IndexOfV1: - _testRecyclableListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testMethod = () => RecyclableList_IndexOfV1(); - break; - case RecyclableListPocBenchmarkType.IndexOfV2: - _testRecyclableListIndexOfV2 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testMethod = () => RecyclableList_IndexOfV2(); - break; - case RecyclableListPocBenchmarkType.IndexOfV3: - _testRecyclableListIndexOfV3 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testMethod = () => RecyclableList_IndexOfV3(); - break; - case RecyclableListPocBenchmarkType.IndexOfV4: - _testRecyclableListIndexOfV4 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testMethod = () => RecyclableList_IndexOfV4(); - break; - case RecyclableListPocBenchmarkType.IndexOf_PooledList: - _testPooledList = new PooledList(TestObjects, ClearMode.Auto); - _testMethod = () => PooledList_IndexOf(); - break; - case RecyclableListPocBenchmarkType.IndexOf: - _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - _testMethod = () => RecyclableList_IndexOf(); - break; - case RecyclableListPocBenchmarkType.EnsureCapacity_ByPowOf2: - _testMethod = () => RecyclableList_EnsureCapacity_ByPowOf2(); - break; - case RecyclableListPocBenchmarkType.EnsureCapacityV1_ByPowOf2: - _testMethod = () => RecyclableList_EnsureCapacityV1_ByPowOf2(); - break; - case RecyclableListPocBenchmarkType.EnsureCapacityV2_ByPowOf2: - _testMethod = () => RecyclableList_EnsureCapacityV2_ByPowOf2(); - break; - case RecyclableListPocBenchmarkType.EnsureCapacityV3_ByPowOf2: - _testMethod = () => RecyclableList_EnsureCapacityV3_ByPowOf2(); - break; - default: - throw new InvalidOperationException($"Unknown benchmark type {{{_benchmarkType}}}"); - } - Console.WriteLine("Data prepared"); - } + base.Setup(); + } - [GlobalCleanup] - public void Cleanup() + public override void Cleanup() { _testArray = null; _testRecyclableList?.Dispose(); @@ -149,7 +108,50 @@ namespace Recyclable.Collections.Benchmarks.POC _testRecyclableListIndexOfV4 = null; _testPooledList?.Dispose(); _testPooledList = null; - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + + base.Cleanup(); } - } + + protected override Action? GetTestMethod(RecyclableListPocBenchmarkType benchmarkType) => benchmarkType switch + { + RecyclableListPocBenchmarkType.IndexOfV1 => () => RecyclableList_IndexOfV1(), + RecyclableListPocBenchmarkType.IndexOfV2 => () => RecyclableList_IndexOfV2(), + RecyclableListPocBenchmarkType.IndexOfV3 => () => RecyclableList_IndexOfV3(), + RecyclableListPocBenchmarkType.IndexOfV4 => () => RecyclableList_IndexOfV4(), + RecyclableListPocBenchmarkType.IndexOf_PooledList => () => PooledList_IndexOf(), + RecyclableListPocBenchmarkType.IndexOf => () => RecyclableList_IndexOf(), + RecyclableListPocBenchmarkType.EnsureCapacity_ByPowOf2 => () => RecyclableList_EnsureCapacity_ByPowOf2(), + RecyclableListPocBenchmarkType.EnsureCapacityV1_ByPowOf2 => () => RecyclableList_EnsureCapacityV1_ByPowOf2(), + RecyclableListPocBenchmarkType.EnsureCapacityV2_ByPowOf2 => () => RecyclableList_EnsureCapacityV2_ByPowOf2(), + RecyclableListPocBenchmarkType.EnsureCapacityV3_ByPowOf2 => () => RecyclableList_EnsureCapacityV3_ByPowOf2(), + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + protected override void PrepareData(T benchmarkType) + { + base.PrepareData(benchmarkType); + + switch (BenchmarkType) + { + case RecyclableListPocBenchmarkType.IndexOfV1: + _testRecyclableListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableListPocBenchmarkType.IndexOfV2: + _testRecyclableListIndexOfV2 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableListPocBenchmarkType.IndexOfV3: + _testRecyclableListIndexOfV3 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableListPocBenchmarkType.IndexOfV4: + _testRecyclableListIndexOfV4 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableListPocBenchmarkType.IndexOf_PooledList: + _testPooledList = new PooledList(TestObjects, ClearMode.Auto); + break; + case RecyclableListPocBenchmarkType.IndexOf: + _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + } + } + } } diff --git a/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs index 48e1bcf..d3b8223 100644 --- a/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs @@ -2,26 +2,44 @@ namespace Recyclable.Collections.Benchmarks.POC { - public class RoundBenchmarks : PocBenchmarkBase + public enum RoundBenchmarkType { + MathFloor, + MathRound, + WithTypecasting, + } + + public class RoundBenchmarks : PocBenchmarkBase + { + [Params(RoundBenchmarkType.MathFloor)] + public override RoundBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + [Params(RoundBenchmarkType.MathRound, RoundBenchmarkType.WithTypecasting)] + public override RoundBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + private const double OptimalParallelSearchStep = 0.329; - [Benchmark(Baseline = true)] public void RoundWithMathFloor() { DoNothing((long)Math.Floor(TestObjectCount * OptimalParallelSearchStep)); } - [Benchmark] public void RoundWithMathRound() { DoNothing((long)Math.Round(TestObjectCount * OptimalParallelSearchStep, 0)); } - [Benchmark(Baseline = false)] public void RoundWithTypeCasting() { DoNothing((long)(TestObjectCount * OptimalParallelSearchStep)); } + + protected override Action GetTestMethod(RoundBenchmarkType benchmarkType) => benchmarkType switch + { + RoundBenchmarkType.MathFloor => RoundWithMathFloor, + RoundBenchmarkType.MathRound => RoundWithMathRound, + RoundBenchmarkType.WithTypecasting => RoundWithTypeCasting, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; } } diff --git a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs index d4dd096..b270c14 100644 --- a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs @@ -2,10 +2,43 @@ namespace Recyclable.Collections.Benchmarks.POC { - public class SpanVsArrayBenchmarks : BenchmarkBase + public enum SpanVsArrayBenchmarkType { - //[Benchmark] - public void ArrayGetWithVar() + ArrayGetWithVar, + ArrayGetWithField, + SpanGet, + ArraySetWithVar, + ArraySetWithField, + SpanSet + } + + public class SpanVsArrayBenchmarks : RecyclableBenchmarkBase + { + // [Params(SpanVsArrayBenchmarkType.ArrayGetWithVar, SpanVsArrayBenchmarkType.SpanGet)] + // [Params(SpanVsArrayBenchmarkType.ArraySetWithVar, SpanVsArrayBenchmarkType.SpanSet)] + [Params + ( + SpanVsArrayBenchmarkType.ArrayGetWithVar, SpanVsArrayBenchmarkType.SpanGet, + SpanVsArrayBenchmarkType.ArraySetWithField, SpanVsArrayBenchmarkType.ArraySetWithVar, SpanVsArrayBenchmarkType.SpanSet + )] + public override SpanVsArrayBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + [Params(SpanVsArrayBenchmarkType.ArrayGetWithField)] + // [Params(SpanVsArrayBenchmarkType.ArraySetWithField)] + public override SpanVsArrayBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + protected override Action GetTestMethod(SpanVsArrayBenchmarkType benchmarkType) => benchmarkType switch + { + SpanVsArrayBenchmarkType.ArrayGetWithVar => ArrayGetWithVar, + SpanVsArrayBenchmarkType.ArrayGetWithField => ArrayGetWithField, + SpanVsArrayBenchmarkType.SpanGet => SpanGet, + SpanVsArrayBenchmarkType.ArraySetWithVar => ArraySetWithVar, + SpanVsArrayBenchmarkType.ArraySetWithField => ArraySetWithField, + SpanVsArrayBenchmarkType.SpanSet => SpanSet, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + public void ArrayGetWithVar() { var data = TestObjects; var dataCount = TestObjectCount; @@ -15,7 +48,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - //[Benchmark(Baseline = true)] public void ArrayGetWithField() { var dataCount = TestObjectCount; @@ -25,7 +57,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - //[Benchmark] public void SpanGet() { var dataCount = TestObjectCount; @@ -36,7 +67,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void ArraySetWithVar() { var data = TestObjects; @@ -47,7 +77,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark(Baseline = true)] public void ArraySetWithField() { var dataCount = TestObjectCount; @@ -57,7 +86,6 @@ namespace Recyclable.Collections.Benchmarks.POC } } - [Benchmark] public void SpanSet() { var dataCount = TestObjectCount; diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs index 1a33945..bad4c11 100644 --- a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -1,19 +1,18 @@ using BenchmarkDotNet.Attributes; using MoreLinq; using System.Runtime.CompilerServices; -using System.Threading; namespace Recyclable.Collections.Benchmarks.POC { - [MemoryDiagnoser] - public class WhenParallelSearchBenchmarks : PocBenchmarkBase + public enum WhenParallelSearchBenchmarkType { - [MethodImpl(MethodImplOptions.NoInlining)] - private static void DoNothing(T index) - { - //Console.WriteLine(index); - } + IndexOfSequentially, + IndexOfParallel, + } + [MemoryDiagnoser] + public class WhenParallelSearchBenchmarks : PocBenchmarkBase + { //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32)] //public int NumberOfThreads { get; set; } = 3; @@ -26,32 +25,11 @@ namespace Recyclable.Collections.Benchmarks.POC public object? ItemToFind { get; set; } - [GlobalSetup] - public void Setup() - { - _testData = Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableList(BlockSize); - ItemToFind = _testData.Last(); - } + [Params(WhenParallelSearchBenchmarkType.IndexOfParallel)] + public override WhenParallelSearchBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } - [GlobalCleanup] - public void Cleanup() - { - _testData = default; - } - - [Benchmark(Baseline = true)] - public void IndexOfSequentially() - { - long index = DoLongIndexOfSequentially(); - if (index >= 0) - { - DoNothing(index); - } - else - { - throw new Exception("Item not found"); - } - } + [Params(WhenParallelSearchBenchmarkType.IndexOfSequentially)] + public override WhenParallelSearchBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } private long DoLongIndexOfSequentially() { @@ -95,7 +73,7 @@ namespace Recyclable.Collections.Benchmarks.POC int blockIndex = searchInfo.BlockIndex; long itemsToSearchCount = searchInfo.ItemsToSearchCount; long[][] memoryBlocks = list.AsArray; - index = Array.IndexOf(memoryBlocks[searchInfo.BlockIndex], itemToFind, searchInfo.StartingItemIndex, blockSize - searchInfo.StartingItemIndex); + index = Array.IndexOf(memoryBlocks[searchInfo.BlockIndex], itemToFind, 0, blockSize); if (itemFoundSignal.IsSet) { return -1; @@ -105,7 +83,7 @@ namespace Recyclable.Collections.Benchmarks.POC return (blockIndex << list.BlockSizePow2BitShift) + index; } - itemsToSearchCount -= blockSize - searchInfo.StartingItemIndex; + itemsToSearchCount -= blockSize; if (itemsToSearchCount < 0) { return -1; @@ -145,20 +123,6 @@ namespace Recyclable.Collections.Benchmarks.POC return itemFoundSignal.IsSet || index < 0 ? -1 : ((long)lastBlockWithData << list.BlockSizePow2BitShift) + index; } - [Benchmark] - public void IndexOfParallel() - { - var index = DoIndexOfParallel(); - if (index >= 0) - { - DoNothing(index); - } - else - { - throw new Exception("Item not found"); - } - } - private long DoIndexOfParallel() { using var itemFoundSignal = new ManualResetEventSlim(false); @@ -271,7 +235,55 @@ namespace Recyclable.Collections.Benchmarks.POC //} allDoneSignal.SignalAndWait(); + throw new NotImplementedException(); return foundItemIndex; } + + protected override void PrepareData(T benchmarkType) + { + base.PrepareData(benchmarkType); + + _testData ??= Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableList(BlockSize); + ItemToFind ??= _testData.Last(); + } + + protected override Action? GetTestMethod(WhenParallelSearchBenchmarkType benchmarkType) => BenchmarkType switch + { + WhenParallelSearchBenchmarkType.IndexOfSequentially => IndexOfSequentially, + WhenParallelSearchBenchmarkType.IndexOfParallel => IndexOfParallel, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + public override void Cleanup() + { + _testData = default; + base.Cleanup(); + } + + public void IndexOfSequentially() + { + long index = DoLongIndexOfSequentially(); + if (index >= 0) + { + DoNothing(index); + } + else + { + throw new Exception("Item not found"); + } + } + + public void IndexOfParallel() + { + var index = DoIndexOfParallel(); + if (index >= 0) + { + DoNothing(index); + } + else + { + throw new Exception("Item not found"); + } + } } } diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs similarity index 75% rename from Recyclable.Collections.Benchmarks/BenchmarkBase.cs rename to Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs index 41925f4..28a163f 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs @@ -1,10 +1,21 @@ using BenchmarkDotNet.Attributes; +using Collections.Pooled; using Recyclable.Collections.Benchmarks.Core; using Recyclable.Collections.Benchmarks.POC; namespace Recyclable.Collections.Benchmarks { - public class BenchmarkBase : PocBenchmarkBase + public enum RecyclableCollectionsBenchmarkSource + { + Array, + List, + PooledList, + RecyclableArrayList, + RecyclableList, + } + + [MemoryDiagnoser] + public class RecyclableBenchmarkBase : PocBenchmarkBase { protected long[]? _testObjects; protected long[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); @@ -12,6 +23,8 @@ namespace Recyclable.Collections.Benchmarks protected List? _testObjectsAsList; protected List TestObjectsAsList => _testObjectsAsList ?? throw new NullReferenceException("Something went wront and the field is not initialized"); + protected PooledList? _testObjectsAsPooledList; + public PooledList TestObjectsAsPooledList => _testObjectsAsPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); protected RecyclableArrayList? _testObjectsAsRecyclableArrayList; protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); protected RecyclableList? _testObjectsAsRecyclableList; @@ -27,16 +40,45 @@ namespace Recyclable.Collections.Benchmarks } } - [GlobalSetup] - public virtual void Setup() + public override void Setup() { - Console.WriteLine("******* GLOBAL SETUP *******"); - _testObjects = DataGenerator.EnumerateTestObjects(TestObjectCount); - Console.WriteLine("******* TEST DATA CREATED *******"); + PrepareData(RecyclableCollectionsBenchmarkSource.Array); + base.Setup(); } - [GlobalCleanup] - public virtual void Cleanup() + protected override void PrepareData(T benchmarkType) + { + Console.WriteLine($"******* PREPARING DATA FOR {benchmarkType} *******"); + switch (benchmarkType) + { + case RecyclableCollectionsBenchmarkSource.Array: + _testObjects ??= DataGenerator.EnumerateTestObjects(TestObjectCount); + break; + + case RecyclableCollectionsBenchmarkSource.List: + _testObjectsAsList ??= TestObjects.ToList(); + break; + + case RecyclableCollectionsBenchmarkSource.PooledList: + _testObjectsAsPooledList ??= new PooledList(TestObjects, ClearMode.Auto); + break; + + case RecyclableCollectionsBenchmarkSource.RecyclableArrayList: + _testObjectsAsRecyclableArrayList ??= new(TestObjects, initialCapacity: TestObjectCount); + break; + + case RecyclableCollectionsBenchmarkSource.RecyclableList: + _testObjectsAsRecyclableList ??= new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + + default: + throw CreateUnknownBenchmarkTypeException(benchmarkType); + } + + base.PrepareData(benchmarkType); + } + + public override void Cleanup() { Console.WriteLine("******* GLOBAL CLEANUP *******"); // Enable this if item type is changed to reference type @@ -46,7 +88,14 @@ namespace Recyclable.Collections.Benchmarks //} _testObjects = default; - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + _testObjectsAsList = default; + _testObjectsAsPooledList?.Dispose(); + _testObjectsAsPooledList = default; + _testObjectsAsRecyclableArrayList?.Dispose(); + _testObjectsAsRecyclableArrayList = default; + _testObjectsAsRecyclableList?.Dispose(); + _testObjectsAsRecyclableList = default; + base.Cleanup(); } } From 53d759586e2b4328df7019e487546b634cedebe3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 13 May 2023 20:35:57 +0200 Subject: [PATCH 254/305] Small cleanup --- .../Benchmarks/Create.cs | 8 +- .../POC/ArraySizeLimitPoc.Benchmarks.cs | 10 +-- .../POC/PocBenchmarkBase.cs | 32 ++++---- Recyclable.Collections.Benchmarks/Program.cs | 4 +- Recyclable.Collections/ItemRange.cs | 2 +- .../ParallelSynchronizationContextPool.cs | 76 +++++++++---------- 6 files changed, 66 insertions(+), 66 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs index 6c8e98b..710da7d 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs @@ -4,25 +4,25 @@ namespace Recyclable.Collections.Benchmarks { public partial class RecyclableCollectionsBenchmarks { - public void List_Create() + public static void List_Create() { var list = new List(); DoNothing(list); } - public void PooledList_Create() + public static void PooledList_Create() { using var list = new PooledList(ClearMode.Auto); DoNothing(list); } - public void RecyclableArrayList_Create() + public static void RecyclableArrayList_Create() { using var list = new RecyclableArrayList(); DoNothing(list); } - public void RecyclableList_Create() + public static void RecyclableList_Create() { using var list = new RecyclableList(); DoNothing(list); diff --git a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs index 960456e..e46231a 100644 --- a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs @@ -14,7 +14,7 @@ namespace Recyclable.Collections.Benchmarks.POC { var benchmark = new ArraySizeLimitPocBenchmarks(); benchmark.Setup(); - benchmark.FindArraySizeLimit(); + FindArraySizeLimit(); benchmark.Cleanup(); } @@ -24,12 +24,12 @@ namespace Recyclable.Collections.Benchmarks.POC [Benchmark(OperationsPerInvoke = 1)] public void Max_Allowed_Size_2_147_483_605() { - long[] arr = ArrayPool.Shared.Rent(TestObjectCount); - DoNothing(arr); + long[] arr = ArrayPool.Shared.Rent(TestObjectCount); + DoNothing(arr); } [Benchmark(OperationsPerInvoke = 1)] - public void FindArraySizeLimit() + public static void FindArraySizeLimit() { var pool = ArrayPool.Shared; var bufferSize = int.MaxValue / 2; @@ -66,5 +66,5 @@ namespace Recyclable.Collections.Benchmarks.POC Console.WriteLine("All done"); throw new Exception($"Everything was successful. You can create {oldValue} byte big arrays. Exception is logged to prevent more benchmarks."); } - } + } } diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 97d4c77..978be86 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -4,31 +4,31 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { - public abstract class PocBenchmarkBase + public abstract class PocBenchmarkBase { public virtual TBenchmarkType? BenchmarkType { get; set; } public virtual TBenchmarkType? BaselineBenchmarkType { get; set; } private Action? _testMethod; - protected Action TestMethod - { - get => _testMethod ?? throw new OperationCanceledException("Skip - Custom benchmark run in progress"); - set => _testMethod = value; - } + protected Action TestMethod + { + get => _testMethod ?? throw new OperationCanceledException("Skip - Custom benchmark run in progress"); + set => _testMethod = value; + } private Action? _baselineMethod; - protected Action BaselineMethod - { - get => _baselineMethod ?? throw new OperationCanceledException("Skip - No baseline defined"); - set => _baselineMethod = value; - } - - [Benchmark] - public void RunWhen() => TestMethod.Invoke(); + protected Action BaselineMethod + { + get => _baselineMethod ?? throw new OperationCanceledException("Skip - No baseline defined"); + set => _baselineMethod = value; + } [Benchmark(Baseline = true)] public void Baseline() => BaselineMethod.Invoke(); + [Benchmark] + public void Actual() => TestMethod.Invoke(); + // 0 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524_288 // 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 // 1073741824 2147483648 @@ -64,12 +64,12 @@ namespace Recyclable.Collections.Benchmarks.POC protected static Exception CreateUnknownBenchmarkTypeException(T benchmarkType) { - return new InvalidOperationException($"******* UNKNOWN BENCHMARK TYPE {{{benchmarkType}}} *******"); + return new InvalidOperationException($"******* UNKNOWN BENCHMARK TYPE {{{benchmarkType}}} *******"); } protected static Exception CreateMethodNotFoundException(in string methodName, in string? className) { - return new MethodAccessException($"******* METHOD {{{methodName}}} NOT FOUND IN CLASS {{{className}}} *******"); + return new MethodAccessException($"******* METHOD {{{methodName}}} NOT FOUND IN CLASS {{{className}}} *******"); } protected virtual void PrepareData(T benchmarkType) { } diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 43fc561..a835447 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -51,8 +51,8 @@ internal class Program private static void Main(string[] args) { RunRecyclableCollectionsBenchmarks(); - //RunPocBenchmarks(); - //RunSelectedBenchmarks(); + // RunPocBenchmarks(); + // RunSelectedBenchmarks(); //RunAssemblyBenchmarks(); } } diff --git a/Recyclable.Collections/ItemRange.cs b/Recyclable.Collections/ItemRange.cs index 4a1d273..892aa43 100644 --- a/Recyclable.Collections/ItemRange.cs +++ b/Recyclable.Collections/ItemRange.cs @@ -2,7 +2,7 @@ using Recyclable.Collections.Pools; namespace Recyclable.Collections { - public sealed class ItemRange + public sealed class ItemRange { public int BlockIndex; public int StartingItemIndex; diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs index 88fa317..d4245bf 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs @@ -2,49 +2,49 @@ using Microsoft.Extensions.ObjectPool; namespace Recyclable.Collections.Parallel { - public static class ParallelSynchronizationContextPool - { - private static readonly SpinLock _updateLock = new(false); - public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + public static class ParallelSynchronizationContextPool + { + private static readonly SpinLock _updateLock = new(false); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - public static ParallelSynchronizationContext GetWithOneParticipant() - { - bool lockTaken = false; - _updateLock.Enter(ref lockTaken); - var context = Shared.Get(); - if (lockTaken) - { - _updateLock.Exit(false); - } + public static ParallelSynchronizationContext GetWithOneParticipant() + { + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + var context = Shared.Get(); + if (lockTaken) + { + _updateLock.Exit(false); + } - if (context.ItemFoundSignal._isSet) - { - context.ItemFoundSignal.Reset(); - } + if (context.ItemFoundSignal._isSet) + { + context.ItemFoundSignal.Reset(); + } - switch (context.AllDoneSignal.ParticipantsRemaining) - { - case > 1: - context.AllDoneSignal.RemoveParticipants(context.AllDoneSignal.ParticipantsRemaining - 1); - break; + switch (context.AllDoneSignal.ParticipantsRemaining) + { + case > 1: + context.AllDoneSignal.RemoveParticipants(context.AllDoneSignal.ParticipantsRemaining - 1); + break; - case 0: - context.AllDoneSignal.AddParticipant(); - break; - } + case 0: + _ = context.AllDoneSignal.AddParticipant(); + break; + } - if (context.Exception != null) - { - context.Exception = null; - } + if (context.Exception != null) + { + context.Exception = null; + } - if (context.FoundItemIndex >= 0) - { - context.FoundItemIndex = -1L; - } + if (context.FoundItemIndex >= 0) + { + context.FoundItemIndex = -1L; + } - context._returned = false; - return context; - } - } + context._returned = false; + return context; + } + } } \ No newline at end of file From d522535da5f0f054eefcd27cb19d58de8e87f6f8 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 13 May 2023 20:39:16 +0200 Subject: [PATCH 255/305] RecyclableCollections.IndexOf benchmarks for all items reworked to search from the end, so that we measure the worst cases first. --- .../Benchmarks/IndexOf_AllItems.cs | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs index 2e9dd95..626e743 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs @@ -2,14 +2,16 @@ { public partial class RecyclableCollectionsBenchmarks { + private const long MaxItemsToSearchFor = RecyclableDefaults.MinItemsCountForParallelization * 3; + public void Array_IndexOf_AllItems() { var data = TestObjects; var list = TestObjects; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 1; i <= dataCount; i++) { - DoNothing(Array.IndexOf(list, data[i])); + DoNothing(Array.IndexOf(list, data[^i])); } } @@ -17,10 +19,10 @@ { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 1; i <= dataCount; i++) { - DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^i])); } } @@ -28,10 +30,10 @@ { var data = TestObjects; var list = TestObjectsAsPooledList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 1; i <= dataCount; i++) { - DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^i])); } } @@ -39,10 +41,10 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableArrayList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 1; i <= dataCount; i++) { - DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^i])); } } @@ -50,10 +52,10 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 1; i <= dataCount; i++) { - DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^i])); } } @@ -61,10 +63,10 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 1; i <= dataCount; i++) { - DoNothing(list.LongIndexOf(data[i])); + DoNothing(list.LongIndexOf(data[^i])); } } } From 735503d66440bf25b266668623996803884ad5c0 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 13 May 2023 20:43:24 +0200 Subject: [PATCH 256/305] Small RecyclableCollections.IndexOf benchmarks simplification --- .../Benchmarks/IndexOf_FirstItems.cs | 12 ++++++------ .../Benchmarks/IndexOf_LastItems.cs | 12 ++++++------ .../POC/PocBenchmarkBase.cs | 1 + 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs index 15e5443..df4ca9e 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs @@ -6,7 +6,7 @@ { var data = TestObjects; var list = TestObjects; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(Array.IndexOf(list, data[i])); @@ -17,7 +17,7 @@ { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -28,7 +28,7 @@ { var data = TestObjects; var list = TestObjectsAsPooledList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -39,7 +39,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableArrayList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -50,7 +50,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -61,7 +61,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.LongIndexOf(data[i])); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs index e0661ad..ab77a17 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs @@ -6,7 +6,7 @@ { var data = TestObjects; var list = TestObjects; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(Array.IndexOf(list, data[^(i + 1)])); @@ -17,7 +17,7 @@ { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[^(i + 1)])); @@ -28,7 +28,7 @@ { var data = TestObjects; var list = TestObjectsAsPooledList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[^(i + 1)])); @@ -39,7 +39,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableArrayList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[^(i + 1)])); @@ -50,7 +50,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[^(i + 1)])); @@ -61,7 +61,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.LongIndexOf(data[^(i + 1)])); diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 978be86..2446dab 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -41,6 +41,7 @@ namespace Recyclable.Collections.Benchmarks.POC //[Params(1, 8, 16, 128, 192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000)] //[Params(RecyclableDefaults.MaxPooledBlockSize)] public virtual int TestObjectCount { get; set; }= 524_288; //131_072; + public virtual int TestObjectCountForSlowMethods => Math.Min(TestObjectCount, 100); //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] //[Params(10, 20, 50, 100)] From 1056e9b57554f5a49767278e792f47aaf3900ac3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 13 May 2023 20:51:29 +0200 Subject: [PATCH 257/305] Default constructor added to RecyclableCollections benchmarks to allow lack of initialization when selected benchmarks are executed. --- .../Benchmarks/RecyclableCollections.Benchmarks.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index b0b78a5..82fad90 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -14,7 +14,14 @@ namespace Recyclable.Collections.Benchmarks public partial class RecyclableCollectionsBenchmarks : RecyclableBenchmarkBase { - [Params + public RecyclableCollectionsBenchmarks() : base() + { + TestObjectCount = 1; + BaselineBenchmarkType = RecyclableCollectionsBenchmarkSource.PooledList; + BenchmarkType = RecyclableCollectionsBenchmarkSource.RecyclableList; + } + + [Params ( RecyclableCollectionsBenchmarkType.Add_WithCapacity, RecyclableCollectionsBenchmarkType.Add, From dd5becfc29bc85e683b0249d8d9b1d68b66a99af Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 19 May 2023 01:03:04 +0200 Subject: [PATCH 258/305] Recyclable.Collections.Benchmarks.IndexOf_AllItems renamed to .IndexOf_BestAndWorstCases --- .../Benchmarks/IndexOf_AllItems.cs | 73 ----------------- .../Benchmarks/IndexOf_BestAndWorstCases.cs | 79 +++++++++++++++++++ .../RecyclableCollections.Benchmarks.cs | 4 +- 3 files changed, 81 insertions(+), 75 deletions(-) delete mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs deleted file mode 100644 index 626e743..0000000 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_AllItems.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace Recyclable.Collections.Benchmarks -{ - public partial class RecyclableCollectionsBenchmarks - { - private const long MaxItemsToSearchFor = RecyclableDefaults.MinItemsCountForParallelization * 3; - - public void Array_IndexOf_AllItems() - { - var data = TestObjects; - var list = TestObjects; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); - for (var i = 1; i <= dataCount; i++) - { - DoNothing(Array.IndexOf(list, data[^i])); - } - } - - public void List_IndexOf_AllItems() - { - var data = TestObjects; - var list = TestObjectsAsList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); - for (var i = 1; i <= dataCount; i++) - { - DoNothing(list.IndexOf(data[^i])); - } - } - - public void PooledList_IndexOf_AllItems() - { - var data = TestObjects; - var list = TestObjectsAsPooledList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); - for (var i = 1; i <= dataCount; i++) - { - DoNothing(list.IndexOf(data[^i])); - } - } - - public void RecyclableArrayList_IndexOf_AllItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableArrayList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); - for (var i = 1; i <= dataCount; i++) - { - DoNothing(list.IndexOf(data[^i])); - } - } - - public void RecyclableList_IndexOf_AllItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); - for (var i = 1; i <= dataCount; i++) - { - DoNothing(list.IndexOf(data[^i])); - } - } - - public void RecyclableList_LongIndexOf_AllItems() - { - var data = TestObjects; - var list = TestObjectsAsRecyclableList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); - for (var i = 1; i <= dataCount; i++) - { - DoNothing(list.LongIndexOf(data[^i])); - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs new file mode 100644 index 0000000..30e2606 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs @@ -0,0 +1,79 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + private const long MaxItemsToSearchFor = 100_000;// RecyclableDefaults.MinItemsCountForParallelization * 3; + + public void Array_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[i])); + DoNothing(Array.IndexOf(list, data[^(i + 1)])); + } + } + + public void List_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void PooledList_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableArrayList_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableArrayList; + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableList_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableList_LongIndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.LongIndexOf(data[^(i + 1)])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 82fad90..bae12f8 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -7,7 +7,7 @@ namespace Recyclable.Collections.Benchmarks { Add_WithCapacity, Add, AddRange_WithCapacity, AddRangeWhenSourceIsArray, AddRangeWhenSourceIsIEnumerable, AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, Contains_FirstItems, - Contains_LastItems, Count, Create_WithCapacity, Create, GetItem, IndexOf_AllItems, IndexOf_FirstItems, + Contains_LastItems, Count, Create_WithCapacity, Create, GetItem, IndexOf_BestAndWorstCases, IndexOf_FirstItems, IndexOf_LastItems, LongCount, Remove_FirstItems, Remove_LastItems, RemoveAt_FirstItems, RemoveAt_LastItems, SetItem, } @@ -37,7 +37,7 @@ namespace Recyclable.Collections.Benchmarks RecyclableCollectionsBenchmarkType.Create_WithCapacity, RecyclableCollectionsBenchmarkType.Create, RecyclableCollectionsBenchmarkType.GetItem, - RecyclableCollectionsBenchmarkType.IndexOf_AllItems, + RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases, RecyclableCollectionsBenchmarkType.IndexOf_FirstItems, RecyclableCollectionsBenchmarkType.IndexOf_LastItems, RecyclableCollectionsBenchmarkType.LongCount, From fa8f1b029a09ec5b7fbab4755e23bb83d8dd9e28 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 02:24:23 +0200 Subject: [PATCH 259/305] RecyclableCollections.Benchmarks.IndexOf_BestAndWorstCases switched to use TestObjectCountForSlowMethods. --- .../Benchmarks/IndexOf_BestAndWorstCases.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs index 30e2606..650ba99 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs @@ -2,13 +2,11 @@ { public partial class RecyclableCollectionsBenchmarks { - private const long MaxItemsToSearchFor = 100_000;// RecyclableDefaults.MinItemsCountForParallelization * 3; - public void Array_IndexOf_BestAndWorstCases() { var data = TestObjects; var list = TestObjects; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(Array.IndexOf(list, data[i])); @@ -20,7 +18,7 @@ { var data = TestObjects; var list = TestObjectsAsList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -32,7 +30,7 @@ { var data = TestObjects; var list = TestObjectsAsPooledList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -44,7 +42,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableArrayList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -56,7 +54,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); @@ -68,7 +66,7 @@ { var data = TestObjects; var list = TestObjectsAsRecyclableList; - var dataCount = Math.Min(TestObjectCount, MaxItemsToSearchFor); + var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { DoNothing(list.IndexOf(data[i])); From 26f9666df63b79068ecadec92512c3fe06dee250 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 02:25:18 +0200 Subject: [PATCH 260/305] Tiny cleanup --- .../RecyclableCollections.Benchmarks.cs | 8 +-- .../POC/PocBenchmarkBase.cs | 4 +- .../Parallel/ManualResetEventSlimmer.cs | 2 +- .../ParallelSynchronizationContext.cs | 63 ++++++++++--------- Recyclable.Collections/RecyclableList.cs | 2 +- 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index bae12f8..1c6d12a 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -14,14 +14,14 @@ namespace Recyclable.Collections.Benchmarks public partial class RecyclableCollectionsBenchmarks : RecyclableBenchmarkBase { - public RecyclableCollectionsBenchmarks() : base() - { + public RecyclableCollectionsBenchmarks() : base() + { TestObjectCount = 1; BaselineBenchmarkType = RecyclableCollectionsBenchmarkSource.PooledList; BenchmarkType = RecyclableCollectionsBenchmarkSource.RecyclableList; - } + } - [Params + [Params ( RecyclableCollectionsBenchmarkType.Add_WithCapacity, RecyclableCollectionsBenchmarkType.Add, diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 2446dab..12af85a 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -91,7 +91,7 @@ namespace Recyclable.Collections.Benchmarks.POC if (BaselineBenchmarkType != null) { - Console.WriteLine($"******* SETTING UP BASELINE DATA *******"); + Console.WriteLine("******* SETTING UP BASELINE DATA *******"); PrepareData(BaselineBenchmarkType); _baselineMethod = GetTestMethod(BaselineBenchmarkType); } @@ -100,7 +100,7 @@ namespace Recyclable.Collections.Benchmarks.POC //~ One would override another - waste of time & resources. if (!EqualityComparer.Default.Equals(BaselineBenchmarkType, BenchmarkType)) { - Console.WriteLine($"******* SETTING UP TEST CASE DATA *******"); + Console.WriteLine("******* SETTING UP TEST CASE DATA *******"); //~ If BaselineBenchmarkType == null, then we'll come here only if BaselineBenchmarkType != null. PrepareData(BenchmarkType!); _testMethod = GetTestMethod(BenchmarkType); diff --git a/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs b/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs index c56260c..042e49a 100644 --- a/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs +++ b/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections.Parallel { - public sealed class ManualResetEventSlimmer + public sealed class ManualResetEventSlimmer { internal bool _isSet; public bool IsSet => _isSet; diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs index 649027c..e41c3f0 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs @@ -1,38 +1,39 @@ namespace Recyclable.Collections.Parallel { - public sealed class ParallelSynchronizationContext : IDisposable - { - public readonly ManualResetEventSlimmer ItemFoundSignal; - public readonly Barrier AllDoneSignal; - public long FoundItemIndex; - public Exception? Exception; - internal bool _returned; + public sealed class ParallelSynchronizationContext : IDisposable + { + public readonly ManualResetEventSlimmer ItemFoundSignal; - public ParallelSynchronizationContext() - { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); + public readonly Barrier AllDoneSignal; + public long FoundItemIndex; + public Exception? Exception; + internal bool _returned; + + public ParallelSynchronizationContext() + { + ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); AllDoneSignal = new(0); - } + } - public ParallelSynchronizationContext(int participantCount) - { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); - AllDoneSignal = new(participantCount); - } + public ParallelSynchronizationContext(int participantCount) + { + ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); + AllDoneSignal = new(participantCount); + } - public void Dispose() - { - if (!_returned) - { - _returned = true; - ParallelSynchronizationContextPool.Shared.Return(this); - } - else - { - ItemFoundSignal.Dispose(); - AllDoneSignal.Dispose(); - GC.SuppressFinalize(this); - } - } - } + public void Dispose() + { + if (!_returned) + { + _returned = true; + ParallelSynchronizationContextPool.Shared.Return(this); + } + else + { + ItemFoundSignal.Dispose(); + AllDoneSignal.Dispose(); + GC.SuppressFinalize(this); + } + } + } } \ No newline at end of file diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 7e64195..f3c1a24 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using Recyclable.Collections.Parallel; using Recyclable.Collections.Pools; -[assembly:InternalsVisibleTo("Recyclable.Collections.Benchmarks")] +[assembly: InternalsVisibleTo("Recyclable.Collections.Benchmarks")] namespace Recyclable.Collections { From 2221f572c5d2424763653f1aadcbdbee2d039d8b Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 14:36:34 +0200 Subject: [PATCH 261/305] Changed order of properties to display better in benchmark results. --- .../Benchmarks/RecyclableCollections.Benchmarks.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 1c6d12a..ff82616 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -50,6 +50,12 @@ namespace Recyclable.Collections.Benchmarks public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_AllItems; + [Params + ( + RecyclableCollectionsBenchmarkSource.PooledList + )] + public override RecyclableCollectionsBenchmarkSource BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + [Params ( RecyclableCollectionsBenchmarkSource.Array, @@ -60,12 +66,6 @@ namespace Recyclable.Collections.Benchmarks )] public override RecyclableCollectionsBenchmarkSource BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } - [Params - ( - RecyclableCollectionsBenchmarkSource.PooledList - )] - public override RecyclableCollectionsBenchmarkSource BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } - protected override Action GetTestMethod(RecyclableCollectionsBenchmarkSource benchmarkType) => (Action?) GetType().GetMethod($"{benchmarkType}_{TestCase}", BindingFlags.Instance | BindingFlags.Public)?.CreateDelegate(typeof(Action), this) ?? throw CreateMethodNotFoundException($"{benchmarkType}_{TestCase}", GetType().FullName); From 52715d9135476fd292daf5976c2b947264d61ef2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 14:40:34 +0200 Subject: [PATCH 262/305] Default value added to allow skipping baseline benchmark. --- Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs index 28a163f..01d2bf7 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs @@ -7,6 +7,7 @@ namespace Recyclable.Collections.Benchmarks { public enum RecyclableCollectionsBenchmarkSource { + Unknown, Array, List, PooledList, From e79a4fd823bed952bdd659bbb805740dfaace7b5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 14:51:14 +0200 Subject: [PATCH 263/305] Block size adjustment to more realistic use cases --- Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 12af85a..69cf0f6 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -51,7 +51,7 @@ namespace Recyclable.Collections.Benchmarks.POC public int BlockSize => TestObjectCount switch { 0 => 1, - > 0 and <= 10 => TestObjectCount, + > 0 and <= 10_240 => TestObjectCount, _ => TestObjectCount / Divider > 0 ? TestObjectCount / Divider : TestObjectCount }; From 5ed87d6f95f83873f717d3531ac090c1beb0d0db Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 14:55:06 +0200 Subject: [PATCH 264/305] New benchmark params added --- .../POC/PocBenchmarkBase.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 69cf0f6..6061fe7 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -33,13 +33,18 @@ namespace Recyclable.Collections.Benchmarks.POC // 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 // 1073741824 2147483648 - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 100_000_000)] - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000, RecyclableDefaults.MaxPooledBlockSize)] - //[Params(1_000_000, 10_000_000, 100_000_000, 1_000_000_000)] - //[Params(1, 8, 16, 128, 192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000)] - //[Params(RecyclableDefaults.MaxPooledBlockSize)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 100_000_000)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000, RecyclableDefaults.MaxPooledBlockSize)] + // [Params(1_000_000, 10_000_000, 100_000_000, 1_000_000_000)] + // [Params(192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144)] + // [Params(524_288, 786_432, 1_048_576)] + // [Params(RecyclableDefaults.MaxPooledBlockSize)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000, 33_554_432, 50_000_000, 100_000_000)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128)] + // [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)] + // [Params(1_048_576)] public virtual int TestObjectCount { get; set; }= 524_288; //131_072; public virtual int TestObjectCountForSlowMethods => Math.Min(TestObjectCount, 100); From 66d9d63f3b0d08a5a458dd9ee58d23d9a88d04c1 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 14:56:17 +0200 Subject: [PATCH 265/305] No. of items for slow benchmarks increased. --- Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 6061fe7..1a12327 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -46,7 +46,7 @@ namespace Recyclable.Collections.Benchmarks.POC // [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)] // [Params(1_048_576)] public virtual int TestObjectCount { get; set; }= 524_288; //131_072; - public virtual int TestObjectCountForSlowMethods => Math.Min(TestObjectCount, 100); + public virtual int TestObjectCountForSlowMethods => Math.Min(TestObjectCount, 131_072); //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] //[Params(10, 20, 50, 100)] From 95e1932769eb3d1e71c69607fb06d235eb75c053 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 14:57:50 +0200 Subject: [PATCH 266/305] Compilation error fixed. --- .../Benchmarks/RecyclableCollections.Benchmarks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index ff82616..5aa2adb 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -48,7 +48,7 @@ namespace Recyclable.Collections.Benchmarks RecyclableCollectionsBenchmarkType.SetItem )] - public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_AllItems; + public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases; [Params ( From 209dac18089c7e978dd4b24c2818af7332e5b76f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 15:09:01 +0200 Subject: [PATCH 267/305] Tiny cleanup --- .../RecyclableListPoc.Benchmarks.IndexOf.cs | 170 +++++++++--------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs index 61cab10..a1ce342 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs @@ -42,7 +42,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - internal static class ItemRangePoolV1 + internal static class ItemRangePoolV1 { [ThreadStatic] private static readonly ObjectPool _shared; @@ -565,19 +565,19 @@ namespace Recyclable.Collections.Benchmarks.POC else { blockIndex = sourceBlockCount; - while (true) - { - newMemoryBlocks[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } - } + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } } list._reservedBlockCount = requiredBlockCount; @@ -1239,7 +1239,7 @@ namespace Recyclable.Collections.Benchmarks.POC GC.SuppressFinalize(this); } } - } + } #endregion @@ -1694,35 +1694,35 @@ namespace Recyclable.Collections.Benchmarks.POC } blockIndex = sourceBlockCount; - while (true) - { - newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } - } + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } else { blockIndex = sourceBlockCount; - while (true) - { - newMemoryBlocks[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } - } + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } } list._reservedBlockCount = requiredBlockCount; @@ -2811,35 +2811,35 @@ namespace Recyclable.Collections.Benchmarks.POC } blockIndex = sourceBlockCount; - while (true) - { - newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } - } + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } else { blockIndex = sourceBlockCount; - while (true) - { - newMemoryBlocks[blockIndex] = new T[minBlockSize]; - if (blockIndex + 1 < requiredBlockCount) - { - blockIndex++; - } - else - { - break; - } - } - } + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } } list._reservedBlockCount = requiredBlockCount; @@ -3561,27 +3561,27 @@ namespace Recyclable.Collections.Benchmarks.POC } } - internal sealed class ParallelSynchronizationContextV4 : IDisposable - { - public readonly ManualResetEventSlimmer ItemFoundSignal; - public readonly Barrier AllDoneSignal; - public long FoundItemIndex; - public Exception? Exception; + internal sealed class ParallelSynchronizationContextV4 : IDisposable + { + public readonly ManualResetEventSlimmer ItemFoundSignal; + public readonly Barrier AllDoneSignal; + public long FoundItemIndex; + public Exception? Exception; - public ParallelSynchronizationContextV4(int participantCount) - { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); - AllDoneSignal = new(participantCount); - } + public ParallelSynchronizationContextV4(int participantCount) + { + ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); + AllDoneSignal = new(participantCount); + } - public void Dispose() - { - ItemFoundSignal.Dispose(); - AllDoneSignal.Dispose(); + public void Dispose() + { + ItemFoundSignal.Dispose(); + AllDoneSignal.Dispose(); - GC.SuppressFinalize(this); - } - } + GC.SuppressFinalize(this); + } + } internal class RecyclableListIndexOfV4 : IDisposable, IList { From 002e043b190be580e5fdcfd1f3017514bcdd6feb Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 15:38:23 +0200 Subject: [PATCH 268/305] RecyclableList.IndexOf performance improvements. --- Recyclable.Collections/ItemRange.cs | 4 +- Recyclable.Collections/ItemRangesIterator.cs | 19 +- .../ParallelSynchronizationContext.cs | 14 +- .../ParallelSynchronizationContextPool.cs | 6 +- Recyclable.Collections/Pools/ItemRangePool.cs | 3 +- Recyclable.Collections/RecyclableDefaults.cs | 3 + Recyclable.Collections/RecyclableList.cs | 348 +++++++----------- Recyclable.Collections/SearchConfiguration.cs | 4 +- .../ItemRangesIteratorTests.cs | 114 +++--- 9 files changed, 207 insertions(+), 308 deletions(-) diff --git a/Recyclable.Collections/ItemRange.cs b/Recyclable.Collections/ItemRange.cs index 892aa43..37a31fa 100644 --- a/Recyclable.Collections/ItemRange.cs +++ b/Recyclable.Collections/ItemRange.cs @@ -5,7 +5,6 @@ namespace Recyclable.Collections public sealed class ItemRange { public int BlockIndex; - public int StartingItemIndex; public long ItemsToSearchCount; internal bool _returned; @@ -13,11 +12,10 @@ namespace Recyclable.Collections { } - public ItemRange(int blockIndex, int startingItemIndex, long itemsToSearchCount) + public ItemRange(int blockIndex, long itemsToSearchCount) { BlockIndex = blockIndex; ItemsToSearchCount = itemsToSearchCount; - StartingItemIndex = startingItemIndex; } public void Dispose() diff --git a/Recyclable.Collections/ItemRangesIterator.cs b/Recyclable.Collections/ItemRangesIterator.cs index 74c7d9e..9a150aa 100644 --- a/Recyclable.Collections/ItemRangesIterator.cs +++ b/Recyclable.Collections/ItemRangesIterator.cs @@ -8,31 +8,22 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] public static void Iterate(in ParallelSynchronizationContext context, in SearchConfiguration search, Func, ItemRange, bool> action) { - int itemIndex = 0; ItemRange itemRange; int blockIndex = search.StartingBlockIndex; long itemsCount = search.ItemsCount; - while (itemsCount > search.Step) + while (itemsCount > search.BlockSize) { - itemRange = new(blockIndex, itemIndex, search.Step); + itemRange = new(blockIndex, search.BlockSize); if (!action(context, search, itemRange)) { break; } - itemsCount -= search.Step; - //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); - blockIndex += (int)((itemIndex + search.Step) >> search.BlockSizePow2BitShift); - itemIndex = (int)((itemIndex + search.Step) & search.BlockSizeMinus1); - //itemIndex += step; - //while (itemIndex >= blockSize) - //{ - // blockIndex++; - // itemIndex -= blockSize; - //} + itemsCount -= search.BlockSize; + blockIndex++; } - itemRange = new(blockIndex, itemIndex, itemsCount); + itemRange = new(blockIndex, itemsCount); _ = action(context, search, itemRange); } } diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs index e41c3f0..2a2fc31 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs @@ -2,7 +2,7 @@ namespace Recyclable.Collections.Parallel { public sealed class ParallelSynchronizationContext : IDisposable { - public readonly ManualResetEventSlimmer ItemFoundSignal; + internal bool _isItemFound; public readonly Barrier AllDoneSignal; public long FoundItemIndex; @@ -11,16 +11,21 @@ namespace Recyclable.Collections.Parallel public ParallelSynchronizationContext() { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); - AllDoneSignal = new(0); + AllDoneSignal = new(0); } public ParallelSynchronizationContext(int participantCount) { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); AllDoneSignal = new(participantCount); } + public void SetItemFound() + { + _isItemFound = true; + } + + public bool IsItemFound => _isItemFound; + public void Dispose() { if (!_returned) @@ -30,7 +35,6 @@ namespace Recyclable.Collections.Parallel } else { - ItemFoundSignal.Dispose(); AllDoneSignal.Dispose(); GC.SuppressFinalize(this); } diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs index d4245bf..aa0142b 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs @@ -17,9 +17,9 @@ namespace Recyclable.Collections.Parallel _updateLock.Exit(false); } - if (context.ItemFoundSignal._isSet) + if (context._isItemFound) { - context.ItemFoundSignal.Reset(); + context._isItemFound = false; } switch (context.AllDoneSignal.ParticipantsRemaining) @@ -40,7 +40,7 @@ namespace Recyclable.Collections.Parallel if (context.FoundItemIndex >= 0) { - context.FoundItemIndex = -1L; + context.FoundItemIndex = RecyclableDefaults.ItemNotFoundIndexLong; } context._returned = false; diff --git a/Recyclable.Collections/Pools/ItemRangePool.cs b/Recyclable.Collections/Pools/ItemRangePool.cs index 532988f..6b332fe 100644 --- a/Recyclable.Collections/Pools/ItemRangePool.cs +++ b/Recyclable.Collections/Pools/ItemRangePool.cs @@ -7,7 +7,7 @@ namespace Recyclable.Collections.Pools private static readonly SpinLock _updateLock = new(false); public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - public static ItemRange Create(int blockIndex, int startingItemIndex, long itemsToSearchCount) + public static ItemRange Create(int blockIndex, long itemsToSearchCount) { bool lockTaken = false; _updateLock.Enter(ref lockTaken); @@ -20,7 +20,6 @@ namespace Recyclable.Collections.Pools itemRange._returned = false; itemRange.BlockIndex = blockIndex; itemRange.ItemsToSearchCount = itemsToSearchCount; - itemRange.StartingItemIndex = startingItemIndex; return itemRange; } } diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 03154ca..274a911 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -4,7 +4,10 @@ { public const int BlockSize = 16_384; public const int Capacity = 1; + public const long MinItemsCountForParallelization = 786_432; public const int MinPooledArrayLength = 128; public const int MaxPooledBlockSize = 2_147_483_591; + public const long ItemNotFoundIndexLong = -1L; + public const int ItemNotFoundIndex = -1; } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index f3c1a24..1295d9f 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -9,12 +9,8 @@ using Recyclable.Collections.Pools; namespace Recyclable.Collections { - public class RecyclableList : IDisposable, IList + public partial class RecyclableList : IDisposable, IList { - private const long ItemNotFoundIndexLong = -1L; - private const int ItemNotFoundIndex = -1; - private const double OptimalParallelSearchStep = 0.1;// 0.329; - private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; @@ -24,7 +20,7 @@ namespace Recyclable.Collections internal byte _blockSizePow2BitShift; internal int _blockSizeMinus1; private int _nextItemBlockIndex; - private int _nextItemIndex; + internal int _nextItemIndex; private int _reservedBlockCount; protected static readonly bool NeedsClearing = !typeof(T).IsValueType; @@ -42,19 +38,15 @@ namespace Recyclable.Collections public int Count => checked((int)_longCount); public bool IsReadOnly { get; } - public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + private int _lastBlockWithData; + public int LastBlockWithData => _lastBlockWithData; - internal long _longCountIndexOfStep; internal long _longCount; public long LongCount { get => _longCount; - set - { - _longCount = value; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); - } + set => _longCount = value; } public int ReservedBlockCount => _reservedBlockCount; @@ -75,7 +67,7 @@ namespace Recyclable.Collections int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); int targetItemIndex = (int)(index & list._blockSizeMinus1); - int lastTakenBlockIndex = list.LastBlockWithData; + int lastTakenBlockIndex = list._lastBlockWithData; while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) { int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 @@ -150,6 +142,8 @@ namespace Recyclable.Collections if (!enumerator.MoveNext()) { + _lastBlockWithData = targetBlockIdx; + _longCount += copied + i - _longCount; if (targetItemIdx < blockSize) { _nextItemBlockIndex = targetBlockIdx; @@ -161,8 +155,6 @@ namespace Recyclable.Collections _nextItemIndex = 0; } - _longCount += copied + i - _longCount; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); return; } @@ -220,153 +212,9 @@ namespace Recyclable.Collections } _longCount = requiredCapacity; - _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; - } - - // & WAS SLOWER - // [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static long DoLongIndexOfParallel(RecyclableList list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) - { - // & WAS SLOWER without - int blockSize = list._blockSize; - int blockIndex = itemRange.BlockIndex; - long itemsToSearchCount = itemRange.ItemsToSearchCount; - int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); - if (itemFoundSignal.IsSet) - { - return ItemNotFoundIndexLong; - } - else if (index >= 0) - { - return (blockIndex << list._blockSizePow2BitShift) + index; - } - - // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that - // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount - // will always result in a negative result. - itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; - if (itemsToSearchCount <= 0) - { - return ItemNotFoundIndexLong; - } - - //T itemToFind = itemRange.ItemToFind; - //int lastBlockWithData = itemRange.LastBlockWithData; - blockIndex++; - while (itemsToSearchCount > blockSize) - { - index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); - if (itemFoundSignal.IsSet) - { - return ItemNotFoundIndexLong; - } - else if (index >= 0) - { - return ((long)blockIndex << list._blockSizePow2BitShift) + index; - } - - itemsToSearchCount -= blockSize; - blockIndex++; - } - - index = itemsToSearchCount <= 0 //|| blockIndex > list.LastBlockWithData - ? ItemNotFoundIndex - // : blockIndex == list._nextItemBlockIndex - // ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) - : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); - // ? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) - //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); - - return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private long DoIndexOfParallel(in T item) - { - if (_longCount == 0) - { - return ItemNotFoundIndex; - } - - using var context = ParallelSynchronizationContextPool.GetWithOneParticipant(); - // (long)(_longCount * 0.329) => most efficient step, based on benchmarks - Iterate(context, item); - context.AllDoneSignal.SignalAndWait(); - if (context.Exception == null) - { - return context.FoundItemIndex; - } - - context.Exception.CaptureAndRethrow(); - return ItemNotFoundIndexLong; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static bool ScheduleIndexOfTask(ParallelSynchronizationContext context, RecyclableList list, T itemToFind, ItemRange itemRange) - { - _ = context.AllDoneSignal.AddParticipant(); - // & WAS SLOWER - // _ = ThreadPool.QueueUserWorkItem((_) => - _ = Task.Factory.StartNew(() => - { - try - { - if (context.ItemFoundSignal.IsSet) - { - return; - } - - var index = DoLongIndexOfParallel(list, itemRange, itemToFind, context.ItemFoundSignal); - if (index >= 0) - { - context.ItemFoundSignal.Set(); - _ = Interlocked.Exchange(ref context.FoundItemIndex, index); - } - } - catch (Exception e) - { - _ = Interlocked.Exchange(ref context.Exception, e); - } - finally - { - context.AllDoneSignal.RemoveParticipant(); - itemRange.Dispose(); - } - }); - - return !context.ItemFoundSignal.IsSet; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) - { - Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); - int itemIndex, blockIndex; - for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) - { - itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); - if (itemIndex >= 0) - { - return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); - } - } - - if (blockIndex == lastBlockIndex) - { - itemIndex = nextItemIndex != 0 - ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) - : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); - } - else - { - return ItemNotFoundIndexLong; - } - - return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndexLong; + _lastBlockWithData = targetBlockIdx - (targetItemIdx > 0 ? 0 : 1); } /// @@ -482,54 +330,32 @@ namespace Recyclable.Collections // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] // & WAS SLOWER // [MethodImpl(MethodImplOptions.NoInlining)] - private void Iterate(in ParallelSynchronizationContext context, in T itemToFind) - { - // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. + //private static void Iterate(RecyclableList list, in ParallelSynchronizationContext context, in T itemToFind) + //{ + // // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. - // & WAS SLOWER without - ItemRange itemRange; - int blockIndex = 0; - // & WAS SLOWER - // int blockSize = _blockSize; - long itemIndex = 0; - long itemsCount = _longCount; - // & WAS SLOWER - // int blockSize = _blockSize; - // long step = list._longCountIndexOfStep; - // & WAS SLOWER - // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) - // & WAS SLOWER - // long longCountIndexOfStep = _longCountIndexOfStep; - while (itemsCount > _longCountIndexOfStep) - { - // At this point itemIndex is limited to blockSize range - int. - itemRange = ItemRangePool.Create(blockIndex, (int)itemIndex, _longCountIndexOfStep); - if (!ScheduleIndexOfTask(context, this, itemToFind, itemRange)) - { - break; - } + // // & WAS SLOWER without + // int blockIndex = 0; - itemsCount -= _longCountIndexOfStep; + // long itemsCount = list._longCount; + // // & WAS SLOWER + // // int blockSize = _blockSize; + // // & WAS SLOWER + // // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) + // while (itemsCount > list._blockSize) + // { + // // At this point itemIndex is limited to blockSize range - int. + // if (!ScheduleIndexOfTask(context, list, blockIndex, list._blockSize, itemToFind)) + // { + // break; + // } - // & WAS SLOWER - // blockIndex = (int)Math.DivRem(itemIndex + step, step, out itemIndex); - // & WAS SLOWER - // blockIndex += (int)((itemIndex + list._longCountIndexOfStep) >> list._blockSizePow2BitShift); - // itemIndex = (int)((itemIndex + list._longCountIndexOfStep) & list._blockSizeMinus1); + // itemsCount -= list._blockSize; + // blockIndex++; + // } - itemIndex += _longCountIndexOfStep; - while (itemIndex >= _blockSize) - { - blockIndex++; - itemIndex -= _blockSize; - } - } - - // & WAS SLOWER - // itemRange = new(blockIndex, (int)itemIndex, list._longCount - (blockIndex << list._blockSizePow2BitShift) - itemIndex); - itemRange = ItemRangePool.Create(blockIndex, (int)itemIndex, itemsCount); - _ = ScheduleIndexOfTask(context, this, itemToFind, itemRange); - } + // _ = ScheduleIndexOfTask(context, list, blockIndex, (int)itemsCount, itemToFind); + //} [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) @@ -648,9 +474,12 @@ namespace Recyclable.Collections _nextItemBlockIndex++; _nextItemIndex = 0; } + else if (_nextItemIndex == 1) + { + _lastBlockWithData++; + } _longCount++; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); } public void AddRange(in T[] items) @@ -693,8 +522,8 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } public void AddRange(RecyclableArrayList items) @@ -733,8 +562,8 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } public void AddRange(RecyclableList items) @@ -794,6 +623,7 @@ namespace Recyclable.Collections } } + _lastBlockWithData = targetBlockIndex; if ((targetCapacity & _blockSizeMinus1) == 0) { _nextItemIndex = 0; @@ -806,7 +636,6 @@ namespace Recyclable.Collections } _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); } public void AddRange(List items) @@ -853,7 +682,7 @@ namespace Recyclable.Collections _nextItemBlockIndex = targetBlockIndex; _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _lastBlockWithData = targetBlockIndex - (_nextItemIndex > 0 ? 0 : 1); } public void AddRange(IList items) @@ -898,9 +727,9 @@ namespace Recyclable.Collections itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; _nextItemIndex = itemsSpan.Length; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } finally { @@ -976,8 +805,8 @@ namespace Recyclable.Collections _reservedBlockCount = 0; _nextItemBlockIndex = 0; _nextItemIndex = 0; + _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; _longCount = 0; - _longCountIndexOfStep = 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1010,17 +839,81 @@ namespace Recyclable.Collections public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(T item) => checked((int)DoIndexOfParallel(item)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public int IndexOf(T item) + { + if (_lastBlockWithData == 0) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + } + else if (_longCount == 0) + { + return RecyclableDefaults.ItemNotFoundIndex; + } + + int itemIndex = Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + if (itemIndex >= 0) + { + return itemIndex; + } + else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization) + { + return (int)RecyclableListIndexOfHelpers.IndexOfSequential(this, item); + } + else + { + // & WAS SLOWER + // long toSearch = _longCount - _blockSize; + if (_longCount > _blockSize) + { + itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize)); + return itemIndex >= 0 ? _blockSize + itemIndex : (int)RecyclableListIndexOfHelpers.IndexOfParallel(this, item); + } + else + { + return RecyclableDefaults.ItemNotFoundIndex; + } + } + } public void Insert(int index, T item) => throw new NotSupportedException(); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public long LongIndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndexLong - : _longCount <= _blockSize - ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) - : DoIndexOfParallel(item); + public long LongIndexOf(T item) + { + if (_lastBlockWithData == 0) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + } + else if (_longCount == 0) + { + return RecyclableDefaults.ItemNotFoundIndex; + } + + int itemIndex = Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + if (itemIndex >= 0) + { + return itemIndex; + } + else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization) + { + return RecyclableListIndexOfHelpers.IndexOfSequential(this, item); + } + else + { + // & WAS SLOWER + // long toSearch = _longCount - _blockSize; + if (_longCount > _blockSize) + { + itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize)); + return itemIndex >= 0 ? _blockSize + itemIndex : RecyclableListIndexOfHelpers.IndexOfParallel(this, item); + } + else + { + return RecyclableDefaults.ItemNotFoundIndex; + } + } + } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool Remove(T item) @@ -1029,7 +922,6 @@ namespace Recyclable.Collections if (index >= 0) { _longCount--; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1039,6 +931,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { @@ -1080,6 +976,8 @@ namespace Recyclable.Collections { _nextItemBlockIndex--; } + + _lastBlockWithData--; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -1091,7 +989,6 @@ namespace Recyclable.Collections } _longCount--; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1101,6 +998,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { @@ -1125,7 +1026,6 @@ namespace Recyclable.Collections } _longCount--; - _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1135,6 +1035,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { diff --git a/Recyclable.Collections/SearchConfiguration.cs b/Recyclable.Collections/SearchConfiguration.cs index 35c8a2c..622ae2a 100644 --- a/Recyclable.Collections/SearchConfiguration.cs +++ b/Recyclable.Collections/SearchConfiguration.cs @@ -8,7 +8,7 @@ namespace Recyclable.Collections public byte BlockSizePow2BitShift { get; init; } public long ItemsCount { get; init; } public int StartingBlockIndex { get; init; } - public long Step { get; init; } + public long BlockSize { get; init; } // public SearchConfiguration(TList list, in TItem itemToFind) // { @@ -23,7 +23,7 @@ namespace Recyclable.Collections BlockSizeMinus1 = list._blockSizeMinus1, BlockSizePow2BitShift = list._blockSizePow2BitShift, ItemsCount = list._longCount, - Step = list._longCountIndexOfStep + BlockSize = list._blockSize }; } } \ No newline at end of file diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs index 586fbae..57b4973 100644 --- a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs +++ b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs @@ -9,7 +9,7 @@ namespace Recyclable.CollectionsTests { [Theory] [MemberData(nameof(TestCases))] - public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, int StartingItemIndex, long ItemsCount)> expected) + public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, long ItemsCount)> expected) { // Prepare var mockList = new RecyclableList(); @@ -23,15 +23,15 @@ namespace Recyclable.CollectionsTests BlockSizeMinus1 = blockSize - 1, BlockSizePow2BitShift = blockSizePow2BitShift, ItemsCount = itemsCount, - Step = step, + BlockSize = step, }; - var actualItemRanges = new List<(int BlockIndex, int StartingItemIndex, long ItemsCount)>(); + var actualItemRanges = new List<(int BlockIndex, long ItemsCount)>(); // Act ItemRangesIterator.Iterate(synchronizationContext, search, (context, search, itemRange) => { - actualItemRanges.Add((itemRange.BlockIndex, itemRange.StartingItemIndex, itemRange.ItemsToSearchCount)); + actualItemRanges.Add((itemRange.BlockIndex, itemRange.ItemsToSearchCount)); return true; }); @@ -43,102 +43,102 @@ namespace Recyclable.CollectionsTests { new object[] { - 0, 128, 333, (long)(333 * 0.329), new(int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 128, 333, (long)(333 * 0.329), new(int BlockIndex, long ItemsCount)[] { - (0, 0, 109), - (0, 109, 109), - (1, 90, 109), - (2, 71, 6) + (0, 109), + (0, 109), + (1, 109), + (2, 6) } }, new object[] { - 0, 2, 10, 2, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 2, 10, 2, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 2), - (1, 0, 2), - (2, 0, 2), - (3, 0, 2), - (4, 0, 2), + (0, 2), + (1, 2), + (2, 2), + (3, 2), + (4, 2), } }, new object[] { - 0, 2, 10, 3, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 2, 10, 3, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 3), - (1, 1, 3), - (3, 0, 3), - (4, 1, 1), + (0, 3), + (1, 3), + (3, 3), + (4, 1), } }, new object[] { - 0, 1, 10, 3, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 1, 10, 3, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 3), - (3, 0, 3), - (6, 0, 3), - (9, 0, 1), + (0, 3), + (3, 3), + (6, 3), + (9, 1), } }, new object[] { - 0, 2, 10, 4, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 2, 10, 4, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 4), - (2, 0, 4), - (4, 0, 2), + (0, 4), + (2, 4), + (4, 2), } }, new object[] { - 0, 1, 10, 2, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 1, 10, 2, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 2), - (2, 0, 2), - (4, 0, 2), - (6, 0, 2), - (8, 0, 2), + (0, 2), + (2, 2), + (4, 2), + (6, 2), + (8, 2), } }, new object[] { - 0, 2, 10, 1, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 2, 10, 1, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 1), - (0, 1, 1), - (1, 0, 1), - (1, 1, 1), - (2, 0, 1), - (2, 1, 1), - (3, 0, 1), - (3, 1, 1), - (4, 0, 1), - (4, 1, 1), + (0, 1), + (0, 1), + (1, 1), + (1, 1), + (2, 1), + (2, 1), + (3, 1), + (3, 1), + (4, 1), + (4, 1), } }, new object[] { - 0, 1, 10, 1, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 1, 10, 1, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 1), - (1, 0, 1), - (2, 0, 1), - (3, 0, 1), - (4, 0, 1), - (5, 0, 1), - (6, 0, 1), - (7, 0, 1), - (8, 0, 1), - (9, 0, 1), + (0, 1), + (1, 1), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 1), + (9, 1), } }, }; From b458f450fc3245f19bd77ae95c410f25f1a9da29 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 15:38:23 +0200 Subject: [PATCH 269/305] RecyclableList.IndexOf performance improvements. --- Recyclable.Collections/ItemRange.cs | 4 +- Recyclable.Collections/ItemRangesIterator.cs | 19 +- .../ParallelSynchronizationContext.cs | 14 +- .../ParallelSynchronizationContextPool.cs | 6 +- Recyclable.Collections/Pools/ItemRangePool.cs | 3 +- Recyclable.Collections/RecyclableDefaults.cs | 3 + ...clableList.RecyclableListIndexOfHelpers.cs | 295 +++++++++++++++ Recyclable.Collections/RecyclableList.cs | 349 +++++++----------- Recyclable.Collections/SearchConfiguration.cs | 4 +- .../ItemRangesIteratorTests.cs | 114 +++--- 10 files changed, 502 insertions(+), 309 deletions(-) create mode 100644 Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs diff --git a/Recyclable.Collections/ItemRange.cs b/Recyclable.Collections/ItemRange.cs index 892aa43..37a31fa 100644 --- a/Recyclable.Collections/ItemRange.cs +++ b/Recyclable.Collections/ItemRange.cs @@ -5,7 +5,6 @@ namespace Recyclable.Collections public sealed class ItemRange { public int BlockIndex; - public int StartingItemIndex; public long ItemsToSearchCount; internal bool _returned; @@ -13,11 +12,10 @@ namespace Recyclable.Collections { } - public ItemRange(int blockIndex, int startingItemIndex, long itemsToSearchCount) + public ItemRange(int blockIndex, long itemsToSearchCount) { BlockIndex = blockIndex; ItemsToSearchCount = itemsToSearchCount; - StartingItemIndex = startingItemIndex; } public void Dispose() diff --git a/Recyclable.Collections/ItemRangesIterator.cs b/Recyclable.Collections/ItemRangesIterator.cs index 74c7d9e..9a150aa 100644 --- a/Recyclable.Collections/ItemRangesIterator.cs +++ b/Recyclable.Collections/ItemRangesIterator.cs @@ -8,31 +8,22 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] public static void Iterate(in ParallelSynchronizationContext context, in SearchConfiguration search, Func, ItemRange, bool> action) { - int itemIndex = 0; ItemRange itemRange; int blockIndex = search.StartingBlockIndex; long itemsCount = search.ItemsCount; - while (itemsCount > search.Step) + while (itemsCount > search.BlockSize) { - itemRange = new(blockIndex, itemIndex, search.Step); + itemRange = new(blockIndex, search.BlockSize); if (!action(context, search, itemRange)) { break; } - itemsCount -= search.Step; - //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); - blockIndex += (int)((itemIndex + search.Step) >> search.BlockSizePow2BitShift); - itemIndex = (int)((itemIndex + search.Step) & search.BlockSizeMinus1); - //itemIndex += step; - //while (itemIndex >= blockSize) - //{ - // blockIndex++; - // itemIndex -= blockSize; - //} + itemsCount -= search.BlockSize; + blockIndex++; } - itemRange = new(blockIndex, itemIndex, itemsCount); + itemRange = new(blockIndex, itemsCount); _ = action(context, search, itemRange); } } diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs index e41c3f0..2a2fc31 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs @@ -2,7 +2,7 @@ namespace Recyclable.Collections.Parallel { public sealed class ParallelSynchronizationContext : IDisposable { - public readonly ManualResetEventSlimmer ItemFoundSignal; + internal bool _isItemFound; public readonly Barrier AllDoneSignal; public long FoundItemIndex; @@ -11,16 +11,21 @@ namespace Recyclable.Collections.Parallel public ParallelSynchronizationContext() { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); - AllDoneSignal = new(0); + AllDoneSignal = new(0); } public ParallelSynchronizationContext(int participantCount) { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); AllDoneSignal = new(participantCount); } + public void SetItemFound() + { + _isItemFound = true; + } + + public bool IsItemFound => _isItemFound; + public void Dispose() { if (!_returned) @@ -30,7 +35,6 @@ namespace Recyclable.Collections.Parallel } else { - ItemFoundSignal.Dispose(); AllDoneSignal.Dispose(); GC.SuppressFinalize(this); } diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs index d4245bf..aa0142b 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs @@ -17,9 +17,9 @@ namespace Recyclable.Collections.Parallel _updateLock.Exit(false); } - if (context.ItemFoundSignal._isSet) + if (context._isItemFound) { - context.ItemFoundSignal.Reset(); + context._isItemFound = false; } switch (context.AllDoneSignal.ParticipantsRemaining) @@ -40,7 +40,7 @@ namespace Recyclable.Collections.Parallel if (context.FoundItemIndex >= 0) { - context.FoundItemIndex = -1L; + context.FoundItemIndex = RecyclableDefaults.ItemNotFoundIndexLong; } context._returned = false; diff --git a/Recyclable.Collections/Pools/ItemRangePool.cs b/Recyclable.Collections/Pools/ItemRangePool.cs index 532988f..6b332fe 100644 --- a/Recyclable.Collections/Pools/ItemRangePool.cs +++ b/Recyclable.Collections/Pools/ItemRangePool.cs @@ -7,7 +7,7 @@ namespace Recyclable.Collections.Pools private static readonly SpinLock _updateLock = new(false); public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - public static ItemRange Create(int blockIndex, int startingItemIndex, long itemsToSearchCount) + public static ItemRange Create(int blockIndex, long itemsToSearchCount) { bool lockTaken = false; _updateLock.Enter(ref lockTaken); @@ -20,7 +20,6 @@ namespace Recyclable.Collections.Pools itemRange._returned = false; itemRange.BlockIndex = blockIndex; itemRange.ItemsToSearchCount = itemsToSearchCount; - itemRange.StartingItemIndex = startingItemIndex; return itemRange; } } diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 03154ca..274a911 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -4,7 +4,10 @@ { public const int BlockSize = 16_384; public const int Capacity = 1; + public const long MinItemsCountForParallelization = 786_432; public const int MinPooledArrayLength = 128; public const int MaxPooledBlockSize = 2_147_483_591; + public const long ItemNotFoundIndexLong = -1L; + public const int ItemNotFoundIndex = -1; } } diff --git a/Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs b/Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs new file mode 100644 index 0000000..03b00bc --- /dev/null +++ b/Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs @@ -0,0 +1,295 @@ +using System.Runtime.CompilerServices; +using Recyclable.Collections.Parallel; + +namespace Recyclable.Collections +{ + public partial class RecyclableList + { + public static class RecyclableListIndexOfHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] + private static void ScheduleIndexOfTask(ParallelSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) + { + _ = context.AllDoneSignal.AddParticipant(); + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + _ = Task.Factory.StartNew(() => + { + try + { + if (context._isItemFound) + { + return; + } + + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); + return; + } + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 1], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 1) << list._blockSizePow2BitShift) + index); + return; + } + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 2) << list._blockSizePow2BitShift) + index); + return; + } + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 3) << list._blockSizePow2BitShift) + index); + } + + //itemsToSearch -= blockSize; + //if (itemsToSearch > 0) + //{ + // if (itemsToSearch < blockSize) + // { + // blockSize = (int)itemsToSearch; + // } + + // index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, blockSize); + // if (index >= 0) + // { + // if (!context.ItemFoundSignal.IsSet) + // { + // context.ItemFoundSignal.Set(); + // _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 2) << list._blockSizePow2BitShift) + index); + // } + + // return; + // } + //} + + //itemsToSearch -= blockSize; + //if (itemsToSearch > 0) + //{ + // if (itemsToSearch < blockSize) + // { + // blockSize = (int)itemsToSearch; + // } + + // index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, blockSize); + // if (!context.ItemFoundSignal.IsSet && index >= 0) + // { + // context.ItemFoundSignal.Set(); + // _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 3) << list._blockSizePow2BitShift) + index); + + // return; + // } + //} + } + catch (Exception e) + { + e.Data.Add("list", list); + e.Data.Add("itemToFind", itemToFind); + e.Data.Add("blockIndex", blockIndex); + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.AllDoneSignal.RemoveParticipant(); + } + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] + private static void ScheduleIndexOfTaskSingle(ParallelSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) + { + _ = context.AllDoneSignal.AddParticipant(); + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + _ = Task.Factory.StartNew(() => + { + try + { + if (context._isItemFound) + { + return; + } + + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); + } + } + catch (Exception e) + { + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.AllDoneSignal.RemoveParticipant(); + } + }); + } + + private const int BlocksForEachTask = 4; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long IndexOfParallel(RecyclableList list, in T item) + { + // & WAS SLOWER + //long itemsCount = Array.IndexOf(list._memoryBlocks[0], item, 0, (int)Math.Min(list._blockSize, list._longCount)); + //if (itemsCount >= 0) + //{ + // return itemsCount; + //} + + //using var context = new ParallelSynchronizationContext(1); + ParallelSynchronizationContext context = ParallelSynchronizationContextPool.GetWithOneParticipant(); + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + //Iterate(this, context, item); + + // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. + + // & WAS SLOWER without + int blockIndex = 2; + // & WAS SLOWER + //long itemsCount = list._longCount - list._blockSize; + // & WAS SLOWER + // int blockSize = _blockSize; + // & WAS SLOWER + // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) + //long blockSize = list._blockSize << 1; + int lastBlockWithData = list._lastBlockWithData - BlocksForEachTask; + while (blockIndex < lastBlockWithData) + { + // At this point itemIndex is limited to blockSize range - int. + ScheduleIndexOfTask(context, list, blockIndex, item); + if (context._isItemFound) + { + break; + } + + blockIndex += BlocksForEachTask; + } + + if (!context._isItemFound) + { + while (blockIndex < list._lastBlockWithData) + { + ScheduleIndexOfTaskSingle(context, list, blockIndex, item); + if (context._isItemFound) + { + break; + } + + blockIndex++; + } + } + + int itemIndex = Array.IndexOf(list._memoryBlocks[list._lastBlockWithData], item, 0, list._nextItemIndex > 0 ? list._nextItemIndex : list._blockSize); + if (itemIndex >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex); + } + + context.AllDoneSignal.SignalAndWait(); + if (context.Exception == null) + { + return context.FoundItemIndex; + } + + context.Exception.CaptureAndRethrow(); + return RecyclableDefaults.ItemNotFoundIndexLong; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long IndexOfSequential(RecyclableList list, in T item) //, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + int itemIndex; + // & WAS SLOWER + //int lastBlockWithData = list._lastBlockWithData; + Span memoryBlocksSpan = new(list._memoryBlocks, 0, list._lastBlockWithData + 1); + int blockIndex; + for (blockIndex = 1; blockIndex < list._lastBlockWithData; blockIndex += 4) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << list._blockSizePow2BitShift); + } + + if (blockIndex + 1 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 1], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 1)<< list._blockSizePow2BitShift); + } + + if (blockIndex + 2 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 2], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 2)<< list._blockSizePow2BitShift); + } + + if (blockIndex + 3 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 3], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 3)<< list._blockSizePow2BitShift); + } + } + } + } + } + + //if (blockIndex == list._lastBlockWithData) + //{ + itemIndex = list._nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._blockSize); + //} + //else + //{ + // return RecyclableDefaults.ItemNotFoundIndexLong; + //} + + return itemIndex >= 0 ? itemIndex + ((long)list._lastBlockWithData << list._blockSizePow2BitShift) : RecyclableDefaults.ItemNotFoundIndexLong; + } + + public static int Min(int a, int b, int c) => a < b ? a < c ? a : c : b < c ? b : c; + } + } +} diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index f3c1a24..22713b4 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -2,19 +2,14 @@ using System.Collections; using System.Numerics; using System.Runtime.CompilerServices; -using Recyclable.Collections.Parallel; using Recyclable.Collections.Pools; [assembly: InternalsVisibleTo("Recyclable.Collections.Benchmarks")] namespace Recyclable.Collections { - public class RecyclableList : IDisposable, IList + public partial class RecyclableList : IDisposable, IList { - private const long ItemNotFoundIndexLong = -1L; - private const int ItemNotFoundIndex = -1; - private const double OptimalParallelSearchStep = 0.1;// 0.329; - private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; @@ -24,7 +19,7 @@ namespace Recyclable.Collections internal byte _blockSizePow2BitShift; internal int _blockSizeMinus1; private int _nextItemBlockIndex; - private int _nextItemIndex; + internal int _nextItemIndex; private int _reservedBlockCount; protected static readonly bool NeedsClearing = !typeof(T).IsValueType; @@ -42,19 +37,15 @@ namespace Recyclable.Collections public int Count => checked((int)_longCount); public bool IsReadOnly { get; } - public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + private int _lastBlockWithData; + public int LastBlockWithData => _lastBlockWithData; - internal long _longCountIndexOfStep; internal long _longCount; public long LongCount { get => _longCount; - set - { - _longCount = value; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); - } + set => _longCount = value; } public int ReservedBlockCount => _reservedBlockCount; @@ -75,7 +66,7 @@ namespace Recyclable.Collections int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); int targetItemIndex = (int)(index & list._blockSizeMinus1); - int lastTakenBlockIndex = list.LastBlockWithData; + int lastTakenBlockIndex = list._lastBlockWithData; while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) { int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 @@ -150,6 +141,8 @@ namespace Recyclable.Collections if (!enumerator.MoveNext()) { + _lastBlockWithData = targetBlockIdx; + _longCount += copied + i - _longCount; if (targetItemIdx < blockSize) { _nextItemBlockIndex = targetBlockIdx; @@ -161,8 +154,6 @@ namespace Recyclable.Collections _nextItemIndex = 0; } - _longCount += copied + i - _longCount; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); return; } @@ -220,153 +211,9 @@ namespace Recyclable.Collections } _longCount = requiredCapacity; - _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; - } - - // & WAS SLOWER - // [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static long DoLongIndexOfParallel(RecyclableList list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) - { - // & WAS SLOWER without - int blockSize = list._blockSize; - int blockIndex = itemRange.BlockIndex; - long itemsToSearchCount = itemRange.ItemsToSearchCount; - int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); - if (itemFoundSignal.IsSet) - { - return ItemNotFoundIndexLong; - } - else if (index >= 0) - { - return (blockIndex << list._blockSizePow2BitShift) + index; - } - - // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that - // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount - // will always result in a negative result. - itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; - if (itemsToSearchCount <= 0) - { - return ItemNotFoundIndexLong; - } - - //T itemToFind = itemRange.ItemToFind; - //int lastBlockWithData = itemRange.LastBlockWithData; - blockIndex++; - while (itemsToSearchCount > blockSize) - { - index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); - if (itemFoundSignal.IsSet) - { - return ItemNotFoundIndexLong; - } - else if (index >= 0) - { - return ((long)blockIndex << list._blockSizePow2BitShift) + index; - } - - itemsToSearchCount -= blockSize; - blockIndex++; - } - - index = itemsToSearchCount <= 0 //|| blockIndex > list.LastBlockWithData - ? ItemNotFoundIndex - // : blockIndex == list._nextItemBlockIndex - // ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) - : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); - // ? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) - //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); - - return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private long DoIndexOfParallel(in T item) - { - if (_longCount == 0) - { - return ItemNotFoundIndex; - } - - using var context = ParallelSynchronizationContextPool.GetWithOneParticipant(); - // (long)(_longCount * 0.329) => most efficient step, based on benchmarks - Iterate(context, item); - context.AllDoneSignal.SignalAndWait(); - if (context.Exception == null) - { - return context.FoundItemIndex; - } - - context.Exception.CaptureAndRethrow(); - return ItemNotFoundIndexLong; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static bool ScheduleIndexOfTask(ParallelSynchronizationContext context, RecyclableList list, T itemToFind, ItemRange itemRange) - { - _ = context.AllDoneSignal.AddParticipant(); - // & WAS SLOWER - // _ = ThreadPool.QueueUserWorkItem((_) => - _ = Task.Factory.StartNew(() => - { - try - { - if (context.ItemFoundSignal.IsSet) - { - return; - } - - var index = DoLongIndexOfParallel(list, itemRange, itemToFind, context.ItemFoundSignal); - if (index >= 0) - { - context.ItemFoundSignal.Set(); - _ = Interlocked.Exchange(ref context.FoundItemIndex, index); - } - } - catch (Exception e) - { - _ = Interlocked.Exchange(ref context.Exception, e); - } - finally - { - context.AllDoneSignal.RemoveParticipant(); - itemRange.Dispose(); - } - }); - - return !context.ItemFoundSignal.IsSet; - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) - { - Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); - int itemIndex, blockIndex; - for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) - { - itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); - if (itemIndex >= 0) - { - return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); - } - } - - if (blockIndex == lastBlockIndex) - { - itemIndex = nextItemIndex != 0 - ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) - : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); - } - else - { - return ItemNotFoundIndexLong; - } - - return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndexLong; + _lastBlockWithData = targetBlockIdx - (targetItemIdx > 0 ? 0 : 1); } /// @@ -482,54 +329,32 @@ namespace Recyclable.Collections // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] // & WAS SLOWER // [MethodImpl(MethodImplOptions.NoInlining)] - private void Iterate(in ParallelSynchronizationContext context, in T itemToFind) - { - // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. + //private static void Iterate(RecyclableList list, in ParallelSynchronizationContext context, in T itemToFind) + //{ + // // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. - // & WAS SLOWER without - ItemRange itemRange; - int blockIndex = 0; - // & WAS SLOWER - // int blockSize = _blockSize; - long itemIndex = 0; - long itemsCount = _longCount; - // & WAS SLOWER - // int blockSize = _blockSize; - // long step = list._longCountIndexOfStep; - // & WAS SLOWER - // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) - // & WAS SLOWER - // long longCountIndexOfStep = _longCountIndexOfStep; - while (itemsCount > _longCountIndexOfStep) - { - // At this point itemIndex is limited to blockSize range - int. - itemRange = ItemRangePool.Create(blockIndex, (int)itemIndex, _longCountIndexOfStep); - if (!ScheduleIndexOfTask(context, this, itemToFind, itemRange)) - { - break; - } + // // & WAS SLOWER without + // int blockIndex = 0; - itemsCount -= _longCountIndexOfStep; + // long itemsCount = list._longCount; + // // & WAS SLOWER + // // int blockSize = _blockSize; + // // & WAS SLOWER + // // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) + // while (itemsCount > list._blockSize) + // { + // // At this point itemIndex is limited to blockSize range - int. + // if (!ScheduleIndexOfTask(context, list, blockIndex, list._blockSize, itemToFind)) + // { + // break; + // } - // & WAS SLOWER - // blockIndex = (int)Math.DivRem(itemIndex + step, step, out itemIndex); - // & WAS SLOWER - // blockIndex += (int)((itemIndex + list._longCountIndexOfStep) >> list._blockSizePow2BitShift); - // itemIndex = (int)((itemIndex + list._longCountIndexOfStep) & list._blockSizeMinus1); + // itemsCount -= list._blockSize; + // blockIndex++; + // } - itemIndex += _longCountIndexOfStep; - while (itemIndex >= _blockSize) - { - blockIndex++; - itemIndex -= _blockSize; - } - } - - // & WAS SLOWER - // itemRange = new(blockIndex, (int)itemIndex, list._longCount - (blockIndex << list._blockSizePow2BitShift) - itemIndex); - itemRange = ItemRangePool.Create(blockIndex, (int)itemIndex, itemsCount); - _ = ScheduleIndexOfTask(context, this, itemToFind, itemRange); - } + // _ = ScheduleIndexOfTask(context, list, blockIndex, (int)itemsCount, itemToFind); + //} [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) @@ -648,9 +473,12 @@ namespace Recyclable.Collections _nextItemBlockIndex++; _nextItemIndex = 0; } + else if (_nextItemIndex == 1) + { + _lastBlockWithData++; + } _longCount++; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); } public void AddRange(in T[] items) @@ -693,8 +521,8 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } public void AddRange(RecyclableArrayList items) @@ -733,8 +561,8 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } public void AddRange(RecyclableList items) @@ -794,6 +622,7 @@ namespace Recyclable.Collections } } + _lastBlockWithData = targetBlockIndex; if ((targetCapacity & _blockSizeMinus1) == 0) { _nextItemIndex = 0; @@ -806,7 +635,6 @@ namespace Recyclable.Collections } _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); } public void AddRange(List items) @@ -853,7 +681,7 @@ namespace Recyclable.Collections _nextItemBlockIndex = targetBlockIndex; _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _lastBlockWithData = targetBlockIndex - (_nextItemIndex > 0 ? 0 : 1); } public void AddRange(IList items) @@ -898,9 +726,9 @@ namespace Recyclable.Collections itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; _nextItemIndex = itemsSpan.Length; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } finally { @@ -976,8 +804,8 @@ namespace Recyclable.Collections _reservedBlockCount = 0; _nextItemBlockIndex = 0; _nextItemIndex = 0; + _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; _longCount = 0; - _longCountIndexOfStep = 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1010,17 +838,81 @@ namespace Recyclable.Collections public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(T item) => checked((int)DoIndexOfParallel(item)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public int IndexOf(T item) + { + if (_lastBlockWithData == 0) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + } + else if (_longCount == 0) + { + return RecyclableDefaults.ItemNotFoundIndex; + } + + int itemIndex = Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + if (itemIndex >= 0) + { + return itemIndex; + } + else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization) + { + return (int)RecyclableListIndexOfHelpers.IndexOfSequential(this, item); + } + else + { + // & WAS SLOWER + // long toSearch = _longCount - _blockSize; + if (_longCount > _blockSize) + { + itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize)); + return itemIndex >= 0 ? _blockSize + itemIndex : (int)RecyclableListIndexOfHelpers.IndexOfParallel(this, item); + } + else + { + return RecyclableDefaults.ItemNotFoundIndex; + } + } + } public void Insert(int index, T item) => throw new NotSupportedException(); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public long LongIndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndexLong - : _longCount <= _blockSize - ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) - : DoIndexOfParallel(item); + public long LongIndexOf(T item) + { + if (_lastBlockWithData == 0) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + } + else if (_longCount == 0) + { + return RecyclableDefaults.ItemNotFoundIndex; + } + + int itemIndex = Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + if (itemIndex >= 0) + { + return itemIndex; + } + else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization) + { + return RecyclableListIndexOfHelpers.IndexOfSequential(this, item); + } + else + { + // & WAS SLOWER + // long toSearch = _longCount - _blockSize; + if (_longCount > _blockSize) + { + itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize)); + return itemIndex >= 0 ? _blockSize + itemIndex : RecyclableListIndexOfHelpers.IndexOfParallel(this, item); + } + else + { + return RecyclableDefaults.ItemNotFoundIndex; + } + } + } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool Remove(T item) @@ -1029,7 +921,6 @@ namespace Recyclable.Collections if (index >= 0) { _longCount--; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1039,6 +930,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { @@ -1080,6 +975,8 @@ namespace Recyclable.Collections { _nextItemBlockIndex--; } + + _lastBlockWithData--; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -1091,7 +988,6 @@ namespace Recyclable.Collections } _longCount--; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1101,6 +997,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { @@ -1125,7 +1025,6 @@ namespace Recyclable.Collections } _longCount--; - _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1135,6 +1034,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { diff --git a/Recyclable.Collections/SearchConfiguration.cs b/Recyclable.Collections/SearchConfiguration.cs index 35c8a2c..622ae2a 100644 --- a/Recyclable.Collections/SearchConfiguration.cs +++ b/Recyclable.Collections/SearchConfiguration.cs @@ -8,7 +8,7 @@ namespace Recyclable.Collections public byte BlockSizePow2BitShift { get; init; } public long ItemsCount { get; init; } public int StartingBlockIndex { get; init; } - public long Step { get; init; } + public long BlockSize { get; init; } // public SearchConfiguration(TList list, in TItem itemToFind) // { @@ -23,7 +23,7 @@ namespace Recyclable.Collections BlockSizeMinus1 = list._blockSizeMinus1, BlockSizePow2BitShift = list._blockSizePow2BitShift, ItemsCount = list._longCount, - Step = list._longCountIndexOfStep + BlockSize = list._blockSize }; } } \ No newline at end of file diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs index 586fbae..57b4973 100644 --- a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs +++ b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs @@ -9,7 +9,7 @@ namespace Recyclable.CollectionsTests { [Theory] [MemberData(nameof(TestCases))] - public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, int StartingItemIndex, long ItemsCount)> expected) + public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, long ItemsCount)> expected) { // Prepare var mockList = new RecyclableList(); @@ -23,15 +23,15 @@ namespace Recyclable.CollectionsTests BlockSizeMinus1 = blockSize - 1, BlockSizePow2BitShift = blockSizePow2BitShift, ItemsCount = itemsCount, - Step = step, + BlockSize = step, }; - var actualItemRanges = new List<(int BlockIndex, int StartingItemIndex, long ItemsCount)>(); + var actualItemRanges = new List<(int BlockIndex, long ItemsCount)>(); // Act ItemRangesIterator.Iterate(synchronizationContext, search, (context, search, itemRange) => { - actualItemRanges.Add((itemRange.BlockIndex, itemRange.StartingItemIndex, itemRange.ItemsToSearchCount)); + actualItemRanges.Add((itemRange.BlockIndex, itemRange.ItemsToSearchCount)); return true; }); @@ -43,102 +43,102 @@ namespace Recyclable.CollectionsTests { new object[] { - 0, 128, 333, (long)(333 * 0.329), new(int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 128, 333, (long)(333 * 0.329), new(int BlockIndex, long ItemsCount)[] { - (0, 0, 109), - (0, 109, 109), - (1, 90, 109), - (2, 71, 6) + (0, 109), + (0, 109), + (1, 109), + (2, 6) } }, new object[] { - 0, 2, 10, 2, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 2, 10, 2, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 2), - (1, 0, 2), - (2, 0, 2), - (3, 0, 2), - (4, 0, 2), + (0, 2), + (1, 2), + (2, 2), + (3, 2), + (4, 2), } }, new object[] { - 0, 2, 10, 3, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 2, 10, 3, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 3), - (1, 1, 3), - (3, 0, 3), - (4, 1, 1), + (0, 3), + (1, 3), + (3, 3), + (4, 1), } }, new object[] { - 0, 1, 10, 3, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 1, 10, 3, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 3), - (3, 0, 3), - (6, 0, 3), - (9, 0, 1), + (0, 3), + (3, 3), + (6, 3), + (9, 1), } }, new object[] { - 0, 2, 10, 4, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 2, 10, 4, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 4), - (2, 0, 4), - (4, 0, 2), + (0, 4), + (2, 4), + (4, 2), } }, new object[] { - 0, 1, 10, 2, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 1, 10, 2, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 2), - (2, 0, 2), - (4, 0, 2), - (6, 0, 2), - (8, 0, 2), + (0, 2), + (2, 2), + (4, 2), + (6, 2), + (8, 2), } }, new object[] { - 0, 2, 10, 1, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 2, 10, 1, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 1), - (0, 1, 1), - (1, 0, 1), - (1, 1, 1), - (2, 0, 1), - (2, 1, 1), - (3, 0, 1), - (3, 1, 1), - (4, 0, 1), - (4, 1, 1), + (0, 1), + (0, 1), + (1, 1), + (1, 1), + (2, 1), + (2, 1), + (3, 1), + (3, 1), + (4, 1), + (4, 1), } }, new object[] { - 0, 1, 10, 1, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[] + 0, 1, 10, 1, new (int BlockIndex, long ItemsCount)[] { - (0, 0, 1), - (1, 0, 1), - (2, 0, 1), - (3, 0, 1), - (4, 0, 1), - (5, 0, 1), - (6, 0, 1), - (7, 0, 1), - (8, 0, 1), - (9, 0, 1), + (0, 1), + (1, 1), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 1), + (9, 1), } }, }; From 98658e73fa6cbf31a4a9c4013f1069235614b43f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 15:43:33 +0200 Subject: [PATCH 270/305] The no. of max items count limited due to very long running times. --- Recyclable.CollectionsTests/RecyclableListTestData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableListTestData.cs index ba0f50b..6163458 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableListTestData.cs @@ -17,7 +17,7 @@ namespace Recyclable.CollectionsTests public static readonly IEnumerable ItemsCountVariants = new long[] { 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 333, 512, 1_024, 8_192, 16_384, - 1_000_000 + 100_000 }; public static readonly IEnumerable BlockSizeVariants = new int[] From 643882fefa0156ab6c0b48b908a9acac80e00539 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 15:44:09 +0200 Subject: [PATCH 271/305] Tiny cleanup --- Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs b/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs index 042e49a..8d53a8d 100644 --- a/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs +++ b/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs @@ -4,6 +4,7 @@ namespace Recyclable.Collections.Parallel { internal bool _isSet; public bool IsSet => _isSet; + internal bool _returned; public ManualResetEventSlimmer() From 9ece5311294d65a45c2cd28847992c003c94c032 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 15:44:42 +0200 Subject: [PATCH 272/305] VS code tasks update --- .vscode/tasks.json | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index af5b07b..f921346 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,6 +2,7 @@ "version": "2.0.0", "tasks": [ { + "isBuildCommand": true, "label": "build debug", "command": "dotnet", "type": "process", @@ -9,7 +10,8 @@ "build", "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj", "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" + "/consoleloggerparameters:NoSummary", + "-f", "net7.0" ], "problemMatcher": "$msCompile" }, @@ -22,8 +24,8 @@ "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary", - "-c", - "Release" + "-c", "Release", + "-f", "net7.0" ], "problemMatcher": "$msCompile", "group": { @@ -54,15 +56,6 @@ "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj" ], "problemMatcher": "$msCompile" - }, - { - "type": "msbuild", - "problemMatcher": [ - "$msCompile" - ], - "group": "build", - "label": "Build: solution", - "detail": "Build the Recyclable.Collections.sln solution using dotnet build" } ] } \ No newline at end of file From 24c2491df0c9a778cf75993aa9e919c1ddbaab7f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 16:10:21 +0200 Subject: [PATCH 273/305] Cleanup of no longer needed classes. ParallelSynchronizationContext/-Pool renamed to IndexOfSynchronizationContext/-Pool. --- .../POC/WhenParallelSearchBenchmarks.cs | 73 ++++++++- Recyclable.Collections/ItemRange.cs | 30 ---- Recyclable.Collections/ItemRangesIterator.cs | 30 ---- ...xt.cs => IndexOfSynchronizationContext.cs} | 8 +- ...s => IndexOfSynchronizationContextPool.cs} | 6 +- Recyclable.Collections/Pools/ItemRangePool.cs | 26 ---- ...rs.cs => RecyclableList.IndexOfHelpers.cs} | 8 +- Recyclable.Collections/RecyclableList.cs | 8 +- .../ItemRangesIteratorTests.cs | 146 ------------------ 9 files changed, 80 insertions(+), 255 deletions(-) delete mode 100644 Recyclable.Collections/ItemRange.cs delete mode 100644 Recyclable.Collections/ItemRangesIterator.cs rename Recyclable.Collections/Parallel/{ParallelSynchronizationContext.cs => IndexOfSynchronizationContext.cs} (71%) rename Recyclable.Collections/Parallel/{ParallelSynchronizationContextPool.cs => IndexOfSynchronizationContextPool.cs} (73%) delete mode 100644 Recyclable.Collections/Pools/ItemRangePool.cs rename Recyclable.Collections/{RecyclableList.RecyclableListIndexOfHelpers.cs => RecyclableList.IndexOfHelpers.cs} (95%) delete mode 100644 Recyclable.CollectionsTests/ItemRangesIteratorTests.cs diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs index bad4c11..683dcdf 100644 --- a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -1,10 +1,11 @@ using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.ObjectPool; using MoreLinq; using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { - public enum WhenParallelSearchBenchmarkType + public enum WhenParallelSearchBenchmarkType { IndexOfSequentially, IndexOfParallel, @@ -13,6 +14,62 @@ namespace Recyclable.Collections.Benchmarks.POC [MemoryDiagnoser] public class WhenParallelSearchBenchmarks : PocBenchmarkBase { + #region ItemRange V2 + + private sealed class ItemRangeV2 + { + public int BlockIndex; + public long ItemsToSearchCount; + internal bool _returned; + + public ItemRangeV2() + { + } + + public ItemRangeV2(int blockIndex, long itemsToSearchCount) + { + BlockIndex = blockIndex; + ItemsToSearchCount = itemsToSearchCount; + } + + public void Dispose() + { + if (!_returned) + { + _returned = true; + ItemRangePoolV2.Shared.Return(this); + } + } + } + + #endregion + + #region ItemRangePoolV2 + + private static class ItemRangePoolV2 + { + private static readonly SpinLock _updateLock = new(false); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + public static ItemRangeV2 Create(int blockIndex, long itemsToSearchCount) + { + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + var itemRange = Shared.Get(); + if (lockTaken) + { + _updateLock.Exit(false); + } + + itemRange._returned = false; + itemRange.BlockIndex = blockIndex; + itemRange.ItemsToSearchCount = itemsToSearchCount; + return itemRange; + } + } + + #endregion + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32)] //public int NumberOfThreads { get; set; } = 3; @@ -66,7 +123,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static long DoLongIndexOfParallel(RecyclableList list, in long itemToFind, ItemRange searchInfo, ManualResetEventSlim itemFoundSignal) + private static long DoLongIndexOfParallel(RecyclableList list, in long itemToFind, ItemRangeV2 searchInfo, ManualResetEventSlim itemFoundSignal) { int index; int blockSize = list.BlockSize; @@ -248,13 +305,13 @@ namespace Recyclable.Collections.Benchmarks.POC } protected override Action? GetTestMethod(WhenParallelSearchBenchmarkType benchmarkType) => BenchmarkType switch - { - WhenParallelSearchBenchmarkType.IndexOfSequentially => IndexOfSequentially, - WhenParallelSearchBenchmarkType.IndexOfParallel => IndexOfParallel, - _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), - }; + { + WhenParallelSearchBenchmarkType.IndexOfSequentially => IndexOfSequentially, + WhenParallelSearchBenchmarkType.IndexOfParallel => IndexOfParallel, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; - public override void Cleanup() + public override void Cleanup() { _testData = default; base.Cleanup(); diff --git a/Recyclable.Collections/ItemRange.cs b/Recyclable.Collections/ItemRange.cs deleted file mode 100644 index 37a31fa..0000000 --- a/Recyclable.Collections/ItemRange.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Recyclable.Collections.Pools; - -namespace Recyclable.Collections -{ - public sealed class ItemRange - { - public int BlockIndex; - public long ItemsToSearchCount; - internal bool _returned; - - public ItemRange() - { - } - - public ItemRange(int blockIndex, long itemsToSearchCount) - { - BlockIndex = blockIndex; - ItemsToSearchCount = itemsToSearchCount; - } - - public void Dispose() - { - if (!_returned) - { - _returned = true; - ItemRangePool.Shared.Return(this); - } - } - } -} diff --git a/Recyclable.Collections/ItemRangesIterator.cs b/Recyclable.Collections/ItemRangesIterator.cs deleted file mode 100644 index 9a150aa..0000000 --- a/Recyclable.Collections/ItemRangesIterator.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Runtime.CompilerServices; -using Recyclable.Collections.Parallel; - -namespace Recyclable.Collections -{ - public static class ItemRangesIterator - { - [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] - public static void Iterate(in ParallelSynchronizationContext context, in SearchConfiguration search, Func, ItemRange, bool> action) - { - ItemRange itemRange; - int blockIndex = search.StartingBlockIndex; - long itemsCount = search.ItemsCount; - while (itemsCount > search.BlockSize) - { - itemRange = new(blockIndex, search.BlockSize); - if (!action(context, search, itemRange)) - { - break; - } - - itemsCount -= search.BlockSize; - blockIndex++; - } - - itemRange = new(blockIndex, itemsCount); - _ = action(context, search, itemRange); - } - } -} diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs similarity index 71% rename from Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs rename to Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs index 2a2fc31..91f0c25 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections.Parallel { - public sealed class ParallelSynchronizationContext : IDisposable + public sealed class IndexOfSynchronizationContext : IDisposable { internal bool _isItemFound; @@ -9,12 +9,12 @@ namespace Recyclable.Collections.Parallel public Exception? Exception; internal bool _returned; - public ParallelSynchronizationContext() + public IndexOfSynchronizationContext() { AllDoneSignal = new(0); } - public ParallelSynchronizationContext(int participantCount) + public IndexOfSynchronizationContext(int participantCount) { AllDoneSignal = new(participantCount); } @@ -31,7 +31,7 @@ namespace Recyclable.Collections.Parallel if (!_returned) { _returned = true; - ParallelSynchronizationContextPool.Shared.Return(this); + IndexOfSynchronizationContextPool.Shared.Return(this); } else { diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs similarity index 73% rename from Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs rename to Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs index aa0142b..9d7f65b 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs @@ -2,12 +2,12 @@ using Microsoft.Extensions.ObjectPool; namespace Recyclable.Collections.Parallel { - public static class ParallelSynchronizationContextPool + public static class IndexOfSynchronizationContextPool { private static readonly SpinLock _updateLock = new(false); - public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - public static ParallelSynchronizationContext GetWithOneParticipant() + public static IndexOfSynchronizationContext GetWithOneParticipant() { bool lockTaken = false; _updateLock.Enter(ref lockTaken); diff --git a/Recyclable.Collections/Pools/ItemRangePool.cs b/Recyclable.Collections/Pools/ItemRangePool.cs deleted file mode 100644 index 6b332fe..0000000 --- a/Recyclable.Collections/Pools/ItemRangePool.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.Extensions.ObjectPool; - -namespace Recyclable.Collections.Pools -{ - public static class ItemRangePool - { - private static readonly SpinLock _updateLock = new(false); - public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - - public static ItemRange Create(int blockIndex, long itemsToSearchCount) - { - bool lockTaken = false; - _updateLock.Enter(ref lockTaken); - var itemRange = Shared.Get(); - if (lockTaken) - { - _updateLock.Exit(false); - } - - itemRange._returned = false; - itemRange.BlockIndex = blockIndex; - itemRange.ItemsToSearchCount = itemsToSearchCount; - return itemRange; - } - } -} diff --git a/Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs b/Recyclable.Collections/RecyclableList.IndexOfHelpers.cs similarity index 95% rename from Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs rename to Recyclable.Collections/RecyclableList.IndexOfHelpers.cs index 03b00bc..2026354 100644 --- a/Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs +++ b/Recyclable.Collections/RecyclableList.IndexOfHelpers.cs @@ -5,12 +5,12 @@ namespace Recyclable.Collections { public partial class RecyclableList { - public static class RecyclableListIndexOfHelpers + public static class IndexOfHelpers { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] // [MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static void ScheduleIndexOfTask(ParallelSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) + private static void ScheduleIndexOfTask(IndexOfSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) { _ = context.AllDoneSignal.AddParticipant(); // & WAS SLOWER @@ -123,7 +123,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] // [MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static void ScheduleIndexOfTaskSingle(ParallelSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) + private static void ScheduleIndexOfTaskSingle(IndexOfSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) { _ = context.AllDoneSignal.AddParticipant(); // & WAS SLOWER @@ -172,7 +172,7 @@ namespace Recyclable.Collections //} //using var context = new ParallelSynchronizationContext(1); - ParallelSynchronizationContext context = ParallelSynchronizationContextPool.GetWithOneParticipant(); + IndexOfSynchronizationContext context = IndexOfSynchronizationContextPool.GetWithOneParticipant(); // (long)(_longCount * 0.329) => most efficient step, based on benchmarks //Iterate(this, context, item); diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 22713b4..c397ea1 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -857,7 +857,7 @@ namespace Recyclable.Collections } else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization) { - return (int)RecyclableListIndexOfHelpers.IndexOfSequential(this, item); + return (int)IndexOfHelpers.IndexOfSequential(this, item); } else { @@ -866,7 +866,7 @@ namespace Recyclable.Collections if (_longCount > _blockSize) { itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize)); - return itemIndex >= 0 ? _blockSize + itemIndex : (int)RecyclableListIndexOfHelpers.IndexOfParallel(this, item); + return itemIndex >= 0 ? _blockSize + itemIndex : (int)IndexOfHelpers.IndexOfParallel(this, item); } else { @@ -896,7 +896,7 @@ namespace Recyclable.Collections } else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization) { - return RecyclableListIndexOfHelpers.IndexOfSequential(this, item); + return IndexOfHelpers.IndexOfSequential(this, item); } else { @@ -905,7 +905,7 @@ namespace Recyclable.Collections if (_longCount > _blockSize) { itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize)); - return itemIndex >= 0 ? _blockSize + itemIndex : RecyclableListIndexOfHelpers.IndexOfParallel(this, item); + return itemIndex >= 0 ? _blockSize + itemIndex : IndexOfHelpers.IndexOfParallel(this, item); } else { diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs deleted file mode 100644 index 57b4973..0000000 --- a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs +++ /dev/null @@ -1,146 +0,0 @@ -using FluentAssertions; -using Recyclable.Collections; -using Recyclable.Collections.Parallel; -using System.Numerics; - -namespace Recyclable.CollectionsTests -{ - public class ItemRangesIteratorTests - { - [Theory] - [MemberData(nameof(TestCases))] - public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, long ItemsCount)> expected) - { - // Prepare - var mockList = new RecyclableList(); - byte blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); - - var synchronizationContext = new ParallelSynchronizationContext(1); - var search = new SearchConfiguration, long>() - { - List = mockList, - ItemToFind = 0, - BlockSizeMinus1 = blockSize - 1, - BlockSizePow2BitShift = blockSizePow2BitShift, - ItemsCount = itemsCount, - BlockSize = step, - }; - - var actualItemRanges = new List<(int BlockIndex, long ItemsCount)>(); - - // Act - ItemRangesIterator.Iterate(synchronizationContext, search, (context, search, itemRange) => - { - actualItemRanges.Add((itemRange.BlockIndex, itemRange.ItemsToSearchCount)); - return true; - }); - - // Validate - _ = actualItemRanges.Should().HaveCount(expected.Count()).And.ContainInConsecutiveOrder(expected); - } - - public static IEnumerable TestCases => new[] - { - new object[] - { - 0, 128, 333, (long)(333 * 0.329), new(int BlockIndex, long ItemsCount)[] - { - (0, 109), - (0, 109), - (1, 109), - (2, 6) - } - }, - - new object[] - { - 0, 2, 10, 2, new (int BlockIndex, long ItemsCount)[] - { - (0, 2), - (1, 2), - (2, 2), - (3, 2), - (4, 2), - } - }, - - new object[] - { - 0, 2, 10, 3, new (int BlockIndex, long ItemsCount)[] - { - (0, 3), - (1, 3), - (3, 3), - (4, 1), - } - }, - - new object[] - { - 0, 1, 10, 3, new (int BlockIndex, long ItemsCount)[] - { - (0, 3), - (3, 3), - (6, 3), - (9, 1), - } - }, - - new object[] - { - 0, 2, 10, 4, new (int BlockIndex, long ItemsCount)[] - { - (0, 4), - (2, 4), - (4, 2), - } - }, - - new object[] - { - 0, 1, 10, 2, new (int BlockIndex, long ItemsCount)[] - { - (0, 2), - (2, 2), - (4, 2), - (6, 2), - (8, 2), - } - }, - - new object[] - { - 0, 2, 10, 1, new (int BlockIndex, long ItemsCount)[] - { - (0, 1), - (0, 1), - (1, 1), - (1, 1), - (2, 1), - (2, 1), - (3, 1), - (3, 1), - (4, 1), - (4, 1), - } - }, - - new object[] - { - 0, 1, 10, 1, new (int BlockIndex, long ItemsCount)[] - { - (0, 1), - (1, 1), - (2, 1), - (3, 1), - (4, 1), - (5, 1), - (6, 1), - (7, 1), - (8, 1), - (9, 1), - } - }, - }; - } -} From a7f3ee50ed3a39628a6e126a4b39359b5d2544b7 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 18:30:18 +0200 Subject: [PATCH 274/305] README.md updated. Initial milestones defined. --- README.md | 129 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index cad1ada..d13719c 100644 --- a/README.md +++ b/README.md @@ -2,34 +2,107 @@ `Recyclable.Collections` project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements `IList`'s interface and is targeted as direct replacements of `List`, `SortableList`, `PriorityQueue` & similar. ## Included -* `RecyclableArrayList` * `RecyclableList` -* `RecyclableSortableList` - * Range: `long` - * Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` -* `RecyclableQueue` - * Range: `long` - * Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` -* `RecyclableStack` - * Range: `long` - * Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` +* `RecyclableLongList` + +# Milestones +1. ✅ Create basic classes + 1. ✅ `RecyclableList` + 1. ✅ `RecyclableLongList` + 1. 🅿️ `RecyclableQueue` + 1. 🅿️ `RecyclableSortedList` + 1. 🅿️ `RecyclableStack` + 1. 🅿️ `RecyclableUnorderedList` +1. ✅ Create basic unit tests + 1. ✅ `RecyclableList` + 1. ✅ `RecyclableLongList` + 1. 🅿️ `RecyclableQueue` + 1. 🅿️ `RecyclableSortedList` + 1. 🅿️ `RecyclableStack` + 1. 🅿️ `RecyclableUnorderedList` +1. ✅ Optimize `RecyclableList` + 1. ✅ `Add` + 1. ✅ `AddRange` + 1. ✅ when source is `array` + 1. ✅ when source is `List` + 1. ✅ when source is `IList` + 1. ✅ when source is `RecyclableList` + 1. ✅ when source is `RecyclableLongList` + 1. ✅ when source is `IEnumerable` + 1. ✅ when sorce has non-enumerated count + 1. ✅ `Clear` + 1. ✅ `Contains` + 1. ✅ `CopyTo` + 1. ✅ `EnsureCapacity` + 1. 🅿️ `GetEnumerator` + 1. ✅ `IndexOf` + 1. ✅ `Insert` + 1. ✅ `Remove` + 1. ✅ `RemoveAt` + 1. ✅ `Resize` + 1. ✅ `this[int index]` +1. ✅ Port `RecyclableList` implementation to `RecyclableLongList` +1. Optimize `RecyclableLongList` + 1. ✅ `Add` + 1. ✅ `AddRange` + 1. ✅ when source is `array` + 1. ✅ when source is `List` + 1. ✅ when source is `IList` + 1. ✅ when source is `RecyclableList` + 1. ✅ when source is `RecyclableLongList` + 1. ✅ when source is `IEnumerable` + 1. ✅ when sorce has non-enumerated count + 1. ✅ `Clear` + 1. ✅ `Contains` + 1. ✅ `CopyTo` + 1. ✅ `EnsureCapacity` + 1. 🅿️ `GetEnumerator` + 1. 👉 `IndexOf` + 1. 👉 `LongIndexOf` + 1. ✅ `Insert` + 1. ✅ `Remove` + 1. ✅ `RemoveAt(int index)` + 1. ✅ `RemoveAt(long index)` + 1. ✅ `RemoveAt` + 1. ✅ `Resize` + 1. ✅ `this[int index]` + 1. ✅ `this[long index]` +1. 🅿️ Rename `RecyclableList` to `RecyclableLongList` +1. 🅿️ Rename `RecyclableArrayList` to `RecyclableList` +1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableList` +1. 🅿️ Implement `List` interfaces + 1. 🅿️ `RecyclableList` + 1. 🅿️ `RecyclableLongList` + 1. 🅿️ `RecyclableQueue` + 1. 🅿️ `RecyclableSortedList` + 1. 🅿️ `RecyclableStack` + 1. 🅿️ `RecyclableUnorderedList` +1. 🅿️ Add support for `ReadOnlySpan` + 1. 🅿️ `RecyclableList` + 1. 🅿️ `RecyclableLongList` + 1. 🅿️ `RecyclableQueue` + 1. 🅿️ `RecyclableSortedList` + 1. 🅿️ `RecyclableStack` + 1. 🅿️ `RecyclableUnorderedList` # Characteristicts of the classes ## Common * All classes implement `IDisposable` interface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missing `using` clause. But considering that `Dispose` will be called by `GC` anyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition of `using`. * Memory blocks are created in 2 ways, depending on `blockSize` value - * when `blockSize < 100` ⇨ blocks are created as regular `arrays` - * when `blockSize >= 100` ⇨ blocks are taken from and returned to `ArrayPool.Shared`, when blocks are removed + * when `blockSize < 128` ⇨ blocks are created as regular `arrays`. + * when `blockSize >= 128` ⇨ blocks are taken from and returned to `ArrayPool.Shared`, when blocks are removed. + * maximum `blockSize` value is `2_147_483_591`, which corresponds more or less to the maximum array size. + * `blockSize` MUST be a power of 2. It will be rounded up to the closest power of 2, if needed. That is due to high performance gain on some operations, like the calculation of item index in a block. * Array pools are shared between the same `T` type. I.e. `List` will use a different pool from `List` and so on. For high concurrency environments you may want to provide your own pools, when this feature becomes available in the upcoming releases. * Trying to access `this[int index]`, `this[long index]`, `Count`, `LongCount` etc. when `Capacity == 0` will raise `NullReferenceException`. This is by design to remove all non-critical code from the constructor. -## `RecyclableArrayList` +## `RecyclableList` * Range: `int` * Interfaces: `IList`, `IEnumerable`, `IDisposable` This is the direct equivalent of `List` class, except that the arrays are taken from the shared pool, instead of directly being allocated. It proves to out perform `List` & the standard arrays in certains operations and is fairly close in others. But they may perform worse in certain scenarios. If you're interested in details, please refer to benchmarks. -## `RecyclableList`, `RecyclableSortableList`, `RecyclableQueue`, `RecyclableStack` +## `RecyclableLongList`, `RecyclableSortableList`, `RecyclableQueue`, `RecyclableStack` * Range: `long` * Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` * All classes provide large storage capabilities, in practice limited only by the memory available in your system. @@ -62,12 +135,16 @@ 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 +# Auto-properties, new language features & code style 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. +Similarily, you will find places in code with hints or warnings disabled. The decision was always driven by performance measures. The use case & business logic was carefully reviewed to ensure it's safe to take shortcuts, before making the change. If you run into failing scenario, please feel free to create a work item against it and/or file a PR to fix it. + +Finally, you'll find places with code commented out. That's to easily track what has been tried & what proved to performe worse. + +At any time, if you know & can show 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. @@ -75,7 +152,7 @@ The `public static` members of the classes are thread safe. Any instance members # Use All the classes included in this package are meant to be direct replacements of their corresponding system classes. * `IList` ⇨ `ILongList` -* `List` ⇨ `RecyclableList` +* `List` ⇨ `RecyclableLongList` * `Queue` ⇨ `RecyclableQueue` * `PriorityQueue` ⇨ `RecyclableSortedList` * `SortedList` ⇨ `RecyclableSortedList` @@ -83,12 +160,12 @@ All the classes included in this package are meant to be direct replacements of 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` +It's the closest equivalent to `List` in regards to functionality. It supports items' removal & delivers the best performance in most scenarios. 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 `RecyclableList` or it's `long`-range version - `RecyclableLongList`. By doing that you can switch between them with highly limited changes in code. + +## `RecyclableLongList` ```CSharp public void RecyclableListExample() @@ -96,7 +173,7 @@ public void RecyclableListExample() int[] testNumbers = new[] { 1, 2, 2, 3 }; // Create - using var list = new RecyclableList(); + using var list = new RecyclableLongList(); // Add items foreach (var index in testNumbers) @@ -124,9 +201,9 @@ public void RecyclableListExample() list.Clear(); } ``` -## `RecyclableArrayList` +## `RecyclableList` -## `RecyclableArrayList` +## `RecyclableList` -## `RecyclableArrayList` +## `RecyclableList` From 8750b217a7751a7dc84268eb7f6016c03e77e69e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 18:47:00 +0200 Subject: [PATCH 275/305] Readme improvements --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d13719c..10a410b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ 1. ✅ when source is `RecyclableList` 1. ✅ when source is `RecyclableLongList` 1. ✅ when source is `IEnumerable` - 1. ✅ when sorce has non-enumerated count + 1. ✅ when source has non-enumerated count 1. ✅ `Clear` 1. ✅ `Contains` 1. ✅ `CopyTo` @@ -51,7 +51,7 @@ 1. ✅ when source is `RecyclableList` 1. ✅ when source is `RecyclableLongList` 1. ✅ when source is `IEnumerable` - 1. ✅ when sorce has non-enumerated count + 1. ✅ when source has non-enumerated count 1. ✅ `Clear` 1. ✅ `Contains` 1. ✅ `CopyTo` @@ -85,7 +85,7 @@ 1. 🅿️ `RecyclableStack` 1. 🅿️ `RecyclableUnorderedList` -# Characteristicts of the classes +# Characteristics of the classes ## Common * All classes implement `IDisposable` interface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missing `using` clause. But considering that `Dispose` will be called by `GC` anyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition of `using`. @@ -100,15 +100,16 @@ ## `RecyclableList` * Range: `int` * Interfaces: `IList`, `IEnumerable`, `IDisposable` -This is the direct equivalent of `List` class, except that the arrays are taken from the shared pool, instead of directly being allocated. It proves to out perform `List` & the standard arrays in certains operations and is fairly close in others. But they may perform worse in certain scenarios. If you're interested in details, please refer to benchmarks. +This is the direct equivalent of `List` class, except that the arrays are taken from the shared pool, instead of directly being allocated. It proves to out perform `List` & the standard arrays in certain operations and is fairly close in others. But they may perform worse in certain scenarios. If you're interested in details, please refer to benchmarks. ## `RecyclableLongList`, `RecyclableSortableList`, `RecyclableQueue`, `RecyclableStack` * Range: `long` * Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` * All classes provide large storage capabilities, in practice limited only by the memory available in your system. * All data is stored in blocks, with the provided `int blockSize` or the default, currently being `10,240` items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list. -* There is no memory copying to increase the capacity of the recyclable classes, anymore. If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes. -* `Insert`, `Remove` & `RemoveAt` methods raise `NotSupportedException`. This is by design in the current version, because it involves performance degradadion. I'm planning works around this in the upcoming weeks. If you need that sooner, you're welcomed to file a PR with the proposed changes. +* There is no memory copying to increase the capacity of the recyclable classes, unless the no. of items exceeds the no. of allocated blocks. In that case, a new array of blocks is created to accommodate . If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes. +* ⚠️ `IndexOf` is not guaranteed to return the index of the first item found. In most cases it will, but there may be scenarios when the index of subsequent items on the list is found. That may happen when the no. of items equals to or exceeds 786_432. In that case `IndexOf` will parallelize the search using tasks. +* `Insert`, `Remove` & `RemoveAt` methods raise `NotSupportedException`. This is by design in the current version, because it involves performance degradation. I'm planning works around this in the upcoming weeks. If you need that sooner, you're welcomed to file a PR with the proposed changes. # Architecture Decisions The architecture of the classes included in this project is driven primarily by performance & memory footprints. The following decisions were made to support it. @@ -121,7 +122,7 @@ Having a common base class for all the classes looks very attractive. But it is 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 +## Protection against incorrect 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. This decision is driven by performance measures. Considering wide use of the lists on the hot-paths, each additional check & operation starts counting. I've decided to eliminate all the checks for the sake of performance. If you feel that you need additional protection, feel free to inherit or reuse any of the provided classes to provide that. @@ -140,14 +141,14 @@ When you look at the code you may note, that it looks like in early c# days. Whe 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. -Similarily, you will find places in code with hints or warnings disabled. The decision was always driven by performance measures. The use case & business logic was carefully reviewed to ensure it's safe to take shortcuts, before making the change. If you run into failing scenario, please feel free to create a work item against it and/or file a PR to fix it. +Similarly, you will find places in code with hints or warnings disabled. The decision was always driven by performance measures. The use case & business logic was carefully reviewed to ensure it's safe to take shortcuts, before making the change. If you run into failing scenario, please feel free to create a work item against it and/or file a PR to fix it. -Finally, you'll find places with code commented out. That's to easily track what has been tried & what proved to performe worse. +Finally, you'll find places with code commented out. That's to easily track what has been tried & what proved to perform worse. At any time, if you know & can show 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. +The `public static` members of the classes are thread safe. Any instance members are not guaranteed 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. From 3328b256ffbc4eead00408ca861c5068fae88ca6 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 19:15:13 +0200 Subject: [PATCH 276/305] Milestones updated --- README.md | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 10a410b..f3d59f0 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ 1. ✅ when source has non-enumerated count 1. ✅ `Clear` 1. ✅ `Contains` - 1. ✅ `CopyTo` + 1. 🅿️ `CopyTo` 1. ✅ `EnsureCapacity` 1. 🅿️ `GetEnumerator` 1. 👉 `IndexOf` @@ -67,16 +67,38 @@ 1. ✅ `Resize` 1. ✅ `this[int index]` 1. ✅ `this[long index]` -1. 🅿️ Rename `RecyclableList` to `RecyclableLongList` -1. 🅿️ Rename `RecyclableArrayList` to `RecyclableList` -1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableList` -1. 🅿️ Implement `List` interfaces - 1. 🅿️ `RecyclableList` - 1. 🅿️ `RecyclableLongList` - 1. 🅿️ `RecyclableQueue` - 1. 🅿️ `RecyclableSortedList` - 1. 🅿️ `RecyclableStack` - 1. 🅿️ `RecyclableUnorderedList` + 1. 🅿️ Rename `RecyclableList` to `RecyclableLongList` + 1. 🅿️ Rename `RecyclableArrayList` to `RecyclableList` + 1. 🅿️ Implement `List` interfaces + 1. 🅿️ `ICollection` + 1. 🅿️ `IEnumerable` + 1. 🅿️ `IEnumerable` + 1. 🅿️ `IList` + 1. 🅿️ `IReadOnlyCollection` + 1. 🅿️ `IReadOnlyList` + 1. 🅿️ `ICollection` + 1. 🅿️ `IList` + 1. Make sure that `NeedsClearing` is used & items are cleared in + 1. 🅿️ `Clear` + 1. 🅿️ `Dispose` + 1. 🅿️ `Remove` + 1. 🅿️ `RemoveAt` + 1. 🅿️ `RemoveBlock` + 1. 🅿️ Replace `Math` class usages with `if` statements + 1. 🅿️ Replace `a - b > 0` & `a - b < 0` comparisons with `a > b` & `a < b` + 1. 🅿️ Replace `a + b > 0` & `a + b < 0` comparisons with `a > b` & `a < b` + 1. 🅿️ Replace `a / b` & `a * b` calculations with equivalents, where possible + 1. 🅿️ Replace virtual calls with static calls + 1. 🅿️ Replace `blockSize` sums by powers of 2, minus 1 + 1. 🅿️ Remove type castings, if possible + 1. 🅿️ Add type casting to `long` for `<<` & `>>` operations, where required + 1. 🅿️ Make type castings `checked` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableList` + 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field +1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableQueue` +1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableStack` +1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableSortedList` +1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableUnorderedList` 1. 🅿️ Add support for `ReadOnlySpan` 1. 🅿️ `RecyclableList` 1. 🅿️ `RecyclableLongList` From 6a9836b8ff1a19cab354aa4257ccdc09b2017de5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 19:20:09 +0200 Subject: [PATCH 277/305] Milestones updated --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f3d59f0..afad65d 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ 1. ✅ `this[long index]` 1. 🅿️ Rename `RecyclableList` to `RecyclableLongList` 1. 🅿️ Rename `RecyclableArrayList` to `RecyclableList` + 1. 🅿️ Convert `_memoryBlocks` to `Array` to allow `ulong` lengths + 1. 🅿️ Convert block indexes from `int` to `ulong` or `long` + 1. 🅿️ Add support for `ReadOnlySpan` 1. 🅿️ Implement `List` interfaces 1. 🅿️ `ICollection` 1. 🅿️ `IEnumerable` @@ -99,7 +102,6 @@ 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableStack` 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableSortedList` 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableUnorderedList` -1. 🅿️ Add support for `ReadOnlySpan` 1. 🅿️ `RecyclableList` 1. 🅿️ `RecyclableLongList` 1. 🅿️ `RecyclableQueue` From 1af65c67c19b8e2e890c322404bf518e1d046324 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 19:25:55 +0200 Subject: [PATCH 278/305] Milestones updated --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index afad65d..8eace7f 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,11 @@ 1. ✅ `Resize` 1. ✅ `this[int index]` 1. ✅ `this[long index]` + 1. 🅿️ Hide not ready classes + 1. 🅿️ `RecyclableQueue` + 1. 🅿️ `RecyclableSortedList` + 1. 🅿️ `RecyclableStack` + 1. 🅿️ `RecyclableUnorderedList` 1. 🅿️ Rename `RecyclableList` to `RecyclableLongList` 1. 🅿️ Rename `RecyclableArrayList` to `RecyclableList` 1. 🅿️ Convert `_memoryBlocks` to `Array` to allow `ulong` lengths @@ -108,6 +113,12 @@ 1. 🅿️ `RecyclableSortedList` 1. 🅿️ `RecyclableStack` 1. 🅿️ `RecyclableUnorderedList` +1. 🅿️ Optimize `OneSizeArrayPool` + 1. 🅿️ Review locks + 1. 🅿️ Measure multi-threading performance +1. 🅿️ Optimize `RecyclableArrayPool` + 1. 🅿️ Review locks + 1. 🅿️ Measure multi-threading performance # Characteristics of the classes From 0894dbd4bf7a1411c75536123e3c06cc5b507051 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 19:50:09 +0200 Subject: [PATCH 279/305] Milestones updated --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8eace7f..379d601 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ 1. 🅿️ Make type castings `checked` 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableList` 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field + 1. 🅿️ Cleanup `RecyclableLongListExtensions` + 1. 🅿️ Convert generic methods to non-generic 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableQueue` 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableStack` 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableSortedList` From 9429218c6294966cc24a3b727fda89a59f137e24 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 20:09:38 +0200 Subject: [PATCH 280/305] RecyclableList renamed to RecyclableLongList. --- .../Benchmarks/Add.cs | 4 +- .../Benchmarks/AddRangeWhenSourceIsArray.cs | 4 +- .../AddRangeWhenSourceIsIEnumerable.cs | 4 +- .../Benchmarks/AddRangeWhenSourceIsIList.cs | 10 +- .../Benchmarks/AddRangeWhenSourceIsList.cs | 4 +- .../AddRangeWhenSourceIsSameType.cs | 4 +- .../Benchmarks/AddRange_WithCapacity.cs | 4 +- .../Benchmarks/Add_WithCapacity.cs | 4 +- .../Benchmarks/Contains_FirstItems.cs | 4 +- .../Benchmarks/Contains_LastItems.cs | 4 +- .../Benchmarks/Count.cs | 4 +- .../Benchmarks/Create.cs | 4 +- .../Benchmarks/Create_WithCapacity.cs | 4 +- .../Benchmarks/GetItem..cs | 20 +-- .../Benchmarks/IndexOf_BestAndWorstCases.cs | 8 +- .../Benchmarks/IndexOf_FirstItems.cs | 8 +- .../Benchmarks/IndexOf_LastItems.cs | 8 +- .../Benchmarks/LongCount.cs | 4 +- .../RecyclableCollections.Benchmarks.cs | 4 +- .../Benchmarks/RemoveAt_FirstItems.cs | 4 +- .../Benchmarks/RemoveAt_LastItems.cs | 4 +- .../Benchmarks/Remove_FirstItems.cs | 4 +- .../Benchmarks/Remove_LastItems.cs | 4 +- .../Benchmarks/SetItem.cs | 4 +- .../POC/RecyclableListPoc.Benchmarks.cs | 157 ------------------ ...eLongListPoc.Benchmarks.EnsureCapacity.cs} | 106 ++++++------ ...cyclableLongListPoc.Benchmarks.IndexOf.cs} | 148 ++++++++--------- .../POC/RecyclableLongListPoc.Benchmarks.cs | 157 ++++++++++++++++++ ...cyclableLongListPos.Benchmarks.IndexOf.txt | 26 +++ .../POC/WhenParallelSearchBenchmarks.cs | 6 +- Recyclable.Collections.Benchmarks/Program.cs | 6 +- .../RecyclableBenchmarkBase.cs | 18 +- .../Results/AllInOneBenchmarks.txt | 56 +++---- Recyclable.Collections/RecyclableArrayList.cs | 6 +- ...s => RecyclableLongList.IndexOfHelpers.cs} | 40 +++-- ...ecyclableList.cs => RecyclableLongList.cs} | 28 ++-- ...ons.cs => RecyclableLongListExtensions.cs} | 12 +- .../RecyclableSortedList.cs | 2 +- Recyclable.Collections/RecyclableStack.cs | 4 +- Recyclable.Collections/SearchConfiguration.cs | 2 +- .../RecyclableArrayListTests.cs | 60 +++---- ...tData.cs => RecyclableLongListTestData.cs} | 8 +- ...istTests.cs => RecyclableLongListTests.cs} | 128 +++++++------- .../RecyclableQueueTests.cs | 4 +- .../RecyclableStackTests.cs | 4 +- 45 files changed, 576 insertions(+), 532 deletions(-) delete mode 100644 Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs rename Recyclable.Collections.Benchmarks/POC/{RecyclableListPoc.Benchmarks.EnsureCapacity.cs => RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs} (94%) rename Recyclable.Collections.Benchmarks/POC/{RecyclableListPoc.Benchmarks.IndexOf.cs => RecyclableLongListPoc.Benchmarks.IndexOf.cs} (95%) create mode 100644 Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/POC/RecyclableLongListPos.Benchmarks.IndexOf.txt rename Recyclable.Collections/{RecyclableList.IndexOfHelpers.cs => RecyclableLongList.IndexOfHelpers.cs} (88%) rename Recyclable.Collections/{RecyclableList.cs => RecyclableLongList.cs} (95%) rename Recyclable.Collections/{RecyclableListExtensions.cs => RecyclableLongListExtensions.cs} (88%) rename Recyclable.CollectionsTests/{RecyclableListTestData.cs => RecyclableLongListTestData.cs} (91%) rename Recyclable.CollectionsTests/{RecyclableListTests.cs => RecyclableLongListTests.cs} (69%) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs index 32173a2..12778f3 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs @@ -37,11 +37,11 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableList_Add() + public void RecyclableLongList_Add() { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(); + using var list = new RecyclableLongList(); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs index 5bc8b09..a5bf6e3 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs @@ -25,10 +25,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableList_AddRangeWhenSourceIsArray() + public void RecyclableLongList_AddRangeWhenSourceIsArray() { var data = TestObjects; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs index 51866dd..c212673 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs @@ -25,10 +25,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableList_AddRangeWhenSourceIsIEnumerable() + public void RecyclableLongList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs index 15ea875..c734da0 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs @@ -6,7 +6,7 @@ namespace Recyclable.Collections.Benchmarks { public void List_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsRecyclableList; + var data = (IList)TestObjectsAsRecyclableLongList; var list = new List(); list.AddRange(data); } @@ -20,15 +20,15 @@ namespace Recyclable.Collections.Benchmarks public void RecyclableArrayList_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsRecyclableList; + var data = (IList)TestObjectsAsRecyclableLongList; using var list = new RecyclableArrayList(); list.AddRange(data); } - public void RecyclableList_AddRangeWhenSourceIsIList() + public void RecyclableLongList_AddRangeWhenSourceIsIList() { - var data = (IList)TestObjectsAsRecyclableList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var data = (IList)TestObjectsAsRecyclableLongList; + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs index f1c07d2..0ca986c 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs @@ -25,10 +25,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableList_AddRangeWhenSourceIsList() + public void RecyclableLongList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs index 3eed14a..c33a4e5 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs @@ -18,10 +18,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableList_AddRangeWhenSourceIsRecyclableArrayList() + public void RecyclableLongList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs index 1694833..37fa179 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs @@ -28,11 +28,11 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableList_AddRange_WithCapacity() + public void RecyclableLongList_AddRange_WithCapacity() { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: dataCount); list.AddRange(data); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs index d727255..9734b20 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs @@ -37,11 +37,11 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableList_Add_WithCapacity() + public void RecyclableLongList_Add_WithCapacity() { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: dataCount); + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs index bdfa8dc..6bf4e1d 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs @@ -46,10 +46,10 @@ } } - public void RecyclableList_Contains_FirstItems() + public void RecyclableLongList_Contains_FirstItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs index 68b9e3a..8f41041 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs @@ -46,10 +46,10 @@ } } - public void RecyclableList_Contains_LastItems() + public void RecyclableLongList_Contains_LastItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs index 0a45e1b..45f18bb 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs @@ -26,9 +26,9 @@ DoNothing(data.Count); } - public void RecyclableList_Count() + public void RecyclableLongList_Count() { - var data = TestObjectsAsRecyclableList; + var data = TestObjectsAsRecyclableLongList; DoNothing(data.Count); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs index 710da7d..2aacb8b 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs @@ -22,9 +22,9 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - public static void RecyclableList_Create() + public static void RecyclableLongList_Create() { - using var list = new RecyclableList(); + using var list = new RecyclableLongList(); DoNothing(list); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs index ba87e09..bceb830 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs @@ -28,9 +28,9 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - public void RecyclableList_Create_WithCapacity() + public void RecyclableLongList_Create_WithCapacity() { - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(list); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index 26c5828..2388a24 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -42,9 +42,9 @@ } } - public void RecyclableList_GetItem() + public void RecyclableLongList_GetItem() { - var data = TestObjectsAsRecyclableList; + var data = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -52,9 +52,9 @@ } } - public void RecyclableList_GetItem_AsSpan() + public void RecyclableLongList_GetItem_AsSpan() { - var data = TestObjectsAsRecyclableList; + var data = TestObjectsAsRecyclableLongList; int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; @@ -78,9 +78,9 @@ } } - public void RecyclableList_GetItem_AsArray() + public void RecyclableLongList_GetItem_AsArray() { - var data = TestObjectsAsRecyclableList; + var data = TestObjectsAsRecyclableLongList; int blockSize = data.BlockSize; int nextItemBlockIndex = data.NextItemBlockIndex; @@ -105,18 +105,18 @@ } } - public void RecyclableList_GetItem_AsForEach() + public void RecyclableLongList_GetItem_AsForEach() { - var data = TestObjectsAsRecyclableList; + var data = TestObjectsAsRecyclableLongList; foreach (var item in data) { DoNothing(item); } } - public void RecyclableList_GetItem_AsWhile() + public void RecyclableLongList_GetItem_AsWhile() { - var data = TestObjectsAsRecyclableList; + var data = TestObjectsAsRecyclableLongList; long dataCount = TestObjectCount; if (dataCount == 0) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs index 650ba99..e1a4710 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs @@ -50,10 +50,10 @@ } } - public void RecyclableList_IndexOf_BestAndWorstCases() + public void RecyclableLongList_IndexOf_BestAndWorstCases() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { @@ -62,10 +62,10 @@ } } - public void RecyclableList_LongIndexOf_BestAndWorstCases() + public void RecyclableLongList_LongIndexOf_BestAndWorstCases() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs index df4ca9e..992d33a 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs @@ -46,10 +46,10 @@ } } - public void RecyclableList_IndexOf_FirstItems() + public void RecyclableLongList_IndexOf_FirstItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { @@ -57,10 +57,10 @@ } } - public void RecyclableList_LongIndexOf_FirstItems() + public void RecyclableLongList_LongIndexOf_FirstItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs index ab77a17..3d8b937 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs @@ -46,10 +46,10 @@ } } - public void RecyclableList_IndexOf_LastItems() + public void RecyclableLongList_IndexOf_LastItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { @@ -57,10 +57,10 @@ } } - public void RecyclableList_LongIndexOf_LastItems() + public void RecyclableLongList_LongIndexOf_LastItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs index 2870f4d..9640ecd 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs @@ -8,9 +8,9 @@ DoNothing(data.LongLength); } - public void RecyclableList_LongCount() + public void RecyclableLongList_LongCount() { - var data = TestObjectsAsRecyclableList; + var data = TestObjectsAsRecyclableLongList; DoNothing(data.LongCount); } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 5aa2adb..5bb8227 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -18,7 +18,7 @@ namespace Recyclable.Collections.Benchmarks { TestObjectCount = 1; BaselineBenchmarkType = RecyclableCollectionsBenchmarkSource.PooledList; - BenchmarkType = RecyclableCollectionsBenchmarkSource.RecyclableList; + BenchmarkType = RecyclableCollectionsBenchmarkSource.RecyclableLongList; } [Params @@ -62,7 +62,7 @@ namespace Recyclable.Collections.Benchmarks RecyclableCollectionsBenchmarkSource.List, RecyclableCollectionsBenchmarkSource.PooledList, RecyclableCollectionsBenchmarkSource.RecyclableArrayList, - RecyclableCollectionsBenchmarkSource.RecyclableList + RecyclableCollectionsBenchmarkSource.RecyclableLongList )] public override RecyclableCollectionsBenchmarkSource BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs index 5bb65cb..8959972 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs @@ -37,10 +37,10 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableList_RemoveAt_FirstItems() + public void RecyclableLongList_RemoveAt_FirstItems() { var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs index f4b808e..77a3576 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs @@ -37,10 +37,10 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableList_RemoveAt_LastItems() + public void RecyclableLongList_RemoveAt_LastItems() { var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs index 3d61a1c..ae15e61 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs @@ -37,10 +37,10 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableList_Remove_FirstItems() + public void RecyclableLongList_Remove_FirstItems() { var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs index 374a722..a727493 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs @@ -37,10 +37,10 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableList_Remove_LastItems() + public void RecyclableLongList_Remove_LastItems() { var data = TestObjects; - using var list = new RecyclableList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs index 689a5e1..115a391 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs @@ -43,9 +43,9 @@ } } - public void RecyclableList_SetItem() + public void RecyclableLongList_SetItem() { - var data = TestObjectsAsRecyclableList; + var data = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs deleted file mode 100644 index 581622a..0000000 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.Reflection; -using BenchmarkDotNet.Attributes; -using Collections.Pooled; -using Recyclable.Collections.Benchmarks.Core; - -namespace Recyclable.Collections.Benchmarks.POC -{ - public enum RecyclableListPocBenchmarkType - { - IndexOfV1, - IndexOfV2, - IndexOfV3, - IndexOfV4, - IndexOf_PooledList, - IndexOf, - EnsureCapacity_ByPowOf2, - EnsureCapacityV1_ByPowOf2, - EnsureCapacityV2_ByPowOf2, - EnsureCapacityV3_ByPowOf2, - } - - [MemoryDiagnoser] - public partial class RecyclableListPocBenchmarks : PocBenchmarkBase - { - [Params(RecyclableListPocBenchmarkType.IndexOf)] - public override RecyclableListPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } - - // [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.RecyclableArrayList, BenchmarkType.RecyclableList)] - [Params(RecyclableListPocBenchmarkType.IndexOf_PooledList)] - public override RecyclableListPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } - - private long[]? _testArray; - public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); - - public RecyclableList TestObjectsAsRecyclableList => _testRecyclableList ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableList)} is null"); - private RecyclableListIndexOfV1? _testRecyclableListIndexOfV1; - internal RecyclableListIndexOfV1 TestObjectsAsRecyclableListIndexOfV1 => _testRecyclableListIndexOfV1 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV1)} is null"); - private RecyclableListIndexOfV2? _testRecyclableListIndexOfV2; - private RecyclableList? _testRecyclableList; - internal RecyclableListIndexOfV2 TestObjectsAsRecyclableListIndexOfV2 => _testRecyclableListIndexOfV2 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV2)} is null"); - private RecyclableListIndexOfV3? _testRecyclableListIndexOfV3; - internal RecyclableListIndexOfV3 TestObjectsAsRecyclableListIndexOfV3 => _testRecyclableListIndexOfV3 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV3)} is null"); - private RecyclableListIndexOfV4? _testRecyclableListIndexOfV4; - internal RecyclableListIndexOfV4 TestObjectsAsRecyclableListIndexOfV4 => _testRecyclableListIndexOfV4 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableListIndexOfV4)} is null"); - private PooledList? _testPooledList; - internal PooledList TestPooledList => _testPooledList ?? throw new NullReferenceException($"Something is wrong and {nameof(PooledList)} is null"); - - private static readonly MethodInfo _ensureCapacityNewFunc; - private static readonly MethodInfo _ensureCapacityV1Func; - private static readonly MethodInfo _ensureCapacityV2Func; - private static readonly MethodInfo _ensureCapacityV3Func; - - static RecyclableListPocBenchmarks() - { - _ensureCapacityNewFunc = typeof(RecyclableList).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) - ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableList)}"); - - _ensureCapacityV1Func = typeof(RecyclableListV1).GetMethod("EnsureCapacity", BindingFlags.Instance | BindingFlags.NonPublic) - ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableListV1)}"); - - _ensureCapacityV2Func = typeof(RecyclableListV2).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.Public) - ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableListV2)}"); - - _ensureCapacityV3Func = typeof(RecyclableListV3).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) - ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableListV3)}"); - } - - public static void Run() - { - var benchmark = new RecyclableListPocBenchmarks(); - benchmark.Setup(); - // benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); - // benchmark.RecyclableList_EnsureCapacityV1_ByPowOf2(); - // benchmark.RecyclableList_EnsureCapacityV2_ByPowOf2(); - // benchmark.RecyclableList_EnsureCapacityV3_ByPowOf2(); - // benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); - // benchmark.RecyclableList_EnsureCapacityV1_ByBlockSize(); - // benchmark.RecyclableList_EnsureCapacityV2_ByBlockSize(); - // benchmark.RecyclableList_EnsureCapacityV3_ByBlockSize(); - benchmark.RecyclableList_IndexOf(); - benchmark.RecyclableList_IndexOfV1(); - benchmark.RecyclableList_IndexOfV2(); - benchmark.RecyclableList_IndexOfV3(); - benchmark.RecyclableList_IndexOfV4(); - benchmark.Cleanup(); - } - - public override void Setup() - { - Console.WriteLine("******* SETTING UP EXPECTED RESULTS DATA *******"); - _testArray = DataGenerator.EnumerateTestObjects(TestObjectCount); - - base.Setup(); - } - - public override void Cleanup() - { - _testArray = null; - _testRecyclableList?.Dispose(); - _testRecyclableList = null; - _testRecyclableListIndexOfV1?.Dispose(); - _testRecyclableListIndexOfV1 = null; - _testRecyclableListIndexOfV2?.Dispose(); - _testRecyclableListIndexOfV2 = null; - _testRecyclableListIndexOfV3?.Dispose(); - _testRecyclableListIndexOfV3 = null; - _testRecyclableListIndexOfV4?.Dispose(); - _testRecyclableListIndexOfV4 = null; - _testPooledList?.Dispose(); - _testPooledList = null; - - base.Cleanup(); - } - - protected override Action? GetTestMethod(RecyclableListPocBenchmarkType benchmarkType) => benchmarkType switch - { - RecyclableListPocBenchmarkType.IndexOfV1 => () => RecyclableList_IndexOfV1(), - RecyclableListPocBenchmarkType.IndexOfV2 => () => RecyclableList_IndexOfV2(), - RecyclableListPocBenchmarkType.IndexOfV3 => () => RecyclableList_IndexOfV3(), - RecyclableListPocBenchmarkType.IndexOfV4 => () => RecyclableList_IndexOfV4(), - RecyclableListPocBenchmarkType.IndexOf_PooledList => () => PooledList_IndexOf(), - RecyclableListPocBenchmarkType.IndexOf => () => RecyclableList_IndexOf(), - RecyclableListPocBenchmarkType.EnsureCapacity_ByPowOf2 => () => RecyclableList_EnsureCapacity_ByPowOf2(), - RecyclableListPocBenchmarkType.EnsureCapacityV1_ByPowOf2 => () => RecyclableList_EnsureCapacityV1_ByPowOf2(), - RecyclableListPocBenchmarkType.EnsureCapacityV2_ByPowOf2 => () => RecyclableList_EnsureCapacityV2_ByPowOf2(), - RecyclableListPocBenchmarkType.EnsureCapacityV3_ByPowOf2 => () => RecyclableList_EnsureCapacityV3_ByPowOf2(), - _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), - }; - - protected override void PrepareData(T benchmarkType) - { - base.PrepareData(benchmarkType); - - switch (BenchmarkType) - { - case RecyclableListPocBenchmarkType.IndexOfV1: - _testRecyclableListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - break; - case RecyclableListPocBenchmarkType.IndexOfV2: - _testRecyclableListIndexOfV2 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - break; - case RecyclableListPocBenchmarkType.IndexOfV3: - _testRecyclableListIndexOfV3 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - break; - case RecyclableListPocBenchmarkType.IndexOfV4: - _testRecyclableListIndexOfV4 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - break; - case RecyclableListPocBenchmarkType.IndexOf_PooledList: - _testPooledList = new PooledList(TestObjects, ClearMode.Auto); - break; - case RecyclableListPocBenchmarkType.IndexOf: - _testRecyclableList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); - break; - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs similarity index 94% rename from Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs rename to Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs index 8c320c8..046bc7d 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs @@ -6,9 +6,9 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { - #region RecyclableList v1 + #region RecyclableLongList v1 - internal class RecyclableListV1 : IDisposable, IList + internal class RecyclableLongListV1 : IDisposable, IList { private const int ItemNotFoundIndex = -1; @@ -352,7 +352,7 @@ namespace Recyclable.Collections.Benchmarks.POC return newCapacity; } - public RecyclableListV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) { memoryBlocksPool ??= _defaultMemoryBlocksPool; blockArrayPool ??= _defaultBlockArrayPool; @@ -378,7 +378,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - public RecyclableListV1(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListV1(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) { memoryBlocksPool ??= _defaultMemoryBlocksPool; blockArrayPool ??= _defaultBlockArrayPool; @@ -520,7 +520,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableListV1 items) + public void AddRange(RecyclableLongListV1 items) { if (items.LongCount == 0) { @@ -691,9 +691,9 @@ namespace Recyclable.Collections.Benchmarks.POC public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { - if (source is RecyclableListV1 sourceRecyclableList) + if (source is RecyclableLongListV1 sourceRecyclableLongList) { - AddRange(sourceRecyclableList); + AddRange(sourceRecyclableLongList); return; } @@ -849,7 +849,7 @@ namespace Recyclable.Collections.Benchmarks.POC IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - ~RecyclableListV1() + ~RecyclableLongListV1() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(); @@ -873,9 +873,9 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion - #region RecyclableList v2 + #region RecyclableLongList v2 - internal class RecyclableListV2 : IDisposable, IList + internal class RecyclableLongListV2 : IDisposable, IList { private const int ItemNotFoundIndex = -1; @@ -920,7 +920,7 @@ namespace Recyclable.Collections.Benchmarks.POC public int NextItemIndex => _nextItemIndex; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - private static void CopyItems(RecyclableListV2 list, long index) + private static void CopyItems(RecyclableLongListV2 list, long index) { T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; @@ -1111,10 +1111,10 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. /// - /// that needs to be resized. + /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. - /// The minimum no. of items MUST be able to store after . + /// The minimum no. of items MUST be able to store after . /// /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -1123,7 +1123,7 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableListV2 list, int minBlockSize, byte minBlockSizeBitShift, long newCapacity) + protected static long Resize(RecyclableLongListV2 list, int minBlockSize, byte minBlockSizeBitShift, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = checked((int)(list._capacity >> minBlockSizeBitShift)); @@ -1198,14 +1198,14 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long EnsureCapacity(RecyclableListV2 list, long requestedCapacity) + public static long EnsureCapacity(RecyclableLongListV2 list, long requestedCapacity) { long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) : requestedCapacity; int blockSize = list._blockSize; - newCapacity = RecyclableListV2.Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + newCapacity = RecyclableLongListV2.Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); if (newCapacity > 0 && blockSize != list._memoryBlocks[0].Length) { blockSize = list._memoryBlocks[0].Length; @@ -1223,7 +1223,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public RecyclableListV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -1262,7 +1262,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - public RecyclableListV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) { memoryBlocksPool ??= _defaultMemoryBlocksPool; _memoryBlocksPool = memoryBlocksPool; @@ -1418,7 +1418,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableListV2 items) + public void AddRange(RecyclableLongListV2 items) { if (items.LongCount == 0) { @@ -1591,9 +1591,9 @@ namespace Recyclable.Collections.Benchmarks.POC public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { - if (source is RecyclableListV2 sourceRecyclableList) + if (source is RecyclableLongListV2 sourceRecyclableLongList) { - AddRange(sourceRecyclableList); + AddRange(sourceRecyclableLongList); return; } @@ -1818,7 +1818,7 @@ namespace Recyclable.Collections.Benchmarks.POC IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - ~RecyclableListV2() + ~RecyclableLongListV2() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(); @@ -1842,9 +1842,9 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion - #region RecyclableList v3 + #region RecyclableLongList v3 - internal class RecyclableListV3 : IDisposable, IList + internal class RecyclableLongListV3 : IDisposable, IList { private const int ItemNotFoundIndex = -1; @@ -1892,7 +1892,7 @@ namespace Recyclable.Collections.Benchmarks.POC public int NextItemIndex => _nextItemIndex; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - private static void CopyItems(RecyclableListV3 list, long index) + private static void CopyItems(RecyclableLongListV3 list, long index) { T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; @@ -2083,10 +2083,10 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. /// - /// that needs to be resized. + /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. - /// The minimum no. of items MUST be able to store after . + /// The minimum no. of items MUST be able to store after . /// /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -2095,7 +2095,7 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableListV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + protected static long Resize(RecyclableLongListV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = list._reservedBlockCount; @@ -2170,7 +2170,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static void SetupBlockArrayPooling(RecyclableListV3 list, int blockSize, ArrayPool? blockArrayPool = null) + protected static void SetupBlockArrayPooling(RecyclableLongListV3 list, int blockSize, ArrayPool? blockArrayPool = null) { list._blockSize = blockSize; list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); @@ -2187,7 +2187,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - protected static long EnsureCapacity(RecyclableListV3 list, long requestedCapacity) + protected static long EnsureCapacity(RecyclableLongListV3 list, long requestedCapacity) { long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) @@ -2207,7 +2207,7 @@ namespace Recyclable.Collections.Benchmarks.POC [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -2236,7 +2236,7 @@ namespace Recyclable.Collections.Benchmarks.POC } #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -2379,7 +2379,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableListV3 items) + public void AddRange(RecyclableLongListV3 items) { if (items.LongCount == 0) { @@ -2552,9 +2552,9 @@ namespace Recyclable.Collections.Benchmarks.POC public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) { - if (source is RecyclableList sourceRecyclableList) + if (source is RecyclableLongList sourceRecyclableLongList) { - AddRange(sourceRecyclableList); + AddRange(sourceRecyclableLongList); return; } @@ -2781,7 +2781,7 @@ namespace Recyclable.Collections.Benchmarks.POC IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - ~RecyclableListV3() + ~RecyclableLongListV3() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(); @@ -2805,53 +2805,53 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion - public partial class RecyclableListPocBenchmarks + public partial class RecyclableLongListPocBenchmarks { - public void RecyclableList_EnsureCapacityV1_ByPowOf2() + public void RecyclableLongList_EnsureCapacityV1_ByPowOf2() { - using var list = new RecyclableListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityV1Func.Invoke(list, new object[] { TestObjectCount })); } - public void RecyclableList_EnsureCapacityV2_ByPowOf2() + public void RecyclableLongList_EnsureCapacityV2_ByPowOf2() { - using var list = new RecyclableListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityV2Func.Invoke(null, new object[] { list, TestObjectCount })); } - public void RecyclableList_EnsureCapacityV3_ByPowOf2() + public void RecyclableLongList_EnsureCapacityV3_ByPowOf2() { - using var list = new RecyclableListV3(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongListV3(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); } - public void RecyclableList_EnsureCapacity_ByPowOf2() + public void RecyclableLongList_EnsureCapacity_ByPowOf2() { - using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityNewFunc.Invoke(null, new object[] { list, TestObjectCount })); } - public void RecyclableList_EnsureCapacityV1_ByBlockSize() + public void RecyclableLongList_EnsureCapacityV1_ByBlockSize() { - using var list = new RecyclableListV1(); + using var list = new RecyclableLongListV1(); DoNothing(_ensureCapacityV1Func.Invoke(list, new object[] { TestObjectCount })); } - public void RecyclableList_EnsureCapacityV2_ByBlockSize() + public void RecyclableLongList_EnsureCapacityV2_ByBlockSize() { - using var list = new RecyclableListV2(); + using var list = new RecyclableLongListV2(); DoNothing(_ensureCapacityV2Func.Invoke(null, new object[] { list, TestObjectCount })); } - public void RecyclableList_EnsureCapacityV3_ByBlockSize() + public void RecyclableLongList_EnsureCapacityV3_ByBlockSize() { - using var list = new RecyclableListV3(); + using var list = new RecyclableLongListV3(); DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); } - public void RecyclableList_EnsureCapacity_ByBlockSize() + public void RecyclableLongList_EnsureCapacity_ByBlockSize() { - using var list = new RecyclableList(); + using var list = new RecyclableLongList(); DoNothing(_ensureCapacityNewFunc.Invoke(null, new object[] { list, TestObjectCount })); } } diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs similarity index 95% rename from Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs rename to Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs index a1ce342..0fbea0a 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs @@ -96,9 +96,9 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion - #region RecyclableList IndexOf v1 + #region RecyclableLongList IndexOf v1 - internal class RecyclableListIndexOfV1 : IDisposable, IList + internal class RecyclableLongListIndexOfV1 : IDisposable, IList { private const int ItemNotFoundIndex = -1; private const double OptimalParallelSearchStep = 0.329; @@ -153,7 +153,7 @@ namespace Recyclable.Collections.Benchmarks.POC public int NextItemIndex => _nextItemIndex; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - private static void CopyItems(RecyclableListIndexOfV1 list, long index) + private static void CopyItems(RecyclableLongListIndexOfV1 list, long index) { T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; @@ -316,7 +316,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static long DoLongIndexOfParallel(in RecyclableListIndexOfV1 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + private static long DoLongIndexOfParallel(in RecyclableLongListIndexOfV1 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) { int blockSize = list._blockSize; int blockIndex = itemRange.BlockIndex; @@ -491,10 +491,10 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. /// - /// that needs to be resized. + /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. - /// The minimum no. of items MUST be able to store after . + /// The minimum no. of items MUST be able to store after . /// /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -507,7 +507,7 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableListIndexOfV1 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + protected static long Resize(RecyclableLongListIndexOfV1 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = list._reservedBlockCount; @@ -594,7 +594,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static void SetupBlockArrayPooling(RecyclableListIndexOfV1 list, int blockSize, ArrayPool? blockArrayPool = null) + protected static void SetupBlockArrayPooling(RecyclableLongListIndexOfV1 list, int blockSize, ArrayPool? blockArrayPool = null) { list._blockSize = blockSize; list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); @@ -604,7 +604,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - protected static long EnsureCapacity(RecyclableListIndexOfV1 list, long requestedCapacity) + protected static long EnsureCapacity(RecyclableLongListIndexOfV1 list, long requestedCapacity) { long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) @@ -624,7 +624,7 @@ namespace Recyclable.Collections.Benchmarks.POC [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListIndexOfV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListIndexOfV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -653,7 +653,7 @@ namespace Recyclable.Collections.Benchmarks.POC } #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListIndexOfV1(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListIndexOfV1(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -799,7 +799,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableListIndexOfV1 items) + public void AddRange(RecyclableLongListIndexOfV1 items) { if (items.LongCount == 0) { @@ -975,9 +975,9 @@ namespace Recyclable.Collections.Benchmarks.POC public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) { - if (source is RecyclableList sourceRecyclableList) + if (source is RecyclableLongList sourceRecyclableLongList) { - AddRange(sourceRecyclableList); + AddRange(sourceRecyclableLongList); return; } @@ -1219,7 +1219,7 @@ namespace Recyclable.Collections.Benchmarks.POC IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - ~RecyclableListIndexOfV1() + ~RecyclableLongListIndexOfV1() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(); @@ -1243,9 +1243,9 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion - #region RecyclableList IndexOf v2 + #region RecyclableLongList IndexOf v2 - internal class RecyclableListIndexOfV2 : IDisposable, IList + internal class RecyclableLongListIndexOfV2 : IDisposable, IList { private const int ItemNotFoundIndex = -1; private const double OptimalParallelSearchStep = 0.329; @@ -1300,7 +1300,7 @@ namespace Recyclable.Collections.Benchmarks.POC public int NextItemIndex => _nextItemIndex; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - private static void CopyItems(RecyclableListIndexOfV2 list, long index) + private static void CopyItems(RecyclableLongListIndexOfV2 list, long index) { T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; @@ -1463,7 +1463,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static long DoLongIndexOfParallel(in RecyclableListIndexOfV2 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + private static long DoLongIndexOfParallel(in RecyclableLongListIndexOfV2 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) { int blockSize = list._blockSize; int blockIndex = itemRange.BlockIndex; @@ -1636,10 +1636,10 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. /// - /// that needs to be resized. + /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. - /// The minimum no. of items MUST be able to store after . + /// The minimum no. of items MUST be able to store after . /// /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -1652,7 +1652,7 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableListIndexOfV2 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + protected static long Resize(RecyclableLongListIndexOfV2 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = list._reservedBlockCount; @@ -1739,7 +1739,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static void SetupBlockArrayPooling(RecyclableListIndexOfV2 list, int blockSize, ArrayPool? blockArrayPool = null) + protected static void SetupBlockArrayPooling(RecyclableLongListIndexOfV2 list, int blockSize, ArrayPool? blockArrayPool = null) { list._blockSize = blockSize; list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); @@ -1749,7 +1749,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - protected static long EnsureCapacity(RecyclableListIndexOfV2 list, long requestedCapacity) + protected static long EnsureCapacity(RecyclableLongListIndexOfV2 list, long requestedCapacity) { long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) @@ -1769,7 +1769,7 @@ namespace Recyclable.Collections.Benchmarks.POC [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListIndexOfV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListIndexOfV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -1798,7 +1798,7 @@ namespace Recyclable.Collections.Benchmarks.POC } #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListIndexOfV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListIndexOfV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -1944,7 +1944,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableListIndexOfV2 items) + public void AddRange(RecyclableLongListIndexOfV2 items) { if (items.LongCount == 0) { @@ -2120,9 +2120,9 @@ namespace Recyclable.Collections.Benchmarks.POC public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) { - if (source is RecyclableListIndexOfV2 sourceRecyclableList) + if (source is RecyclableLongListIndexOfV2 sourceRecyclableLongList) { - AddRange(sourceRecyclableList); + AddRange(sourceRecyclableLongList); return; } @@ -2364,7 +2364,7 @@ namespace Recyclable.Collections.Benchmarks.POC IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - ~RecyclableListIndexOfV2() + ~RecyclableLongListIndexOfV2() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(); @@ -2388,9 +2388,9 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion - #region RecyclableList IndexOf v3 + #region RecyclableLongList IndexOf v3 - internal class RecyclableListIndexOfV3 : IDisposable, IList + internal class RecyclableLongListIndexOfV3 : IDisposable, IList { private const long ItemNotFoundIndexLong = -1L; private const int ItemNotFoundIndex = -1; @@ -2446,7 +2446,7 @@ namespace Recyclable.Collections.Benchmarks.POC public int NextItemIndex => _nextItemIndex; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - private static void CopyItems(RecyclableListIndexOfV3 list, long index) + private static void CopyItems(RecyclableLongListIndexOfV3 list, long index) { T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; @@ -2608,7 +2608,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static long DoLongIndexOfParallel(in RecyclableListIndexOfV3 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + private static long DoLongIndexOfParallel(in RecyclableLongListIndexOfV3 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) { int blockSize = list._blockSize; int blockIndex = itemRange.BlockIndex; @@ -2753,10 +2753,10 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. /// - /// that needs to be resized. + /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. - /// The minimum no. of items MUST be able to store after . + /// The minimum no. of items MUST be able to store after . /// /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -2769,7 +2769,7 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableListIndexOfV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + protected static long Resize(RecyclableLongListIndexOfV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = list._reservedBlockCount; @@ -2856,7 +2856,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static void SetupBlockArrayPooling(RecyclableListIndexOfV3 list, int blockSize, ArrayPool? blockArrayPool = null) + protected static void SetupBlockArrayPooling(RecyclableLongListIndexOfV3 list, int blockSize, ArrayPool? blockArrayPool = null) { list._blockSize = blockSize; list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); @@ -2866,7 +2866,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - protected static long EnsureCapacity(RecyclableListIndexOfV3 list, long requestedCapacity) + protected static long EnsureCapacity(RecyclableLongListIndexOfV3 list, long requestedCapacity) { long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) @@ -2886,7 +2886,7 @@ namespace Recyclable.Collections.Benchmarks.POC [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListIndexOfV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListIndexOfV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -2915,7 +2915,7 @@ namespace Recyclable.Collections.Benchmarks.POC } #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListIndexOfV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListIndexOfV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -3061,7 +3061,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableListIndexOfV3 items) + public void AddRange(RecyclableLongListIndexOfV3 items) { if (items.LongCount == 0) { @@ -3237,9 +3237,9 @@ namespace Recyclable.Collections.Benchmarks.POC public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) { - if (source is RecyclableList sourceRecyclableList) + if (source is RecyclableLongList sourceRecyclableLongList) { - AddRange(sourceRecyclableList); + AddRange(sourceRecyclableLongList); return; } @@ -3478,7 +3478,7 @@ namespace Recyclable.Collections.Benchmarks.POC IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - ~RecyclableListIndexOfV3() + ~RecyclableLongListIndexOfV3() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(); @@ -3502,15 +3502,15 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion - #region RecyclableList IndexOf v4 + #region RecyclableLongList IndexOf v4 - internal static class RecyclableListIndexOfV4Extensions + internal static class RecyclableLongListIndexOfV4Extensions { private const long ItemNotFoundIndexLong = -1L; private const int ItemNotFoundIndex = -1; [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - public static long DoLongIndexOfParallel(this RecyclableListIndexOfV4 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + public static long DoLongIndexOfParallel(this RecyclableLongListIndexOfV4 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) { int blockSize = list._blockSize; int blockIndex = itemRange.BlockIndex; @@ -3583,7 +3583,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - internal class RecyclableListIndexOfV4 : IDisposable, IList + internal class RecyclableLongListIndexOfV4 : IDisposable, IList { private const long ItemNotFoundIndexLong = -1L; private const int ItemNotFoundIndex = -1; @@ -3639,7 +3639,7 @@ namespace Recyclable.Collections.Benchmarks.POC public int NextItemIndex => _nextItemIndex; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - private static void CopyItems(RecyclableListIndexOfV4 list, long index) + private static void CopyItems(RecyclableLongListIndexOfV4 list, long index) { T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; @@ -3817,7 +3817,7 @@ namespace Recyclable.Collections.Benchmarks.POC return ItemNotFoundIndexLong; } - private static bool ScheduleIndexOfTasks(ParallelSynchronizationContextV4 context, RecyclableListIndexOfV4 list, T itemToFind, ItemRangeV1 itemRange) + private static bool ScheduleIndexOfTasks(ParallelSynchronizationContextV4 context, RecyclableLongListIndexOfV4 list, T itemToFind, ItemRangeV1 itemRange) { _ = context.AllDoneSignal.AddParticipant(); _ = Task.Factory.StartNew(() => @@ -3845,7 +3845,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] - public static void Iterate(in ParallelSynchronizationContextV4 context, RecyclableListIndexOfV4 list, in T itemToFind, Func, T, ItemRangeV1, bool> action) + public static void Iterate(in ParallelSynchronizationContextV4 context, RecyclableLongListIndexOfV4 list, in T itemToFind, Func, T, ItemRangeV1, bool> action) { int itemIndex = 0; ItemRangeV1 itemRange; @@ -3907,10 +3907,10 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. /// - /// that needs to be resized. + /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. - /// The minimum no. of items MUST be able to store after . + /// The minimum no. of items MUST be able to store after . /// /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -3923,7 +3923,7 @@ namespace Recyclable.Collections.Benchmarks.POC /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableListIndexOfV4 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + protected static long Resize(RecyclableLongListIndexOfV4 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = list._reservedBlockCount; @@ -4010,7 +4010,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static void SetupBlockArrayPooling(RecyclableListIndexOfV4 list, int blockSize, ArrayPool? blockArrayPool = null) + protected static void SetupBlockArrayPooling(RecyclableLongListIndexOfV4 list, int blockSize, ArrayPool? blockArrayPool = null) { list._blockSize = blockSize; list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); @@ -4020,7 +4020,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - protected static long EnsureCapacity(RecyclableListIndexOfV4 list, long requestedCapacity) + protected static long EnsureCapacity(RecyclableLongListIndexOfV4 list, long requestedCapacity) { long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) @@ -4040,7 +4040,7 @@ namespace Recyclable.Collections.Benchmarks.POC [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListIndexOfV4(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListIndexOfV4(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -4069,7 +4069,7 @@ namespace Recyclable.Collections.Benchmarks.POC } #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableListIndexOfV4(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListIndexOfV4(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -4215,7 +4215,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableListIndexOfV4 items) + public void AddRange(RecyclableLongListIndexOfV4 items) { if (items.LongCount == 0) { @@ -4391,9 +4391,9 @@ namespace Recyclable.Collections.Benchmarks.POC public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) { - if (source is RecyclableList sourceRecyclableList) + if (source is RecyclableLongList sourceRecyclableLongList) { - AddRange(sourceRecyclableList); + AddRange(sourceRecyclableLongList); return; } @@ -4632,7 +4632,7 @@ namespace Recyclable.Collections.Benchmarks.POC IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - ~RecyclableListIndexOfV4() + ~RecyclableLongListIndexOfV4() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(); @@ -4656,12 +4656,12 @@ namespace Recyclable.Collections.Benchmarks.POC #endregion - public partial class RecyclableListPocBenchmarks + public partial class RecyclableLongListPocBenchmarks { - public void RecyclableList_IndexOfV1() + public void RecyclableLongList_IndexOfV1() { var data = TestObjects; - var list = TestObjectsAsRecyclableListIndexOfV1; + var list = TestObjectsAsRecyclableLongListIndexOfV1; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -4680,10 +4680,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - public void RecyclableList_IndexOfV2() + public void RecyclableLongList_IndexOfV2() { var data = TestObjects; - var list = TestObjectsAsRecyclableListIndexOfV2; + var list = TestObjectsAsRecyclableLongListIndexOfV2; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -4691,10 +4691,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - public void RecyclableList_IndexOfV3() + public void RecyclableLongList_IndexOfV3() { var data = TestObjects; - var list = TestObjectsAsRecyclableListIndexOfV3; + var list = TestObjectsAsRecyclableLongListIndexOfV3; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -4702,10 +4702,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - public void RecyclableList_IndexOfV4() + public void RecyclableLongList_IndexOfV4() { var data = TestObjects; - var list = TestObjectsAsRecyclableListIndexOfV4; + var list = TestObjectsAsRecyclableLongListIndexOfV4; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -4713,10 +4713,10 @@ namespace Recyclable.Collections.Benchmarks.POC } } - public void RecyclableList_IndexOf() + public void RecyclableLongList_IndexOf() { var data = TestObjects; - var list = TestObjectsAsRecyclableList; + var list = TestObjectsAsRecyclableLongList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs new file mode 100644 index 0000000..b24b1ef --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs @@ -0,0 +1,157 @@ +using System.Reflection; +using BenchmarkDotNet.Attributes; +using Collections.Pooled; +using Recyclable.Collections.Benchmarks.Core; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum RecyclableLongListPocBenchmarkType + { + IndexOfV1, + IndexOfV2, + IndexOfV3, + IndexOfV4, + IndexOf_PooledList, + IndexOf, + EnsureCapacity_ByPowOf2, + EnsureCapacityV1_ByPowOf2, + EnsureCapacityV2_ByPowOf2, + EnsureCapacityV3_ByPowOf2, + } + + [MemoryDiagnoser] + public partial class RecyclableLongListPocBenchmarks : PocBenchmarkBase + { + [Params(RecyclableLongListPocBenchmarkType.IndexOf)] + public override RecyclableLongListPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + // [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.RecyclableArrayList, BenchmarkType.RecyclableLongList)] + [Params(RecyclableLongListPocBenchmarkType.IndexOf_PooledList)] + public override RecyclableLongListPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + private long[]? _testArray; + public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); + + public RecyclableLongList TestObjectsAsRecyclableLongList => _testRecyclableLongList ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongList)} is null"); + private RecyclableLongListIndexOfV1? _testRecyclableLongListIndexOfV1; + internal RecyclableLongListIndexOfV1 TestObjectsAsRecyclableLongListIndexOfV1 => _testRecyclableLongListIndexOfV1 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongListIndexOfV1)} is null"); + private RecyclableLongListIndexOfV2? _testRecyclableLongListIndexOfV2; + private RecyclableLongList? _testRecyclableLongList; + internal RecyclableLongListIndexOfV2 TestObjectsAsRecyclableLongListIndexOfV2 => _testRecyclableLongListIndexOfV2 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongListIndexOfV2)} is null"); + private RecyclableLongListIndexOfV3? _testRecyclableLongListIndexOfV3; + internal RecyclableLongListIndexOfV3 TestObjectsAsRecyclableLongListIndexOfV3 => _testRecyclableLongListIndexOfV3 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongListIndexOfV3)} is null"); + private RecyclableLongListIndexOfV4? _testRecyclableLongListIndexOfV4; + internal RecyclableLongListIndexOfV4 TestObjectsAsRecyclableLongListIndexOfV4 => _testRecyclableLongListIndexOfV4 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongListIndexOfV4)} is null"); + private PooledList? _testPooledList; + internal PooledList TestPooledList => _testPooledList ?? throw new NullReferenceException($"Something is wrong and {nameof(PooledList)} is null"); + + private static readonly MethodInfo _ensureCapacityNewFunc; + private static readonly MethodInfo _ensureCapacityV1Func; + private static readonly MethodInfo _ensureCapacityV2Func; + private static readonly MethodInfo _ensureCapacityV3Func; + + static RecyclableLongListPocBenchmarks() + { + _ensureCapacityNewFunc = typeof(RecyclableLongList).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableLongList)}"); + + _ensureCapacityV1Func = typeof(RecyclableLongListV1).GetMethod("EnsureCapacity", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableLongListV1)}"); + + _ensureCapacityV2Func = typeof(RecyclableLongListV2).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.Public) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableLongListV2)}"); + + _ensureCapacityV3Func = typeof(RecyclableLongListV3).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableLongListV3)}"); + } + + public static void Run() + { + var benchmark = new RecyclableLongListPocBenchmarks(); + benchmark.Setup(); + // benchmark.RecyclableLongList_EnsureCapacity_ByPowOf2(); + // benchmark.RecyclableLongList_EnsureCapacityV1_ByPowOf2(); + // benchmark.RecyclableLongList_EnsureCapacityV2_ByPowOf2(); + // benchmark.RecyclableLongList_EnsureCapacityV3_ByPowOf2(); + // benchmark.RecyclableLongList_EnsureCapacity_ByBlockSize(); + // benchmark.RecyclableLongList_EnsureCapacityV1_ByBlockSize(); + // benchmark.RecyclableLongList_EnsureCapacityV2_ByBlockSize(); + // benchmark.RecyclableLongList_EnsureCapacityV3_ByBlockSize(); + benchmark.RecyclableLongList_IndexOf(); + benchmark.RecyclableLongList_IndexOfV1(); + benchmark.RecyclableLongList_IndexOfV2(); + benchmark.RecyclableLongList_IndexOfV3(); + benchmark.RecyclableLongList_IndexOfV4(); + benchmark.Cleanup(); + } + + public override void Setup() + { + Console.WriteLine("******* SETTING UP EXPECTED RESULTS DATA *******"); + _testArray = DataGenerator.EnumerateTestObjects(TestObjectCount); + + base.Setup(); + } + + public override void Cleanup() + { + _testArray = null; + _testRecyclableLongList?.Dispose(); + _testRecyclableLongList = null; + _testRecyclableLongListIndexOfV1?.Dispose(); + _testRecyclableLongListIndexOfV1 = null; + _testRecyclableLongListIndexOfV2?.Dispose(); + _testRecyclableLongListIndexOfV2 = null; + _testRecyclableLongListIndexOfV3?.Dispose(); + _testRecyclableLongListIndexOfV3 = null; + _testRecyclableLongListIndexOfV4?.Dispose(); + _testRecyclableLongListIndexOfV4 = null; + _testPooledList?.Dispose(); + _testPooledList = null; + + base.Cleanup(); + } + + protected override Action? GetTestMethod(RecyclableLongListPocBenchmarkType benchmarkType) => benchmarkType switch + { + RecyclableLongListPocBenchmarkType.IndexOfV1 => () => RecyclableLongList_IndexOfV1(), + RecyclableLongListPocBenchmarkType.IndexOfV2 => () => RecyclableLongList_IndexOfV2(), + RecyclableLongListPocBenchmarkType.IndexOfV3 => () => RecyclableLongList_IndexOfV3(), + RecyclableLongListPocBenchmarkType.IndexOfV4 => () => RecyclableLongList_IndexOfV4(), + RecyclableLongListPocBenchmarkType.IndexOf_PooledList => () => PooledList_IndexOf(), + RecyclableLongListPocBenchmarkType.IndexOf => () => RecyclableLongList_IndexOf(), + RecyclableLongListPocBenchmarkType.EnsureCapacity_ByPowOf2 => () => RecyclableLongList_EnsureCapacity_ByPowOf2(), + RecyclableLongListPocBenchmarkType.EnsureCapacityV1_ByPowOf2 => () => RecyclableLongList_EnsureCapacityV1_ByPowOf2(), + RecyclableLongListPocBenchmarkType.EnsureCapacityV2_ByPowOf2 => () => RecyclableLongList_EnsureCapacityV2_ByPowOf2(), + RecyclableLongListPocBenchmarkType.EnsureCapacityV3_ByPowOf2 => () => RecyclableLongList_EnsureCapacityV3_ByPowOf2(), + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + protected override void PrepareData(T benchmarkType) + { + base.PrepareData(benchmarkType); + + switch (BenchmarkType) + { + case RecyclableLongListPocBenchmarkType.IndexOfV1: + _testRecyclableLongListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableLongListPocBenchmarkType.IndexOfV2: + _testRecyclableLongListIndexOfV2 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableLongListPocBenchmarkType.IndexOfV3: + _testRecyclableLongListIndexOfV3 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableLongListPocBenchmarkType.IndexOfV4: + _testRecyclableLongListIndexOfV4 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableLongListPocBenchmarkType.IndexOf_PooledList: + _testPooledList = new PooledList(TestObjects, ClearMode.Auto); + break; + case RecyclableLongListPocBenchmarkType.IndexOf: + _testRecyclableLongList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPos.Benchmarks.IndexOf.txt b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPos.Benchmarks.IndexOf.txt new file mode 100644 index 0000000..d4413b6 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPos.Benchmarks.IndexOf.txt @@ -0,0 +1,26 @@ +BenchmarkDotNet=v0.13.3, OS=ubuntu 20.04 +Intel Xeon Platinum 8272CL CPU 2.60GHz, 1 CPU, 4 logical and 2 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | Mean | Error | StdDev | Ratio | RatioSD | +|------------------------- |--------:|---------:|---------:|------:|--------:| +| RecyclableLongList_IndexOfV1 | 2.274 s | 0.0447 s | 0.0894 s | 1.00 | 0.04 | +| RecyclableLongList_IndexOfV2 | 2.302 s | 0.0456 s | 0.0750 s | 1.00 | 0.00 | +| RecyclableLongList_IndexOf | 2.234 s | 0.0446 s | 0.0681 s | 0.97 | 0.05 | + +// * Hints * +Outliers + RecyclableLongListPocBenchmarks.RecyclableLongList_IndexOfV2: Default -> 2 outliers were removed (2.54 s, 2.56 s) + RecyclableLongListPocBenchmarks.RecyclableLongList_IndexOf: Default -> 1 outlier was detected (2.06 s) + +// * Legends * + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 s : 1 Second (1 sec) + diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs index 683dcdf..b2b3e4a 100644 --- a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -78,7 +78,7 @@ namespace Recyclable.Collections.Benchmarks.POC //[Params(329_000, 512_000, 750_000, 1_000_000, 3_290_000)] //public int Step { get; set; } = 329_000; // This was the fastest so far - private RecyclableList? _testData; + private RecyclableLongList? _testData; public object? ItemToFind { get; set; } @@ -123,7 +123,7 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static long DoLongIndexOfParallel(RecyclableList list, in long itemToFind, ItemRangeV2 searchInfo, ManualResetEventSlim itemFoundSignal) + private static long DoLongIndexOfParallel(RecyclableLongList list, in long itemToFind, ItemRangeV2 searchInfo, ManualResetEventSlim itemFoundSignal) { int index; int blockSize = list.BlockSize; @@ -300,7 +300,7 @@ namespace Recyclable.Collections.Benchmarks.POC { base.PrepareData(benchmarkType); - _testData ??= Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableList(BlockSize); + _testData ??= Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableLongList(BlockSize); ItemToFind ??= _testData.Last(); } diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index a835447..3c05b5f 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -27,8 +27,8 @@ internal class Program //foreach (var _ in Enumerable.Range(1, 1000)) //{ - //benchmark.RecyclableList_Remove_FirstItems(); - //benchmark.RecyclableList_RemoveAt_LastItems(); + //benchmark.RecyclableLongList_Remove_FirstItems(); + //benchmark.RecyclableLongList_RemoveAt_LastItems(); //benchmark.List_RemoveAt_LastItems(); //} @@ -40,7 +40,7 @@ internal class Program _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); - _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); diff --git a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs index 01d2bf7..abeb9a8 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs @@ -12,7 +12,7 @@ namespace Recyclable.Collections.Benchmarks List, PooledList, RecyclableArrayList, - RecyclableList, + RecyclableLongList, } [MemoryDiagnoser] @@ -28,8 +28,8 @@ namespace Recyclable.Collections.Benchmarks public PooledList TestObjectsAsPooledList => _testObjectsAsPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); 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 RecyclableLongList? _testObjectsAsRecyclableLongList; + protected RecyclableLongList TestObjectsAsRecyclableLongList => _testObjectsAsRecyclableLongList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); protected IEnumerable TestObjectsAsIEnumerable { get @@ -68,8 +68,8 @@ namespace Recyclable.Collections.Benchmarks _testObjectsAsRecyclableArrayList ??= new(TestObjects, initialCapacity: TestObjectCount); break; - case RecyclableCollectionsBenchmarkSource.RecyclableList: - _testObjectsAsRecyclableList ??= new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + case RecyclableCollectionsBenchmarkSource.RecyclableLongList: + _testObjectsAsRecyclableLongList ??= new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); break; default: @@ -94,8 +94,8 @@ namespace Recyclable.Collections.Benchmarks _testObjectsAsPooledList = default; _testObjectsAsRecyclableArrayList?.Dispose(); _testObjectsAsRecyclableArrayList = default; - _testObjectsAsRecyclableList?.Dispose(); - _testObjectsAsRecyclableList = default; + _testObjectsAsRecyclableLongList?.Dispose(); + _testObjectsAsRecyclableLongList = default; base.Cleanup(); } } @@ -123,11 +123,11 @@ namespace Recyclable.Collections.Benchmarks // } // [Benchmark(Baseline = true)] - // public void RecyclableList() + // public void RecyclableLongList() // { // var data = TestObjects; // long dataCount = TestObjectCount; - // using var list = new RecyclableList(BlockSize, totalItemsCount: dataCount); + // using var list = new RecyclableLongList(BlockSize, totalItemsCount: dataCount); // for (long i = 0; i < dataCount; i++) // { // list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt index bf3a53a..a2f956e 100644 --- a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt +++ b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt @@ -8,115 +8,115 @@ | List_Create_WithCapacity | 2,377.6 us | 43.03 us | 40.25 us | 1.93 | 0.03 | 54.6875 | 54.6875 | 54.6875 | 8000451 B | 137,938.81 | | PooledList_Create_WithCapacity | 1,229.4 us | 10.70 us | 9.48 us | 1.00 | 0.00 | - | - | - | 58 B | 1.00 | | RecyclableArrayList_Create_WithCapacity | 1,233.9 us | 3.93 us | 3.68 us | 1.00 | 0.01 | - | - | - | 41 B | 0.71 | -| RecyclableList_Create_WithCapacity | 786.5 us | 2.32 us | 1.94 us | 0.64 | 0.01 | - | - | - | 425 B | 7.33 | +| RecyclableLongList_Create_WithCapacity | 786.5 us | 2.32 us | 1.94 us | 0.64 | 0.01 | - | - | - | 425 B | 7.33 | | List_Create | 3.5523 ns | 0.0181 ns | 0.0141 ns | 3.5476 ns | 0.000 | 0.00 | 0.0153 | - | - | 32 B | 0.000 | | PooledList_Create | 6.9236 ns | 0.0110 ns | 0.0098 ns | 6.9222 ns | 0.000 | 0.00 | 0.0268 | - | - | 56 B | 0.000 | | RecyclableArrayList_Create | 4.6451 ns | 0.0063 ns | 0.0053 ns | 4.6457 ns | 0.000 | 0.00 | 0.0191 | - | - | 40 B | 0.000 | -| RecyclableList_Create | 212.8164 ns | 11.9841 ns | 35.3355 ns | 213.2258 ns | 0.000 | 0.00 | 0.0381 | 0.0379 | - | 80 B | 0.000 | +| RecyclableLongList_Create | 212.8164 ns | 11.9841 ns | 35.3355 ns | 213.2258 ns | 0.000 | 0.00 | 0.0381 | 0.0379 | - | 80 B | 0.000 | Array_GetItem | 239.7 us | 0.36 us | 0.32 us | 0.66 | 0.00 | - | NA | List_GetItem | 363.5 us | 0.67 us | 0.56 us | 1.00 | 0.00 | - | NA | PooledList_GetItem | 365.3 us | 0.90 us | 0.75 us | 1.00 | 0.00 | - | NA | RecyclableArrayList_GetItem | 239.2 us | 0.07 us | 0.06 us | 0.65 | 0.00 | - | NA | - RecyclableList_GetItem | 538.0 us | 1.01 us | 0.89 us | 1.47 | 0.00 | 1 B | NA | - RecyclableList_GetItem_AsSpan | 673.5 us | 4.85 us | 4.30 us | 1.84 | 0.01 | 1 B | NA | - RecyclableList_GetItem_AsArray | 239.4 us | 0.22 us | 0.20 us | 0.66 | 0.00 | - | NA | - RecyclableList_GetItem_AsForEach | 5,514.6 us | 27.28 us | 22.78 us | 15.10 | 0.06 | 109 B | NA | - RecyclableList_GetItem_AsWhile | 459.3 us | 0.38 us | 0.34 us | 1.26 | 0.00 | - | NA | + RecyclableLongList_GetItem | 538.0 us | 1.01 us | 0.89 us | 1.47 | 0.00 | 1 B | NA | + RecyclableLongList_GetItem_AsSpan | 673.5 us | 4.85 us | 4.30 us | 1.84 | 0.01 | 1 B | NA | + RecyclableLongList_GetItem_AsArray | 239.4 us | 0.22 us | 0.20 us | 0.66 | 0.00 | - | NA | + RecyclableLongList_GetItem_AsForEach | 5,514.6 us | 27.28 us | 22.78 us | 15.10 | 0.06 | 109 B | NA | + RecyclableLongList_GetItem_AsWhile | 459.3 us | 0.38 us | 0.34 us | 1.26 | 0.00 | - | NA | | Array_SetItem | 2,576,086.8389 ns | 13,880.4584 ns | 11,590.8114 ns | 2,576,978.1250 ns | 2.194 | 0.01 | - | - | - | 2 B | 0.000 | | List_SetItem | 3,361,745.7734 ns | 98,390.1277 ns | 290,105.5114 ns | 3,505,120.8984 ns | 2.771 | 0.29 | - | - | - | 2 B | 0.000 | | PooledList_SetItem | 2,676,757.7524 ns | 1,228.8178 ns | 1,026.1185 ns | 2,676,991.4062 ns | 2.280 | 0.01 | - | - | - | 2 B | 0.000 | | RecyclableArrayList_SetItem | 2,547,059.6540 ns | 9,002.9953 ns | 7,980.9208 ns | 2,542,056.8359 ns | 2.170 | 0.01 | - | - | - | 2 B | 0.000 | -| RecyclableList_SetItem | 2,326,771.9866 ns | 3,186.0942 ns | 2,824.3895 ns | 2,326,636.7188 ns | 1.981 | 0.01 | - | - | - | 2 B | 0.000 | +| RecyclableLongList_SetItem | 2,326,771.9866 ns | 3,186.0942 ns | 2,824.3895 ns | 2,326,636.7188 ns | 1.981 | 0.01 | - | - | - | 2 B | 0.000 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | |------------------------------------------------------------ |----------:|----------:|----------:|------:|--------:|---------:|--------:|--------:|-----------:|------------:| | List_Add | 11.002 ms | 0.2189 ms | 0.2996 ms | 1.36 | 0.04 | 140.6250 | 93.7500 | 93.7500 | 16778117 B | 236,311.51 | | PooledList_Add | 8.117 ms | 0.0115 ms | 0.0102 ms | 1.00 | 0.00 | - | - | - | 71 B | 1.00 | | RecyclableArrayList_Add | 8.771 ms | 0.0169 ms | 0.0141 ms | 1.08 | 0.00 | - | - | - | 1233 B | 17.37 | -| RecyclableList_Add | 6.024 ms | 0.1101 ms | 0.1030 ms | 0.74 | 0.01 | - | - | - | 3317 B | 46.72 | +| RecyclableLongList_Add | 6.024 ms | 0.1101 ms | 0.1030 ms | 0.74 | 0.01 | - | - | - | 3317 B | 46.72 | | List_Add_WithCapacity | 3,724,682.0668 ns | 88,719.4866 ns | 260,198.8962 ns | 3,717,873.0469 ns | 3.326 | 0.09 | 50.7813 | 50.7813 | 50.7813 | 4000368 B | 1.000 | | PooledList_Add_WithCapacity | 2,575,618.8337 ns | 5,277.9410 ns | 4,678.7572 ns | 2,575,805.2734 ns | 2.194 | 0.01 | - | - | - | 60 B | 0.000 | | RecyclableArrayList_Add_WithCapacity | 2,785,606.2500 ns | 4,595.4354 ns | 4,073.7338 ns | 2,785,292.7734 ns | 2.372 | 0.01 | - | - | - | 42 B | 0.000 | -| RecyclableList_Add_WithCapacity | 2,878,685.9701 ns | 44,974.7609 ns | 35,113.3350 ns | 2,873,102.9297 ns | 2.453 | 0.03 | - | - | - | 170 B | 0.000 | +| RecyclableLongList_Add_WithCapacity | 2,878,685.9701 ns | 44,974.7609 ns | 35,113.3350 ns | 2,873,102.9297 ns | 2.453 | 0.03 | - | - | - | 170 B | 0.000 | | List_AddRangeWhenSourceIsArray | 1,966,607.1581 ns | 39,075.4380 ns | 100,165.2861 ns | 1,958,457.4219 ns | 1.721 | 0.07 | 42.9688 | 42.9688 | 42.9688 | 4000364 B | 1.000 | | PooledList_AddRangeWhenSourceIsArray | 972,973.4049 ns | 12,365.6121 ns | 11,566.8012 ns | 974,744.8242 ns | 0.829 | 0.01 | - | - | - | 57 B | 0.000 | | RecyclableArrayList_AddRangeWhenSourceIsArray | 1,098,651.7578 ns | 3,202.7829 ns | 2,995.8851 ns | 1,097,979.4922 ns | 0.936 | 0.00 | - | - | - | 41 B | 0.000 | -| RecyclableList_AddRangeWhenSourceIsArray | 1,023,799.0234 ns | 2,772.1984 ns | 2,457.4816 ns | 1,023,618.7500 ns | 0.872 | 0.00 | - | - | - | 169 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsArray | 1,023,799.0234 ns | 2,772.1984 ns | 2,457.4816 ns | 1,023,618.7500 ns | 0.872 | 0.00 | - | - | - | 169 B | 0.000 | | List_AddRangeWhenSourceIsList | 1,954,805.9462 ns | 37,631.3829 ns | 71,597.6170 ns | 1,947,531.6406 ns | 1.656 | 0.06 | 42.9688 | 42.9688 | 42.9688 | 4000363 B | 1.000 | | PooledList_AddRangeWhenSourceIsList | 963,657.7116 ns | 5,594.8195 ns | 5,233.3976 ns | 965,055.4199 ns | 0.820 | 0.01 | - | - | - | 57 B | 0.000 | | RecyclableArrayList_AddRangeWhenSourceIsList | 1,114,697.8385 ns | 4,015.8164 ns | 3,756.3971 ns | 1,115,863.6719 ns | 0.950 | 0.00 | - | - | - | 41 B | 0.000 | -| RecyclableList_AddRangeWhenSourceIsList | 1,008,424.0374 ns | 2,179.7502 ns | 1,932.2918 ns | 1,008,779.8828 ns | 0.859 | 0.00 | - | - | - | 169 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsList | 1,008,424.0374 ns | 2,179.7502 ns | 1,932.2918 ns | 1,008,779.8828 ns | 0.859 | 0.00 | - | - | - | 169 B | 0.000 | | List_AddRangeWhenSourceIsIList | 2,045,453.8895 ns | 40,653.4327 ns | 98,956.0738 ns | 2,040,041.7969 ns | 1.734 | 0.12 | 42.9688 | 42.9688 | 42.9688 | 4000366 B | 1.000 | | PooledList_AddRangeWhenSourceIsIList | 977,402.4790 ns | 4,165.1933 ns | 3,478.1251 ns | 978,420.3125 ns | 0.832 | 0.00 | - | - | - | 58 B | 0.000 | | RecyclableArrayList_AddRangeWhenSourceIsIList | 1,109,563.8477 ns | 4,480.2733 ns | 4,190.8505 ns | 1,108,281.9336 ns | 0.945 | 0.00 | - | - | - | 41 B | 0.000 | -| RecyclableList_AddRangeWhenSourceIsIList | 2,632,797.0052 ns | 3,899.2563 ns | 3,044.2828 ns | 2,632,267.1875 ns | 2.243 | 0.01 | - | - | - | 170 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsIList | 2,632,797.0052 ns | 3,899.2563 ns | 3,044.2828 ns | 2,632,267.1875 ns | 2.243 | 0.01 | - | - | - | 170 B | 0.000 | | PooledList_AddRangeWhenSourceIsIEnumerable | 28,102,425.4630 ns | 556,294.1092 ns | 779,846.6478 ns | 28,009,543.7500 ns | 24.007 | 0.74 | 2187.5000 | 1375.0000 | 218.7500 | 12000526 B | 3.000 | | List_AddRangeWhenSourceIsIEnumerable | 47,884,030.8442 ns | 955,391.3313 ns | 1,370,193.6989 ns | 48,152,172.7273 ns | 40.578 | 1.04 | 2272.7273 | 1363.6364 | 363.6364 | 20389288 B | 5.097 | | RecyclableArrayList_AddRangeWhenSourceIsIEnumerable | 27,102,237.2533 ns | 535,598.7947 ns | 595,316.6412 ns | 27,248,382.8125 ns | 23.009 | 0.61 | 2250.0000 | 1406.2500 | 250.0000 | 12000305 B | 3.000 | -| RecyclableList_AddRangeWhenSourceIsIEnumerable | 27,206,882.1023 ns | 517,997.7196 ns | 636,147.7188 ns | 27,262,153.1250 ns | 23.049 | 0.64 | 2218.7500 | 1468.7500 | 250.0000 | 12000361 B | 3.000 | +| RecyclableLongList_AddRangeWhenSourceIsIEnumerable | 27,206,882.1023 ns | 517,997.7196 ns | 636,147.7188 ns | 27,262,153.1250 ns | 23.049 | 0.64 | 2218.7500 | 1468.7500 | 250.0000 | 12000361 B | 3.000 | | PooledList_AddRangeWhenSourceIsPooledList | 958,132.3033 ns | 4,444.5508 ns | 3,939.9785 ns | 957,355.9570 ns | 0.816 | 0.00 | - | - | - | 57 B | 0.000 | | RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList | 1,068,135.4492 ns | 15,036.9473 ns | 12,556.5320 ns | 1,063,337.2070 ns | 0.910 | 0.01 | - | - | - | 41 B | 0.000 | -| RecyclableList_AddRangeWhenSourceIsRecyclableArrayList | 1,057,253.7249 ns | 2,697.5224 ns | 2,391.2834 ns | 1,057,475.9766 ns | 0.901 | 0.00 | - | - | - | 169 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsRecyclableArrayList | 1,057,253.7249 ns | 2,697.5224 ns | 2,391.2834 ns | 1,057,475.9766 ns | 0.901 | 0.00 | - | - | - | 169 B | 0.000 | | List_AddRange_WithCapacity | 2,022,247.3039 ns | 44,489.4467 ns | 129,071.9520 ns | 2,030,418.9453 ns | 1.767 | 0.06 | 39.0625 | 39.0625 | 39.0625 | 4000318 B | 1.000 | | PooledList_AddRange_WithCapacity | 965,635.9570 ns | 11,740.2154 ns | 10,981.8048 ns | 968,593.2617 ns | 0.822 | 0.01 | - | - | - | 57 B | 0.000 | | RecyclableArrayList_AddRange_WithCapacity | 1,044,438.5517 ns | 3,047.5353 ns | 2,544.8300 ns | 1,045,038.8672 ns | 0.890 | 0.00 | - | - | - | 41 B | 0.000 | -| RecyclableList_AddRange_WithCapacity | 1,028,953.5156 ns | 17,735.8499 ns | 14,810.2379 ns | 1,022,104.4922 ns | 0.876 | 0.01 | - | - | - | 169 B | 0.000 | +| RecyclableLongList_AddRange_WithCapacity | 1,028,953.5156 ns | 17,735.8499 ns | 14,810.2379 ns | 1,022,104.4922 ns | 0.876 | 0.01 | - | - | - | 169 B | 0.000 | | Array_Count | 0.0087 ns | 0.0110 ns | 0.0103 ns | 0.0024 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | List_Count | 1.2190 ns | 0.0046 ns | 0.0040 ns | 1.2173 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | PooledList_Count | 0.0037 ns | 0.0015 ns | 0.0012 ns | 0.0040 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | RecyclableArrayList_Count | 1.2096 ns | 0.0009 ns | 0.0008 ns | 1.2092 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | -| RecyclableList_Count | 0.0103 ns | 0.0008 ns | 0.0007 ns | 0.0102 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableLongList_Count | 0.0103 ns | 0.0008 ns | 0.0007 ns | 0.0102 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | Array_LongCount | 0.0021 ns | 0.0005 ns | 0.0004 ns | 0.0022 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | -| RecyclableList_LongCount | 0.0030 ns | 0.0004 ns | 0.0003 ns | 0.0031 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableLongList_LongCount | 0.0030 ns | 0.0004 ns | 0.0003 ns | 0.0031 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | List_Contains_FirstItems | 1,855,720,661.5385 ns | 16,632,292.4881 ns | 13,888,717.5079 ns | 1,850,307,400.0000 ns | 1,580.480 | 10.24 | - | - | - | 600 B | 0.000 | | PooledList_Contains_FirstItems | 1,854,542,814.2857 ns | 3,151,361.3032 ns | 2,793,599.7119 ns | 1,855,147,650.0000 ns | 1,579.345 | 5.84 | - | - | - | 600 B | 0.000 | -| RecyclableList_Contains_FirstItems | 1,842,715,553.8462 ns | 5,837,438.3043 ns | 4,874,525.3630 ns | 1,840,917,300.0000 ns | 1,569.420 | 6.23 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_Contains_FirstItems | 1,842,715,553.8462 ns | 5,837,438.3043 ns | 4,874,525.3630 ns | 1,840,917,300.0000 ns | 1,569.420 | 6.23 | - | - | - | 600 B | 0.000 | | List_Contains_LastItems | 56,934,350,853.3333 ns | 257,718,254.2389 ns | 241,069,814.9149 ns | 56,884,611,800.0000 ns | 48,516.143 | 270.39 | - | - | - | 600 B | 0.000 | | PooledList_Contains_LastItems | 57,262,565,633.3333 ns | 295,685,051.7358 ns | 276,583,980.8497 ns | 57,246,796,900.0000 ns | 48,801.131 | 237.64 | - | - | - | 600 B | 0.000 | -| RecyclableList_Contains_LastItems | 57,019,747,564.2857 ns | 264,241,719.1753 ns | 234,243,401.3552 ns | 57,059,345,900.0000 ns | 48,552.402 | 223.09 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_Contains_LastItems | 57,019,747,564.2857 ns | 264,241,719.1753 ns | 234,243,401.3552 ns | 57,059,345,900.0000 ns | 48,552.402 | 223.09 | - | - | - | 600 B | 0.000 | | Array_IndexOf_FirstItems | 1,874,810,260.0000 ns | 25,774,115.5271 ns | 24,109,123.6555 ns | 1,867,388,700.0000 ns | 1,597.521 | 22.54 | - | - | - | 600 B | 0.000 | | List_IndexOf_FirstItems | 1,864,704,725.0000 ns | 10,982,470.8154 ns | 8,574,390.8164 ns | 1,866,253,200.0000 ns | 1,588.702 | 10.94 | - | - | - | 600 B | 0.000 | | PooledList_IndexOf_FirstItems | 1,840,856,800.0000 ns | 4,046,589.8784 ns | 3,159,311.2037 ns | 1,839,944,050.0000 ns | 1,568.374 | 6.17 | - | - | - | 600 B | 0.000 | | RecyclableArrayList_IndexOf_FirstItems | 1,832,934,950.0000 ns | 1,729,308.0131 ns | 1,350,129.9476 ns | 1,832,475,300.0000 ns | 1,561.625 | 5.52 | - | - | - | 600 B | 0.000 | -| RecyclableList_IndexOf_FirstItems | 1,844,986,792.3077 ns | 3,209,050.1952 ns | 2,679,702.2516 ns | 1,843,943,600.0000 ns | 1,571.359 | 6.49 | - | - | - | 600 B | 0.000 | -| RecyclableList_LongIndexOf_FirstItems | 1,878,933,500.0000 ns | 17,118,131.6171 ns | 16,012,310.9355 ns | 1,870,009,600.0000 ns | 1,601.616 | 14.82 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_IndexOf_FirstItems | 1,844,986,792.3077 ns | 3,209,050.1952 ns | 2,679,702.2516 ns | 1,843,943,600.0000 ns | 1,571.359 | 6.49 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_LongIndexOf_FirstItems | 1,878,933,500.0000 ns | 17,118,131.6171 ns | 16,012,310.9355 ns | 1,870,009,600.0000 ns | 1,601.616 | 14.82 | - | - | - | 600 B | 0.000 | | List_IndexOf_LastItems | 57,303,575,140.0000 ns | 297,612,744.3548 ns | 278,387,145.7216 ns | 57,391,576,000.0000 ns | 48,792.865 | 303.56 | - | - | - | 600 B | 0.000 | | PooledList_IndexOf_LastItems | 57,032,399,106.6667 ns | 281,241,865.3817 ns | 263,073,815.3730 ns | 56,895,992,200.0000 ns | 48,566.622 | 322.88 | - | - | - | 600 B | 0.000 | -| RecyclableList_IndexOf_LastItems | 57,308,279,713.3333 ns | 272,718,905.6589 ns | 255,101,433.5602 ns | 57,297,645,400.0000 ns | 48,767.562 | 239.96 | - | - | - | 600 B | 0.000 | -| RecyclableList_LongIndexOf_LastItems | 57,198,323,460.0000 ns | 282,971,632.5816 ns | 264,691,840.6850 ns | 57,198,418,300.0000 ns | 48,669.107 | 304.11 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_IndexOf_LastItems | 57,308,279,713.3333 ns | 272,718,905.6589 ns | 255,101,433.5602 ns | 57,297,645,400.0000 ns | 48,767.562 | 239.96 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_LongIndexOf_LastItems | 57,198,323,460.0000 ns | 282,971,632.5816 ns | 264,691,840.6850 ns | 57,198,418,300.0000 ns | 48,669.107 | 304.11 | - | - | - | 600 B | 0.000 | | List_Remove_FirstItems | 5,023,030,012.5000 ns | 82,615,052.4052 ns | 107,422,903.9562 ns | 5,000,417,000.0000 ns | 4,295.171 | 116.37 | - | - | - | 4000656 B | 1.000 | | PooledList_Remove_FirstItems | 4,750,601,721.4286 ns | 17,989,258.8487 ns | 15,947,009.4038 ns | 4,747,622,550.0000 ns | 4,043.997 | 21.26 | - | - | - | 992 B | 0.000 | | RecyclableArrayList_Remove_FirstItems | 4,950,634,900.0000 ns | 33,761,094.2570 ns | 29,928,330.6849 ns | 4,936,519,050.0000 ns | 4,218.075 | 23.90 | - | - | - | 640 B | 0.000 | -| RecyclableList_Remove_FirstItems | 5,020,383,250.0000 ns | 100,209,734.5497 ns | 115,401,679.3276 ns | 5,029,163,750.0000 ns | 4,306.772 | 86.95 | - | - | - | 952 B | 0.000 | +| RecyclableLongList_Remove_FirstItems | 5,020,383,250.0000 ns | 100,209,734.5497 ns | 115,401,679.3276 ns | 5,029,163,750.0000 ns | 4,306.772 | 86.95 | - | - | - | 952 B | 0.000 | | List_Remove_LastItems | 56,593,869,333.3333 ns | 258,693,958.6041 ns | 241,982,489.3835 ns | 56,513,091,800.0000 ns | 48,189.227 | 272.02 | - | - | - | 4000656 B | 1.000 | | PooledList_Remove_LastItems | 56,465,443,542.8571 ns | 99,443,506.1766 ns | 88,154,078.0245 ns | 56,458,364,050.0000 ns | 48,092.866 | 216.09 | - | - | - | 992 B | 0.000 | | RecyclableArrayList_Remove_LastItems | 57,432,405,146.6667 ns | 264,062,625.8226 ns | 247,004,344.0307 ns | 57,409,508,000.0000 ns | 48,952.707 | 234.83 | - | - | - | 640 B | 0.000 | -| RecyclableList_Remove_LastItems | 57,077,678,728.5714 ns | 210,992,675.8689 ns | 187,039,511.4398 ns | 57,038,290,150.0000 ns | 48,614.503 | 242.79 | - | - | - | 952 B | 0.000 | +| RecyclableLongList_Remove_LastItems | 57,077,678,728.5714 ns | 210,992,675.8689 ns | 187,039,511.4398 ns | 57,038,290,150.0000 ns | 48,614.503 | 242.79 | - | - | - | 952 B | 0.000 | | List_RemoveAt_FirstItems | 4,517,669,893.3333 ns | 77,360,576.7460 ns | 72,363,131.4861 ns | 4,546,302,700.0000 ns | 3,850.590 | 62.34 | - | - | - | 4000656 B | 1.000 | | PooledList_RemoveAt_FirstItems | 4,439,417,700.0000 ns | 40,915,112.8948 ns | 38,272,021.9874 ns | 4,418,366,400.0000 ns | 3,779.751 | 38.92 | - | - | - | 992 B | 0.000 | | RecyclableArrayList_RemoveAt_FirstItems | 4,558,102,892.8571 ns | 25,076,973.8515 ns | 22,230,084.1404 ns | 4,559,174,800.0000 ns | 3,882.107 | 16.61 | - | - | - | 640 B | 0.000 | -| RecyclableList_RemoveAt_FirstItems | 4,617,033,766.6667 ns | 85,061,197.3076 ns | 79,566,296.7373 ns | 4,632,994,800.0000 ns | 3,928.768 | 72.17 | - | - | - | 952 B | 0.000 | +| RecyclableLongList_RemoveAt_FirstItems | 4,617,033,766.6667 ns | 85,061,197.3076 ns | 79,566,296.7373 ns | 4,632,994,800.0000 ns | 3,928.768 | 72.17 | - | - | - | 952 B | 0.000 | | List_RemoveAt_LastItems | 2,005,211.3281 ns | 40,078.0351 ns | 84,538.1787 ns | 2,010,579.1016 ns | 1.725 | 0.08 | 42.9688 | 42.9688 | 42.9688 | 4000306 B | 1.000 | | PooledList_RemoveAt_LastItems | 1,047,436.1654 ns | 5,655.2027 ns | 5,289.8801 ns | 1,044,690.9180 ns | 0.892 | 0.01 | - | - | - | 58 B | 0.000 | | RecyclableArrayList_RemoveAt_LastItems | 1,256,380.8969 ns | 9,142.3285 ns | 7,634.2584 ns | 1,254,354.9805 ns | 1.070 | 0.01 | - | - | - | 41 B | 0.000 | -| RecyclableList_RemoveAt_LastItems | 1,621,193.5826 ns | 20,030.6198 ns | 17,756.6227 ns | 1,620,789.8438 ns | 1.379 | 0.02 | - | - | - | 353 B | 0.000 | +| RecyclableLongList_RemoveAt_LastItems | 1,621,193.5826 ns | 20,030.6198 ns | 17,756.6227 ns | 1,620,789.8438 ns | 1.379 | 0.02 | - | - | - | 353 B | 0.000 | diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 850a465..e1a841c 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -249,7 +249,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public void AddRange(RecyclableList items) + public void AddRange(RecyclableLongList items) { if (items.LongCount == 0) { @@ -259,12 +259,12 @@ namespace Recyclable.Collections var sourceItemsCount = items.LongCount; if (sourceItemsCount > int.MaxValue) { - ThrowArgumentOutOfRangeException($"The number of items exceeds the maximum capacity of {nameof(RecyclableArrayList)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableList)}, instead"); + ThrowArgumentOutOfRangeException($"The number of items exceeds the maximum capacity of {nameof(RecyclableArrayList)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); } if (_count + sourceItemsCount > int.MaxValue) { - ThrowArgumentOutOfRangeException($"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableArrayList)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableList)}, instead"); + ThrowArgumentOutOfRangeException($"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableArrayList)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); } int targetCapacity = _count + (int)sourceItemsCount; diff --git a/Recyclable.Collections/RecyclableList.IndexOfHelpers.cs b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs similarity index 88% rename from Recyclable.Collections/RecyclableList.IndexOfHelpers.cs rename to Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs index 2026354..f702481 100644 --- a/Recyclable.Collections/RecyclableList.IndexOfHelpers.cs +++ b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs @@ -3,14 +3,14 @@ using Recyclable.Collections.Parallel; namespace Recyclable.Collections { - public partial class RecyclableList + public partial class RecyclableLongList { public static class IndexOfHelpers { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] // [MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static void ScheduleIndexOfTask(IndexOfSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) + private static void ScheduleIndexOfTask(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) { _ = context.AllDoneSignal.AddParticipant(); // & WAS SLOWER @@ -123,7 +123,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] // [MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static void ScheduleIndexOfTaskSingle(IndexOfSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) + private static void ScheduleIndexOfTaskSingle(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) { _ = context.AllDoneSignal.AddParticipant(); // & WAS SLOWER @@ -162,7 +162,7 @@ namespace Recyclable.Collections private const int BlocksForEachTask = 4; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long IndexOfParallel(RecyclableList list, in T item) + public static long IndexOfParallel(RecyclableLongList list, in T item) { // & WAS SLOWER //long itemsCount = Array.IndexOf(list._memoryBlocks[0], item, 0, (int)Math.Min(list._blockSize, list._longCount)); @@ -214,25 +214,43 @@ namespace Recyclable.Collections } } - int itemIndex = Array.IndexOf(list._memoryBlocks[list._lastBlockWithData], item, 0, list._nextItemIndex > 0 ? list._nextItemIndex : list._blockSize); - if (itemIndex >= 0) + if (!context._isItemFound) { - context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex); + int itemIndex = Array.IndexOf(list._memoryBlocks[list._lastBlockWithData], item, 0, list._nextItemIndex > 0 ? list._nextItemIndex : list._blockSize); + if (!context._isItemFound && itemIndex >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex); + } } context.AllDoneSignal.SignalAndWait(); if (context.Exception == null) { - return context.FoundItemIndex; + try + { + return context.FoundItemIndex; + } + finally + { + context.Dispose(); + } } - context.Exception.CaptureAndRethrow(); + try + { + context.Exception.CaptureAndRethrow(); + } + finally + { + context.Dispose(); + } + return RecyclableDefaults.ItemNotFoundIndexLong; } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long IndexOfSequential(RecyclableList list, in T item) //, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + public static long IndexOfSequential(RecyclableLongList list, in T item) //, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) { int itemIndex; // & WAS SLOWER diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableLongList.cs similarity index 95% rename from Recyclable.Collections/RecyclableList.cs rename to Recyclable.Collections/RecyclableLongList.cs index c397ea1..4d914e9 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -8,7 +8,7 @@ using Recyclable.Collections.Pools; namespace Recyclable.Collections { - public partial class RecyclableList : IDisposable, IList + public partial class RecyclableLongList : IDisposable, IList { private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); @@ -55,7 +55,7 @@ namespace Recyclable.Collections public int NextItemIndex => _nextItemIndex; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - private static void CopyItems(RecyclableList list, long index) + private static void CopyItems(RecyclableLongList list, long index) { T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; @@ -219,10 +219,10 @@ namespace Recyclable.Collections /// /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. /// - /// that needs to be resized. + /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. /// Pre-calculated bit shifting value for left & right shift operations against. - /// The minimum no. of items MUST be able to store after . + /// The minimum no. of items MUST be able to store after . /// /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -235,7 +235,7 @@ namespace Recyclable.Collections /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + protected static long Resize(RecyclableLongList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = list._reservedBlockCount; @@ -329,7 +329,7 @@ namespace Recyclable.Collections // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] // & WAS SLOWER // [MethodImpl(MethodImplOptions.NoInlining)] - //private static void Iterate(RecyclableList list, in ParallelSynchronizationContext context, in T itemToFind) + //private static void Iterate(RecyclableLongList list, in ParallelSynchronizationContext context, in T itemToFind) //{ // // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. @@ -357,7 +357,7 @@ namespace Recyclable.Collections //} [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) + protected static void SetupBlockArrayPooling(RecyclableLongList list, int blockSize, ArrayPool? blockArrayPool = null) { list._blockSize = blockSize; list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); @@ -367,7 +367,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - protected static long EnsureCapacity(RecyclableList list, long requestedCapacity) + protected static long EnsureCapacity(RecyclableLongList list, long requestedCapacity) { long newCapacity = list._capacity > 0 ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) @@ -387,7 +387,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -416,7 +416,7 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -565,7 +565,7 @@ namespace Recyclable.Collections _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } - public void AddRange(RecyclableList items) + public void AddRange(RecyclableLongList items) { if (items.LongCount == 0) { @@ -741,9 +741,9 @@ namespace Recyclable.Collections public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) { - if (source is RecyclableList sourceRecyclableList) + if (source is RecyclableLongList sourceRecyclableLongList) { - AddRange(sourceRecyclableList); + AddRange(sourceRecyclableLongList); return; } @@ -1055,7 +1055,7 @@ namespace Recyclable.Collections IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - ~RecyclableList() + ~RecyclableLongList() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(); diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableLongListExtensions.cs similarity index 88% rename from Recyclable.Collections/RecyclableListExtensions.cs rename to Recyclable.Collections/RecyclableLongListExtensions.cs index fedf113..b4073e4 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableLongListExtensions.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public static class RecyclableListExtensions + public static class RecyclableLongListExtensions { private const int _minPooledArraySize = RecyclableDefaults.MinPooledArrayLength; private const long ItemNotFoundIndexLong = -1L; @@ -46,7 +46,7 @@ namespace Recyclable.Collections } //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); + //public static bool Contains(this RecyclableLongList arrays, T item) => arrays.Any(x => x.Contains(item)); public static IEnumerable Enumerate(this T[][] arrays, int chunkSize, long totalCount) { @@ -83,7 +83,7 @@ namespace Recyclable.Collections // => comparer.Compare(value, maxValue) > 0 ? maxValue : value; //[MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this RecyclableList arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) + public static long LongIndexOf(this RecyclableLongList arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) { for (var itemIdx = 0L; itemIdx < itemsCount; itemIdx++) { @@ -126,10 +126,10 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + public static RecyclableLongList ToRecyclableLongList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + public static RecyclableLongList ToRecyclableLongList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RecyclableArrayList ToRecyclableArrayList(this IEnumerable values) => new(values); @@ -144,7 +144,7 @@ namespace Recyclable.Collections public static RecyclableArrayList ToRecyclableArrayList(this RecyclableArrayList values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IEnumerable values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize); + public static RecyclableLongList ToRecyclableLongList(this IEnumerable values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] RentArrayFromPool(this int minSize, ArrayPool arrayPool) => (minSize >= _minPooledArraySize) diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs index c3e63bd..b947e9d 100644 --- a/Recyclable.Collections/RecyclableSortedList.cs +++ b/Recyclable.Collections/RecyclableSortedList.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections { - public class RecyclableSortedList : RecyclableList<(TKey Key, TValue Value)> + public class RecyclableSortedList : RecyclableLongList<(TKey Key, TValue Value)> where TKey : notnull { private static readonly IComparer<(TKey, TValue)> _comparer = new FrequencyTupleComparer(); diff --git a/Recyclable.Collections/RecyclableStack.cs b/Recyclable.Collections/RecyclableStack.cs index fb67715..50d7140 100644 --- a/Recyclable.Collections/RecyclableStack.cs +++ b/Recyclable.Collections/RecyclableStack.cs @@ -7,7 +7,7 @@ namespace Recyclable.Collections private bool _disposedValue; private readonly int _blockSize; - protected RecyclableList List { get; } + protected RecyclableLongList List { get; } public int Count => List.Count; public long LongCount { @@ -32,7 +32,7 @@ namespace Recyclable.Collections List = new(list, blockSize); } - public RecyclableStack(RecyclableList list, int blockSize = RecyclableDefaults.BlockSize) + public RecyclableStack(RecyclableLongList list, int blockSize = RecyclableDefaults.BlockSize) { _blockSize = blockSize; List = new(list, blockSize); diff --git a/Recyclable.Collections/SearchConfiguration.cs b/Recyclable.Collections/SearchConfiguration.cs index 622ae2a..0150a28 100644 --- a/Recyclable.Collections/SearchConfiguration.cs +++ b/Recyclable.Collections/SearchConfiguration.cs @@ -16,7 +16,7 @@ namespace Recyclable.Collections // List = list; // } - public static SearchConfiguration, TItem> CreateFrom(RecyclableList list, in TItem itemToFind) => new() + public static SearchConfiguration, TItem> CreateFrom(RecyclableLongList list, in TItem itemToFind) => new() { List = list, ItemToFind = itemToFind, diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 66d6c01..192f624 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -6,7 +6,7 @@ namespace Recyclable.CollectionsTests public class RecyclableArrayListTests { [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -25,9 +25,9 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataRecyclableArrayList); } - else if (testData is RecyclableList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableLongList) { - list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableLongList); } else if (testData is IList testDataIList) { @@ -49,7 +49,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) { // Prepare @@ -68,9 +68,9 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataRecyclableArrayList); } - else if (testData is RecyclableList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableLongList) { - list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableLongList); } else if (testData is IList testDataIList) { @@ -92,7 +92,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -114,10 +114,10 @@ namespace Recyclable.CollectionsTests list.AddRange(testDataRecyclableArrayList); list.AddRange(testDataRecyclableArrayList); } - else if (testData is RecyclableList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableLongList) { - list.AddRange(testDataRecyclableList); - list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableLongList); + list.AddRange(testDataRecyclableLongList); } else if (testData is IList testDataIList) { @@ -147,7 +147,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -166,7 +166,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -186,7 +186,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount) { // Act @@ -203,7 +203,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -220,7 +220,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -232,7 +232,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) { // Act @@ -244,7 +244,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable testData, long itemsCount) { // Act @@ -257,7 +257,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount) { // Act @@ -269,7 +269,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ContainsShouldFindAllItems(string testCase, IEnumerable testData, long itemsCount) { if (itemsCount == 0) @@ -301,7 +301,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount) { if (itemsCount == 0) @@ -325,7 +325,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -341,7 +341,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -368,7 +368,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -394,7 +394,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -409,7 +409,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { if (itemsCount == 0) @@ -447,7 +447,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -467,7 +467,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -489,7 +489,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -511,7 +511,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) { // Prepare @@ -530,7 +530,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void RemoveFromTheEndShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) { // Act & Validate diff --git a/Recyclable.CollectionsTests/RecyclableListTestData.cs b/Recyclable.CollectionsTests/RecyclableLongListTestData.cs similarity index 91% rename from Recyclable.CollectionsTests/RecyclableListTestData.cs rename to Recyclable.CollectionsTests/RecyclableLongListTestData.cs index 6163458..ac7111b 100644 --- a/Recyclable.CollectionsTests/RecyclableListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTestData.cs @@ -2,7 +2,7 @@ namespace Recyclable.CollectionsTests { - public static class RecyclableListTestData + public static class RecyclableLongListTestData { private static IEnumerable DoCreateTestData(long itemsCount) { @@ -50,8 +50,8 @@ namespace Recyclable.CollectionsTests { yield return new object[] { - $"RecyclableList(itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize})", - CreateTestData(itemsCount).ToRecyclableList(sourceBlockSize), + $"RecyclableLongList(itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize})", + CreateTestData(itemsCount).ToRecyclableLongList(sourceBlockSize), itemsCount }; } @@ -69,7 +69,7 @@ namespace Recyclable.CollectionsTests new object[] { "int[]", Array.Empty() }, new object[] { "List", Array.Empty().ToList() }, new object[] { "RecyclableArrayList", Array.Empty().ToRecyclableArrayList() }, - new object[] { "RecyclableList", Array.Empty().ToRecyclableList() }, + new object[] { "RecyclableLongList", Array.Empty().ToRecyclableLongList() }, new object[] { "IList", new CustomIList(Array.Empty()) }, new object[] { "IEnumerable with non-enumerated count", Array.Empty().AsEnumerable() }, new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(Array.Empty()) } diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs similarity index 69% rename from Recyclable.CollectionsTests/RecyclableListTests.cs rename to Recyclable.CollectionsTests/RecyclableLongListTests.cs index af4a0aa..4f8669c 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -3,14 +3,14 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - public class RecyclableListTests + public class RecyclableLongListTests { [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); // Act if (testData is long[] testDataArray) @@ -25,7 +25,7 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataRecyclableArrayList); } - else if (testData is RecyclableList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableList) { list.AddRange(testDataRecyclableList); } @@ -50,11 +50,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) { // Prepare - using var list = new RecyclableList(); + using var list = new RecyclableLongList(); // Act if (testData is long[] testDataArray) @@ -69,7 +69,7 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataRecyclableArrayList); } - else if (testData is RecyclableList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableList) { list.AddRange(testDataRecyclableList); } @@ -93,11 +93,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); // Act if (testData is long[] testDataArray) @@ -115,7 +115,7 @@ namespace Recyclable.CollectionsTests list.AddRange(testDataRecyclableArrayList); list.AddRange(testDataRecyclableArrayList); } - else if (testData is RecyclableList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableList) { list.AddRange(testDataRecyclableList); list.AddRange(testDataRecyclableList); @@ -148,14 +148,14 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare testData = testData.Concat(testData).ToArray(); // Act - using var list = new RecyclableList(minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); foreach (var index in testData) { list.Add(index); @@ -167,11 +167,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); // Act foreach (var index in testData) @@ -185,11 +185,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void GetByIndexShouldReturnCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act & Validate foreach (var item in testData) @@ -199,12 +199,12 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] //[InlineData("Debugging case", new long[1] { 1 }, 1, 2)] public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Act - using var list = new RecyclableList(minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); list.AddRange(testData); foreach (var index in testData) { @@ -217,11 +217,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); _ = list.LongCount.Should().Be(itemsCount); // Act @@ -234,11 +234,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); list.Dispose(); // Act @@ -246,14 +246,14 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare testData = testData.Concat(testData).ToArray(); // Act - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Validate _ = list.LongCount.Should().Be(itemsCount * 2); @@ -261,11 +261,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Act - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Validate _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); @@ -274,11 +274,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Act - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Validate _ = list.LongCount.Should().Be(itemsCount); @@ -286,7 +286,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ContainsShouldFindAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare @@ -296,7 +296,7 @@ namespace Recyclable.CollectionsTests return; } - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -308,11 +308,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.TargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.TargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ContainsShouldNotFindAnythingWhenListEmpty(long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); + using var list = new RecyclableLongList(minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); // Validate _ = list.Contains(0).Should().BeFalse(); @@ -320,7 +320,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare @@ -330,7 +330,7 @@ namespace Recyclable.CollectionsTests return; } - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -345,12 +345,12 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] //[InlineData("Debuging case", new[] { 1L, 2, }, 2, 2)] public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act var actual = new long[itemsCount]; @@ -359,11 +359,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void DisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act list.Dispose(); @@ -374,11 +374,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act var actual = new List(); @@ -396,7 +396,7 @@ namespace Recyclable.CollectionsTests public void IndexOfShouldNotFindAnythingWhenListEmpty() { // Prepare - using var list = new RecyclableList(); + using var list = new RecyclableLongList(); // Validate _ = list.IndexOf(0).Should().Be(-1); @@ -404,7 +404,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare @@ -415,7 +415,7 @@ namespace Recyclable.CollectionsTests } // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -430,11 +430,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act & Validate foreach (var item in testData) @@ -445,7 +445,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { if (itemsCount == 0) @@ -454,7 +454,7 @@ namespace Recyclable.CollectionsTests } // Act - using RecyclableList list = new(testData, minBlockSize: targetBlockSize); + using RecyclableLongList list = new(testData, minBlockSize: targetBlockSize); list.QuickSort(); // Validate @@ -483,22 +483,22 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act _ = Assert.Throws(() => list.Insert(0, -1)); } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void InsertShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act _ = Assert.Throws(() => list.Insert(0, -1)); @@ -508,7 +508,7 @@ namespace Recyclable.CollectionsTests public void LongIndexOfShouldNotFindAnythingWhenListEmpty() { // Prepare - using var list = new RecyclableList(); + using var list = new RecyclableLongList(); // Validate _ = list.LongIndexOf(0).Should().Be(-1); @@ -516,7 +516,7 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void LongIndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { if (itemsCount == 0) @@ -525,7 +525,7 @@ namespace Recyclable.CollectionsTests } // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -540,11 +540,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void LongIndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act & Validate foreach (var item in testData) @@ -558,11 +558,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -580,11 +580,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -602,11 +602,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -621,11 +621,11 @@ namespace Recyclable.CollectionsTests } [Theory] - [MemberData(nameof(RecyclableListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableListTestData))] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void RemoveFromTheEndShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) { // Prepare - using var list = new RecyclableList(testData, minBlockSize: targetBlockSize); + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); var expectedData = testData.ToArray(); int removedCount = 0; foreach (var item in testData.Reverse()) diff --git a/Recyclable.CollectionsTests/RecyclableQueueTests.cs b/Recyclable.CollectionsTests/RecyclableQueueTests.cs index 4940eb9..fbbeea0 100644 --- a/Recyclable.CollectionsTests/RecyclableQueueTests.cs +++ b/Recyclable.CollectionsTests/RecyclableQueueTests.cs @@ -72,7 +72,7 @@ namespace Recyclable.CollectionsTests list.Enqueue(_testData[itemIdx]); } - var dequeuedItems = new RecyclableList(); + var dequeuedItems = new RecyclableLongList(); while (list.LongCount > 0) { dequeuedItems.Add(list.Dequeue()); @@ -106,7 +106,7 @@ namespace Recyclable.CollectionsTests using var list = new RecyclableQueue(_testData); // Act - var actual = new RecyclableList(); + var actual = new RecyclableLongList(); using var enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { diff --git a/Recyclable.CollectionsTests/RecyclableStackTests.cs b/Recyclable.CollectionsTests/RecyclableStackTests.cs index e6330a0..dffdd52 100644 --- a/Recyclable.CollectionsTests/RecyclableStackTests.cs +++ b/Recyclable.CollectionsTests/RecyclableStackTests.cs @@ -14,7 +14,7 @@ namespace Recyclable.CollectionsTests using var list = new RecyclableStack(_testData); // Act - var actual = new RecyclableList(); + var actual = new RecyclableLongList(); using var enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { @@ -90,7 +90,7 @@ namespace Recyclable.CollectionsTests list.Push(_testData[itemIdx]); } - var dequeuedItems = new RecyclableList(); + var dequeuedItems = new RecyclableLongList(); while (list.LongCount > 0) { dequeuedItems.Add(list.Pop()); From cf0c86d353e958748e9b617a221ba0066a070455 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 20:19:56 +0200 Subject: [PATCH 281/305] Tiny cleanup --- Recyclable.Collections/RecyclableArrayList.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index e1a841c..f86ff18 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -4,9 +4,8 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public class RecyclableArrayList : IEnumerable, IList, IDisposable + public class RecyclableArrayList : IList, IDisposable { - private const int ItemNotFound = -1; private static readonly ArrayPool _arrayPool = ArrayPool.Create(); protected static readonly bool NeedsClearing = !typeof(T).IsValueType; @@ -405,7 +404,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(T itemToFind) => _count > 0 ? Array.IndexOf(_memoryBlock, itemToFind, 0, _count) : ItemNotFound; + public int IndexOf(T itemToFind) => _count > 0 ? Array.IndexOf(_memoryBlock, itemToFind, 0, _count) : RecyclableDefaults.ItemNotFoundIndex; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void Insert(int index, T item) From 1a723c6f4fdf37d0c182f82b679a47c731aae959 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 20:43:33 +0200 Subject: [PATCH 282/305] RecyclableArrayList renamed to RecyclableList --- .../Benchmarks/Add.cs | 4 +- .../Benchmarks/AddRangeWhenSourceIsArray.cs | 4 +- .../AddRangeWhenSourceIsIEnumerable.cs | 4 +- .../Benchmarks/AddRangeWhenSourceIsIList.cs | 4 +- .../Benchmarks/AddRangeWhenSourceIsList.cs | 4 +- .../AddRangeWhenSourceIsSameType.cs | 10 +-- .../Benchmarks/AddRange_WithCapacity.cs | 4 +- .../Benchmarks/Add_WithCapacity.cs | 4 +- .../Benchmarks/Contains_FirstItems.cs | 4 +- .../Benchmarks/Contains_LastItems.cs | 4 +- .../Benchmarks/Count.cs | 4 +- .../Benchmarks/Create.cs | 4 +- .../Benchmarks/Create_WithCapacity.cs | 4 +- .../Benchmarks/GetItem..cs | 4 +- .../Benchmarks/IndexOf_BestAndWorstCases.cs | 4 +- .../Benchmarks/IndexOf_FirstItems.cs | 4 +- .../Benchmarks/IndexOf_LastItems.cs | 4 +- .../RecyclableCollections.Benchmarks.cs | 2 +- .../Benchmarks/RemoveAt_FirstItems.cs | 4 +- .../Benchmarks/RemoveAt_LastItems.cs | 4 +- .../Benchmarks/Remove_FirstItems.cs | 4 +- .../Benchmarks/Remove_LastItems.cs | 4 +- .../Benchmarks/SetItem.cs | 4 +- ...leLongListPoc.Benchmarks.EnsureCapacity.cs | 18 ++--- ...ecyclableLongListPoc.Benchmarks.IndexOf.cs | 24 +++---- .../POC/RecyclableLongListPoc.Benchmarks.cs | 2 +- .../RecyclableBenchmarkBase.cs | 18 ++--- .../Results/AllInOneBenchmarks.txt | 38 +++++------ Recyclable.Collections/GlobalSuppressions.cs | 2 +- Recyclable.Collections/ListExtensions.cs | 8 +-- ...cyclableArrayList.cs => RecyclableList.cs} | 30 ++++----- Recyclable.Collections/RecyclableLongList.cs | 6 +- .../RecyclableLongListExtensions.cs | 8 +-- Recyclable.Collections/RecyclableQueue.cs | 2 +- ...rayListTests.cs => RecyclableListTests.cs} | 66 +++++++++---------- .../RecyclableLongListTestData.cs | 4 +- .../RecyclableLongListTests.cs | 20 +++--- 37 files changed, 171 insertions(+), 171 deletions(-) rename Recyclable.Collections/{RecyclableArrayList.cs => RecyclableList.cs} (91%) rename Recyclable.CollectionsTests/{RecyclableArrayListTests.cs => RecyclableListTests.cs} (88%) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs index 12778f3..18aeaf4 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs @@ -26,11 +26,11 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableArrayList_Add() + public void RecyclableList_Add() { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs index a5bf6e3..542f5a7 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs @@ -18,10 +18,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableArrayList_AddRangeWhenSourceIsArray() + public void RecyclableList_AddRangeWhenSourceIsArray() { var data = TestObjects; - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs index c212673..d16e91c 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs @@ -18,10 +18,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() + public void RecyclableList_AddRangeWhenSourceIsIEnumerable() { var data = TestObjectsAsIEnumerable; - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs index c734da0..54a660b 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs @@ -18,10 +18,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableArrayList_AddRangeWhenSourceIsIList() + public void RecyclableList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsRecyclableLongList; - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs index 0ca986c..580ebbb 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs @@ -18,10 +18,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableArrayList_AddRangeWhenSourceIsList() + public void RecyclableList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs index c33a4e5..9f28def 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs @@ -11,16 +11,16 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() + public void RecyclableList_AddRangeWhenSourceIsRecyclableList() { - var data = TestObjectsAsRecyclableArrayList; - using var list = new RecyclableArrayList(); + var data = TestObjectsAsRecyclableList; + using var list = new RecyclableList(); list.AddRange(data); } - public void RecyclableLongList_AddRangeWhenSourceIsRecyclableArrayList() + public void RecyclableLongList_AddRangeWhenSourceIsRecyclableList() { - var data = TestObjectsAsRecyclableArrayList; + var data = TestObjectsAsRecyclableList; using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs index 37fa179..8616914 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs @@ -20,11 +20,11 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - public void RecyclableArrayList_AddRange_WithCapacity() + public void RecyclableList_AddRange_WithCapacity() { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(dataCount); + using var list = new RecyclableList(dataCount); list.AddRange(data); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs index 9734b20..0838d36 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs @@ -26,11 +26,11 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableArrayList_Add_WithCapacity() + public void RecyclableList_Add_WithCapacity() { var data = TestObjects; var dataCount = TestObjectCount; - using var list = new RecyclableArrayList(dataCount); + using var list = new RecyclableList(dataCount); for (var i = 0; i < dataCount; i++) { list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs index 6bf4e1d..07c1901 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs @@ -35,10 +35,10 @@ } } - public void RecyclableArrayList_Contains_FirstItems() + public void RecyclableList_Contains_FirstItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableArrayList; + var list = TestObjectsAsRecyclableList; var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs index 8f41041..30a5dfe 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs @@ -35,10 +35,10 @@ } } - public void RecyclableArrayList_Contains_LastItems() + public void RecyclableList_Contains_LastItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableArrayList; + var list = TestObjectsAsRecyclableList; var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs index 45f18bb..7c30a64 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs @@ -20,9 +20,9 @@ DoNothing(data.Count); } - public void RecyclableArrayList_Count() + public void RecyclableList_Count() { - var data = TestObjectsAsRecyclableArrayList; + var data = TestObjectsAsRecyclableList; DoNothing(data.Count); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs index 2aacb8b..cccc65a 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs @@ -16,9 +16,9 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - public static void RecyclableArrayList_Create() + public static void RecyclableList_Create() { - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); DoNothing(list); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs index bceb830..372f2da 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs @@ -22,9 +22,9 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - public void RecyclableArrayList_Create_WithCapacity() + public void RecyclableList_Create_WithCapacity() { - using var list = new RecyclableArrayList(TestObjectCount); + using var list = new RecyclableList(TestObjectCount); DoNothing(list); } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs index 2388a24..b8fcb3f 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -32,9 +32,9 @@ } } - public void RecyclableArrayList_GetItem() + public void RecyclableList_GetItem() { - var data = TestObjectsAsRecyclableArrayList; + var data = TestObjectsAsRecyclableList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs index e1a4710..e1d182c 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs @@ -38,10 +38,10 @@ } } - public void RecyclableArrayList_IndexOf_BestAndWorstCases() + public void RecyclableList_IndexOf_BestAndWorstCases() { var data = TestObjects; - var list = TestObjectsAsRecyclableArrayList; + var list = TestObjectsAsRecyclableList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs index 992d33a..f552702 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs @@ -35,10 +35,10 @@ } } - public void RecyclableArrayList_IndexOf_FirstItems() + public void RecyclableList_IndexOf_FirstItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableArrayList; + var list = TestObjectsAsRecyclableList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs index 3d8b937..1b7681f 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs @@ -35,10 +35,10 @@ } } - public void RecyclableArrayList_IndexOf_LastItems() + public void RecyclableList_IndexOf_LastItems() { var data = TestObjects; - var list = TestObjectsAsRecyclableArrayList; + var list = TestObjectsAsRecyclableList; var dataCount = TestObjectCountForSlowMethods; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 5bb8227..a17bae3 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -61,7 +61,7 @@ namespace Recyclable.Collections.Benchmarks RecyclableCollectionsBenchmarkSource.Array, RecyclableCollectionsBenchmarkSource.List, RecyclableCollectionsBenchmarkSource.PooledList, - RecyclableCollectionsBenchmarkSource.RecyclableArrayList, + RecyclableCollectionsBenchmarkSource.RecyclableList, RecyclableCollectionsBenchmarkSource.RecyclableLongList )] public override RecyclableCollectionsBenchmarkSource BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs index 8959972..f4edfe8 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs @@ -26,10 +26,10 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableArrayList_RemoveAt_FirstItems() + public void RecyclableList_RemoveAt_FirstItems() { var data = TestObjects; - using var list = new RecyclableArrayList(data); + using var list = new RecyclableList(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs index 77a3576..30e6af3 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs @@ -26,10 +26,10 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableArrayList_RemoveAt_LastItems() + public void RecyclableList_RemoveAt_LastItems() { var data = TestObjects; - using var list = new RecyclableArrayList(data); + using var list = new RecyclableList(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs index ae15e61..3f18e65 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs @@ -26,10 +26,10 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableArrayList_Remove_FirstItems() + public void RecyclableList_Remove_FirstItems() { var data = TestObjects; - using var list = new RecyclableArrayList(data); + using var list = new RecyclableList(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs index a727493..dfa2bef 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs @@ -26,10 +26,10 @@ namespace Recyclable.Collections.Benchmarks } } - public void RecyclableArrayList_Remove_LastItems() + public void RecyclableList_Remove_LastItems() { var data = TestObjects; - using var list = new RecyclableArrayList(data); + using var list = new RecyclableList(data); var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) { diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs index 115a391..aad2f78 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs @@ -33,9 +33,9 @@ } } - public void RecyclableArrayList_SetItem() + public void RecyclableList_SetItem() { - var data = TestObjectsAsRecyclableArrayList; + var data = TestObjectsAsRecyclableList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs index 046bc7d..3f0146e 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs @@ -481,7 +481,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -697,9 +697,9 @@ namespace Recyclable.Collections.Benchmarks.POC return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } @@ -1379,7 +1379,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -1597,9 +1597,9 @@ namespace Recyclable.Collections.Benchmarks.POC return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } @@ -2340,7 +2340,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -2558,9 +2558,9 @@ namespace Recyclable.Collections.Benchmarks.POC return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs index 0fbea0a..9fb3289 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs @@ -759,7 +759,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -981,9 +981,9 @@ namespace Recyclable.Collections.Benchmarks.POC return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } @@ -1904,7 +1904,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -2126,9 +2126,9 @@ namespace Recyclable.Collections.Benchmarks.POC return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } @@ -3021,7 +3021,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -3243,9 +3243,9 @@ namespace Recyclable.Collections.Benchmarks.POC return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } @@ -4175,7 +4175,7 @@ namespace Recyclable.Collections.Benchmarks.POC _nextItemBlockIndex = targetBlockIndex; } - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -4397,9 +4397,9 @@ namespace Recyclable.Collections.Benchmarks.POC return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs index b24b1ef..0a067a8 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs @@ -25,7 +25,7 @@ namespace Recyclable.Collections.Benchmarks.POC [Params(RecyclableLongListPocBenchmarkType.IndexOf)] public override RecyclableLongListPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } - // [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.RecyclableArrayList, BenchmarkType.RecyclableLongList)] + // [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.RecyclableList, BenchmarkType.RecyclableLongList)] [Params(RecyclableLongListPocBenchmarkType.IndexOf_PooledList)] public override RecyclableLongListPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } diff --git a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs index abeb9a8..2123d94 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs @@ -11,7 +11,7 @@ namespace Recyclable.Collections.Benchmarks Array, List, PooledList, - RecyclableArrayList, + RecyclableList, RecyclableLongList, } @@ -26,8 +26,8 @@ namespace Recyclable.Collections.Benchmarks protected PooledList? _testObjectsAsPooledList; public PooledList TestObjectsAsPooledList => _testObjectsAsPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); - 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 RecyclableLongList? _testObjectsAsRecyclableLongList; protected RecyclableLongList TestObjectsAsRecyclableLongList => _testObjectsAsRecyclableLongList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); protected IEnumerable TestObjectsAsIEnumerable @@ -64,8 +64,8 @@ namespace Recyclable.Collections.Benchmarks _testObjectsAsPooledList ??= new PooledList(TestObjects, ClearMode.Auto); break; - case RecyclableCollectionsBenchmarkSource.RecyclableArrayList: - _testObjectsAsRecyclableArrayList ??= new(TestObjects, initialCapacity: TestObjectCount); + case RecyclableCollectionsBenchmarkSource.RecyclableList: + _testObjectsAsRecyclableList ??= new(TestObjects, initialCapacity: TestObjectCount); break; case RecyclableCollectionsBenchmarkSource.RecyclableLongList: @@ -92,8 +92,8 @@ namespace Recyclable.Collections.Benchmarks _testObjectsAsList = default; _testObjectsAsPooledList?.Dispose(); _testObjectsAsPooledList = default; - _testObjectsAsRecyclableArrayList?.Dispose(); - _testObjectsAsRecyclableArrayList = default; + _testObjectsAsRecyclableList?.Dispose(); + _testObjectsAsRecyclableList = default; _testObjectsAsRecyclableLongList?.Dispose(); _testObjectsAsRecyclableLongList = default; base.Cleanup(); @@ -104,11 +104,11 @@ namespace Recyclable.Collections.Benchmarks //public class ListVsLinkedListBenchmarks //{ // [Benchmark] - // public void RecyclableArrayList() + // public void RecyclableList() // { // var data = TestObjects; // var dataCount = TestObjectCount; - // using var list = new RecyclableArrayList(initialCapacity: dataCount); + // using var list = new RecyclableList(initialCapacity: dataCount); // for (long i = 0; i < dataCount; i++) // { // list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt index a2f956e..9d06771 100644 --- a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt +++ b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt @@ -7,18 +7,18 @@ | Array_Create_WithCapacity | 2,447.9 us | 8.35 us | 7.40 us | 1.99 | 0.02 | 58.5938 | 58.5938 | 58.5938 | 8000061 B | 137,932.09 | | List_Create_WithCapacity | 2,377.6 us | 43.03 us | 40.25 us | 1.93 | 0.03 | 54.6875 | 54.6875 | 54.6875 | 8000451 B | 137,938.81 | | PooledList_Create_WithCapacity | 1,229.4 us | 10.70 us | 9.48 us | 1.00 | 0.00 | - | - | - | 58 B | 1.00 | -| RecyclableArrayList_Create_WithCapacity | 1,233.9 us | 3.93 us | 3.68 us | 1.00 | 0.01 | - | - | - | 41 B | 0.71 | +| RecyclableList_Create_WithCapacity | 1,233.9 us | 3.93 us | 3.68 us | 1.00 | 0.01 | - | - | - | 41 B | 0.71 | | RecyclableLongList_Create_WithCapacity | 786.5 us | 2.32 us | 1.94 us | 0.64 | 0.01 | - | - | - | 425 B | 7.33 | | List_Create | 3.5523 ns | 0.0181 ns | 0.0141 ns | 3.5476 ns | 0.000 | 0.00 | 0.0153 | - | - | 32 B | 0.000 | | PooledList_Create | 6.9236 ns | 0.0110 ns | 0.0098 ns | 6.9222 ns | 0.000 | 0.00 | 0.0268 | - | - | 56 B | 0.000 | -| RecyclableArrayList_Create | 4.6451 ns | 0.0063 ns | 0.0053 ns | 4.6457 ns | 0.000 | 0.00 | 0.0191 | - | - | 40 B | 0.000 | +| RecyclableList_Create | 4.6451 ns | 0.0063 ns | 0.0053 ns | 4.6457 ns | 0.000 | 0.00 | 0.0191 | - | - | 40 B | 0.000 | | RecyclableLongList_Create | 212.8164 ns | 11.9841 ns | 35.3355 ns | 213.2258 ns | 0.000 | 0.00 | 0.0381 | 0.0379 | - | 80 B | 0.000 | Array_GetItem | 239.7 us | 0.36 us | 0.32 us | 0.66 | 0.00 | - | NA | List_GetItem | 363.5 us | 0.67 us | 0.56 us | 1.00 | 0.00 | - | NA | PooledList_GetItem | 365.3 us | 0.90 us | 0.75 us | 1.00 | 0.00 | - | NA | - RecyclableArrayList_GetItem | 239.2 us | 0.07 us | 0.06 us | 0.65 | 0.00 | - | NA | + RecyclableList_GetItem | 239.2 us | 0.07 us | 0.06 us | 0.65 | 0.00 | - | NA | RecyclableLongList_GetItem | 538.0 us | 1.01 us | 0.89 us | 1.47 | 0.00 | 1 B | NA | RecyclableLongList_GetItem_AsSpan | 673.5 us | 4.85 us | 4.30 us | 1.84 | 0.01 | 1 B | NA | RecyclableLongList_GetItem_AsArray | 239.4 us | 0.22 us | 0.20 us | 0.66 | 0.00 | - | NA | @@ -28,54 +28,54 @@ | Array_SetItem | 2,576,086.8389 ns | 13,880.4584 ns | 11,590.8114 ns | 2,576,978.1250 ns | 2.194 | 0.01 | - | - | - | 2 B | 0.000 | | List_SetItem | 3,361,745.7734 ns | 98,390.1277 ns | 290,105.5114 ns | 3,505,120.8984 ns | 2.771 | 0.29 | - | - | - | 2 B | 0.000 | | PooledList_SetItem | 2,676,757.7524 ns | 1,228.8178 ns | 1,026.1185 ns | 2,676,991.4062 ns | 2.280 | 0.01 | - | - | - | 2 B | 0.000 | -| RecyclableArrayList_SetItem | 2,547,059.6540 ns | 9,002.9953 ns | 7,980.9208 ns | 2,542,056.8359 ns | 2.170 | 0.01 | - | - | - | 2 B | 0.000 | +| RecyclableList_SetItem | 2,547,059.6540 ns | 9,002.9953 ns | 7,980.9208 ns | 2,542,056.8359 ns | 2.170 | 0.01 | - | - | - | 2 B | 0.000 | | RecyclableLongList_SetItem | 2,326,771.9866 ns | 3,186.0942 ns | 2,824.3895 ns | 2,326,636.7188 ns | 1.981 | 0.01 | - | - | - | 2 B | 0.000 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | |------------------------------------------------------------ |----------:|----------:|----------:|------:|--------:|---------:|--------:|--------:|-----------:|------------:| | List_Add | 11.002 ms | 0.2189 ms | 0.2996 ms | 1.36 | 0.04 | 140.6250 | 93.7500 | 93.7500 | 16778117 B | 236,311.51 | | PooledList_Add | 8.117 ms | 0.0115 ms | 0.0102 ms | 1.00 | 0.00 | - | - | - | 71 B | 1.00 | -| RecyclableArrayList_Add | 8.771 ms | 0.0169 ms | 0.0141 ms | 1.08 | 0.00 | - | - | - | 1233 B | 17.37 | +| RecyclableList_Add | 8.771 ms | 0.0169 ms | 0.0141 ms | 1.08 | 0.00 | - | - | - | 1233 B | 17.37 | | RecyclableLongList_Add | 6.024 ms | 0.1101 ms | 0.1030 ms | 0.74 | 0.01 | - | - | - | 3317 B | 46.72 | | List_Add_WithCapacity | 3,724,682.0668 ns | 88,719.4866 ns | 260,198.8962 ns | 3,717,873.0469 ns | 3.326 | 0.09 | 50.7813 | 50.7813 | 50.7813 | 4000368 B | 1.000 | | PooledList_Add_WithCapacity | 2,575,618.8337 ns | 5,277.9410 ns | 4,678.7572 ns | 2,575,805.2734 ns | 2.194 | 0.01 | - | - | - | 60 B | 0.000 | -| RecyclableArrayList_Add_WithCapacity | 2,785,606.2500 ns | 4,595.4354 ns | 4,073.7338 ns | 2,785,292.7734 ns | 2.372 | 0.01 | - | - | - | 42 B | 0.000 | +| RecyclableList_Add_WithCapacity | 2,785,606.2500 ns | 4,595.4354 ns | 4,073.7338 ns | 2,785,292.7734 ns | 2.372 | 0.01 | - | - | - | 42 B | 0.000 | | RecyclableLongList_Add_WithCapacity | 2,878,685.9701 ns | 44,974.7609 ns | 35,113.3350 ns | 2,873,102.9297 ns | 2.453 | 0.03 | - | - | - | 170 B | 0.000 | | List_AddRangeWhenSourceIsArray | 1,966,607.1581 ns | 39,075.4380 ns | 100,165.2861 ns | 1,958,457.4219 ns | 1.721 | 0.07 | 42.9688 | 42.9688 | 42.9688 | 4000364 B | 1.000 | | PooledList_AddRangeWhenSourceIsArray | 972,973.4049 ns | 12,365.6121 ns | 11,566.8012 ns | 974,744.8242 ns | 0.829 | 0.01 | - | - | - | 57 B | 0.000 | -| RecyclableArrayList_AddRangeWhenSourceIsArray | 1,098,651.7578 ns | 3,202.7829 ns | 2,995.8851 ns | 1,097,979.4922 ns | 0.936 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsArray | 1,098,651.7578 ns | 3,202.7829 ns | 2,995.8851 ns | 1,097,979.4922 ns | 0.936 | 0.00 | - | - | - | 41 B | 0.000 | | RecyclableLongList_AddRangeWhenSourceIsArray | 1,023,799.0234 ns | 2,772.1984 ns | 2,457.4816 ns | 1,023,618.7500 ns | 0.872 | 0.00 | - | - | - | 169 B | 0.000 | | List_AddRangeWhenSourceIsList | 1,954,805.9462 ns | 37,631.3829 ns | 71,597.6170 ns | 1,947,531.6406 ns | 1.656 | 0.06 | 42.9688 | 42.9688 | 42.9688 | 4000363 B | 1.000 | | PooledList_AddRangeWhenSourceIsList | 963,657.7116 ns | 5,594.8195 ns | 5,233.3976 ns | 965,055.4199 ns | 0.820 | 0.01 | - | - | - | 57 B | 0.000 | -| RecyclableArrayList_AddRangeWhenSourceIsList | 1,114,697.8385 ns | 4,015.8164 ns | 3,756.3971 ns | 1,115,863.6719 ns | 0.950 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsList | 1,114,697.8385 ns | 4,015.8164 ns | 3,756.3971 ns | 1,115,863.6719 ns | 0.950 | 0.00 | - | - | - | 41 B | 0.000 | | RecyclableLongList_AddRangeWhenSourceIsList | 1,008,424.0374 ns | 2,179.7502 ns | 1,932.2918 ns | 1,008,779.8828 ns | 0.859 | 0.00 | - | - | - | 169 B | 0.000 | | List_AddRangeWhenSourceIsIList | 2,045,453.8895 ns | 40,653.4327 ns | 98,956.0738 ns | 2,040,041.7969 ns | 1.734 | 0.12 | 42.9688 | 42.9688 | 42.9688 | 4000366 B | 1.000 | | PooledList_AddRangeWhenSourceIsIList | 977,402.4790 ns | 4,165.1933 ns | 3,478.1251 ns | 978,420.3125 ns | 0.832 | 0.00 | - | - | - | 58 B | 0.000 | -| RecyclableArrayList_AddRangeWhenSourceIsIList | 1,109,563.8477 ns | 4,480.2733 ns | 4,190.8505 ns | 1,108,281.9336 ns | 0.945 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsIList | 1,109,563.8477 ns | 4,480.2733 ns | 4,190.8505 ns | 1,108,281.9336 ns | 0.945 | 0.00 | - | - | - | 41 B | 0.000 | | RecyclableLongList_AddRangeWhenSourceIsIList | 2,632,797.0052 ns | 3,899.2563 ns | 3,044.2828 ns | 2,632,267.1875 ns | 2.243 | 0.01 | - | - | - | 170 B | 0.000 | | PooledList_AddRangeWhenSourceIsIEnumerable | 28,102,425.4630 ns | 556,294.1092 ns | 779,846.6478 ns | 28,009,543.7500 ns | 24.007 | 0.74 | 2187.5000 | 1375.0000 | 218.7500 | 12000526 B | 3.000 | | List_AddRangeWhenSourceIsIEnumerable | 47,884,030.8442 ns | 955,391.3313 ns | 1,370,193.6989 ns | 48,152,172.7273 ns | 40.578 | 1.04 | 2272.7273 | 1363.6364 | 363.6364 | 20389288 B | 5.097 | -| RecyclableArrayList_AddRangeWhenSourceIsIEnumerable | 27,102,237.2533 ns | 535,598.7947 ns | 595,316.6412 ns | 27,248,382.8125 ns | 23.009 | 0.61 | 2250.0000 | 1406.2500 | 250.0000 | 12000305 B | 3.000 | +| RecyclableList_AddRangeWhenSourceIsIEnumerable | 27,102,237.2533 ns | 535,598.7947 ns | 595,316.6412 ns | 27,248,382.8125 ns | 23.009 | 0.61 | 2250.0000 | 1406.2500 | 250.0000 | 12000305 B | 3.000 | | RecyclableLongList_AddRangeWhenSourceIsIEnumerable | 27,206,882.1023 ns | 517,997.7196 ns | 636,147.7188 ns | 27,262,153.1250 ns | 23.049 | 0.64 | 2218.7500 | 1468.7500 | 250.0000 | 12000361 B | 3.000 | | PooledList_AddRangeWhenSourceIsPooledList | 958,132.3033 ns | 4,444.5508 ns | 3,939.9785 ns | 957,355.9570 ns | 0.816 | 0.00 | - | - | - | 57 B | 0.000 | -| RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList | 1,068,135.4492 ns | 15,036.9473 ns | 12,556.5320 ns | 1,063,337.2070 ns | 0.910 | 0.01 | - | - | - | 41 B | 0.000 | -| RecyclableLongList_AddRangeWhenSourceIsRecyclableArrayList | 1,057,253.7249 ns | 2,697.5224 ns | 2,391.2834 ns | 1,057,475.9766 ns | 0.901 | 0.00 | - | - | - | 169 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsRecyclableList | 1,068,135.4492 ns | 15,036.9473 ns | 12,556.5320 ns | 1,063,337.2070 ns | 0.910 | 0.01 | - | - | - | 41 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsRecyclableList | 1,057,253.7249 ns | 2,697.5224 ns | 2,391.2834 ns | 1,057,475.9766 ns | 0.901 | 0.00 | - | - | - | 169 B | 0.000 | | List_AddRange_WithCapacity | 2,022,247.3039 ns | 44,489.4467 ns | 129,071.9520 ns | 2,030,418.9453 ns | 1.767 | 0.06 | 39.0625 | 39.0625 | 39.0625 | 4000318 B | 1.000 | | PooledList_AddRange_WithCapacity | 965,635.9570 ns | 11,740.2154 ns | 10,981.8048 ns | 968,593.2617 ns | 0.822 | 0.01 | - | - | - | 57 B | 0.000 | -| RecyclableArrayList_AddRange_WithCapacity | 1,044,438.5517 ns | 3,047.5353 ns | 2,544.8300 ns | 1,045,038.8672 ns | 0.890 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableList_AddRange_WithCapacity | 1,044,438.5517 ns | 3,047.5353 ns | 2,544.8300 ns | 1,045,038.8672 ns | 0.890 | 0.00 | - | - | - | 41 B | 0.000 | | RecyclableLongList_AddRange_WithCapacity | 1,028,953.5156 ns | 17,735.8499 ns | 14,810.2379 ns | 1,022,104.4922 ns | 0.876 | 0.01 | - | - | - | 169 B | 0.000 | | Array_Count | 0.0087 ns | 0.0110 ns | 0.0103 ns | 0.0024 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | List_Count | 1.2190 ns | 0.0046 ns | 0.0040 ns | 1.2173 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | PooledList_Count | 0.0037 ns | 0.0015 ns | 0.0012 ns | 0.0040 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | -| RecyclableArrayList_Count | 1.2096 ns | 0.0009 ns | 0.0008 ns | 1.2092 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableList_Count | 1.2096 ns | 0.0009 ns | 0.0008 ns | 1.2092 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | RecyclableLongList_Count | 0.0103 ns | 0.0008 ns | 0.0007 ns | 0.0102 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | | Array_LongCount | 0.0021 ns | 0.0005 ns | 0.0004 ns | 0.0022 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | @@ -92,7 +92,7 @@ | Array_IndexOf_FirstItems | 1,874,810,260.0000 ns | 25,774,115.5271 ns | 24,109,123.6555 ns | 1,867,388,700.0000 ns | 1,597.521 | 22.54 | - | - | - | 600 B | 0.000 | | List_IndexOf_FirstItems | 1,864,704,725.0000 ns | 10,982,470.8154 ns | 8,574,390.8164 ns | 1,866,253,200.0000 ns | 1,588.702 | 10.94 | - | - | - | 600 B | 0.000 | | PooledList_IndexOf_FirstItems | 1,840,856,800.0000 ns | 4,046,589.8784 ns | 3,159,311.2037 ns | 1,839,944,050.0000 ns | 1,568.374 | 6.17 | - | - | - | 600 B | 0.000 | -| RecyclableArrayList_IndexOf_FirstItems | 1,832,934,950.0000 ns | 1,729,308.0131 ns | 1,350,129.9476 ns | 1,832,475,300.0000 ns | 1,561.625 | 5.52 | - | - | - | 600 B | 0.000 | +| RecyclableList_IndexOf_FirstItems | 1,832,934,950.0000 ns | 1,729,308.0131 ns | 1,350,129.9476 ns | 1,832,475,300.0000 ns | 1,561.625 | 5.52 | - | - | - | 600 B | 0.000 | | RecyclableLongList_IndexOf_FirstItems | 1,844,986,792.3077 ns | 3,209,050.1952 ns | 2,679,702.2516 ns | 1,843,943,600.0000 ns | 1,571.359 | 6.49 | - | - | - | 600 B | 0.000 | | RecyclableLongList_LongIndexOf_FirstItems | 1,878,933,500.0000 ns | 17,118,131.6171 ns | 16,012,310.9355 ns | 1,870,009,600.0000 ns | 1,601.616 | 14.82 | - | - | - | 600 B | 0.000 | @@ -103,20 +103,20 @@ | List_Remove_FirstItems | 5,023,030,012.5000 ns | 82,615,052.4052 ns | 107,422,903.9562 ns | 5,000,417,000.0000 ns | 4,295.171 | 116.37 | - | - | - | 4000656 B | 1.000 | | PooledList_Remove_FirstItems | 4,750,601,721.4286 ns | 17,989,258.8487 ns | 15,947,009.4038 ns | 4,747,622,550.0000 ns | 4,043.997 | 21.26 | - | - | - | 992 B | 0.000 | -| RecyclableArrayList_Remove_FirstItems | 4,950,634,900.0000 ns | 33,761,094.2570 ns | 29,928,330.6849 ns | 4,936,519,050.0000 ns | 4,218.075 | 23.90 | - | - | - | 640 B | 0.000 | +| RecyclableList_Remove_FirstItems | 4,950,634,900.0000 ns | 33,761,094.2570 ns | 29,928,330.6849 ns | 4,936,519,050.0000 ns | 4,218.075 | 23.90 | - | - | - | 640 B | 0.000 | | RecyclableLongList_Remove_FirstItems | 5,020,383,250.0000 ns | 100,209,734.5497 ns | 115,401,679.3276 ns | 5,029,163,750.0000 ns | 4,306.772 | 86.95 | - | - | - | 952 B | 0.000 | | List_Remove_LastItems | 56,593,869,333.3333 ns | 258,693,958.6041 ns | 241,982,489.3835 ns | 56,513,091,800.0000 ns | 48,189.227 | 272.02 | - | - | - | 4000656 B | 1.000 | | PooledList_Remove_LastItems | 56,465,443,542.8571 ns | 99,443,506.1766 ns | 88,154,078.0245 ns | 56,458,364,050.0000 ns | 48,092.866 | 216.09 | - | - | - | 992 B | 0.000 | -| RecyclableArrayList_Remove_LastItems | 57,432,405,146.6667 ns | 264,062,625.8226 ns | 247,004,344.0307 ns | 57,409,508,000.0000 ns | 48,952.707 | 234.83 | - | - | - | 640 B | 0.000 | +| RecyclableList_Remove_LastItems | 57,432,405,146.6667 ns | 264,062,625.8226 ns | 247,004,344.0307 ns | 57,409,508,000.0000 ns | 48,952.707 | 234.83 | - | - | - | 640 B | 0.000 | | RecyclableLongList_Remove_LastItems | 57,077,678,728.5714 ns | 210,992,675.8689 ns | 187,039,511.4398 ns | 57,038,290,150.0000 ns | 48,614.503 | 242.79 | - | - | - | 952 B | 0.000 | | List_RemoveAt_FirstItems | 4,517,669,893.3333 ns | 77,360,576.7460 ns | 72,363,131.4861 ns | 4,546,302,700.0000 ns | 3,850.590 | 62.34 | - | - | - | 4000656 B | 1.000 | | PooledList_RemoveAt_FirstItems | 4,439,417,700.0000 ns | 40,915,112.8948 ns | 38,272,021.9874 ns | 4,418,366,400.0000 ns | 3,779.751 | 38.92 | - | - | - | 992 B | 0.000 | -| RecyclableArrayList_RemoveAt_FirstItems | 4,558,102,892.8571 ns | 25,076,973.8515 ns | 22,230,084.1404 ns | 4,559,174,800.0000 ns | 3,882.107 | 16.61 | - | - | - | 640 B | 0.000 | +| RecyclableList_RemoveAt_FirstItems | 4,558,102,892.8571 ns | 25,076,973.8515 ns | 22,230,084.1404 ns | 4,559,174,800.0000 ns | 3,882.107 | 16.61 | - | - | - | 640 B | 0.000 | | RecyclableLongList_RemoveAt_FirstItems | 4,617,033,766.6667 ns | 85,061,197.3076 ns | 79,566,296.7373 ns | 4,632,994,800.0000 ns | 3,928.768 | 72.17 | - | - | - | 952 B | 0.000 | | List_RemoveAt_LastItems | 2,005,211.3281 ns | 40,078.0351 ns | 84,538.1787 ns | 2,010,579.1016 ns | 1.725 | 0.08 | 42.9688 | 42.9688 | 42.9688 | 4000306 B | 1.000 | | PooledList_RemoveAt_LastItems | 1,047,436.1654 ns | 5,655.2027 ns | 5,289.8801 ns | 1,044,690.9180 ns | 0.892 | 0.01 | - | - | - | 58 B | 0.000 | -| RecyclableArrayList_RemoveAt_LastItems | 1,256,380.8969 ns | 9,142.3285 ns | 7,634.2584 ns | 1,254,354.9805 ns | 1.070 | 0.01 | - | - | - | 41 B | 0.000 | +| RecyclableList_RemoveAt_LastItems | 1,256,380.8969 ns | 9,142.3285 ns | 7,634.2584 ns | 1,254,354.9805 ns | 1.070 | 0.01 | - | - | - | 41 B | 0.000 | | RecyclableLongList_RemoveAt_LastItems | 1,621,193.5826 ns | 20,030.6198 ns | 17,756.6227 ns | 1,620,789.8438 ns | 1.379 | 0.02 | - | - | - | 353 B | 0.000 | diff --git a/Recyclable.Collections/GlobalSuppressions.cs b/Recyclable.Collections/GlobalSuppressions.cs index 67f632b..1f77122 100644 --- a/Recyclable.Collections/GlobalSuppressions.cs +++ b/Recyclable.Collections/GlobalSuppressions.cs @@ -5,4 +5,4 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Style", "IDE0032:Use auto property", Justification = "", Scope = "member", Target = "~F:Recyclable.Collections.RecyclableArrayList`1._count")] +[assembly: SuppressMessage("Style", "IDE0032:Use auto property", Justification = "", Scope = "member", Target = "~F:Recyclable.Collections.RecyclableList`1._count")] diff --git a/Recyclable.Collections/ListExtensions.cs b/Recyclable.Collections/ListExtensions.cs index f9361d7..350a006 100644 --- a/Recyclable.Collections/ListExtensions.cs +++ b/Recyclable.Collections/ListExtensions.cs @@ -7,10 +7,10 @@ namespace Recyclable.Collections public static class ListExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this RecyclableArrayList arrays, T item) => arrays.Any(x => x.Contains(item)); + public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyTo(this RecyclableArrayList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + public static void CopyTo(this RecyclableList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) { Span arrayMemory = destinationArray.AsSpan(); arrayMemory = arrayMemory[destinationArrayIndex..]; @@ -49,7 +49,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Enumerate(this RecyclableArrayList arrays, int chunkSize, long totalCount) + public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) { long currentCount = 0; for (var arrayIdx = 0; (arrayIdx < arrays.Count) && (currentCount < totalCount); arrayIdx++) @@ -104,7 +104,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this RecyclableArrayList arrays, int blockSize, T item, IEqualityComparer comparer) + public static long LongIndexOf(this RecyclableList arrays, int blockSize, T item, IEqualityComparer comparer) { for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) { diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableList.cs similarity index 91% rename from Recyclable.Collections/RecyclableArrayList.cs rename to Recyclable.Collections/RecyclableList.cs index f86ff18..c9b3c7e 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public class RecyclableArrayList : IList, IDisposable + public class RecyclableList : IList, IDisposable { private static readonly ArrayPool _arrayPool = ArrayPool.Create(); @@ -43,7 +43,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static int EnsureCapacity(RecyclableArrayList list, int requestedCapacity) + protected static int EnsureCapacity(RecyclableList list, int requestedCapacity) { int newCapacity; switch (list._capacity > 0) @@ -69,13 +69,13 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added - public RecyclableArrayList() + public RecyclableList() #pragma warning restore CS8618 { } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added - public RecyclableArrayList(int initialCapacity) + public RecyclableList(int initialCapacity) #pragma warning restore CS8618 { if (initialCapacity > 0) @@ -86,34 +86,34 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added - public RecyclableArrayList(RecyclableArrayList source) + public RecyclableList(RecyclableList source) #pragma warning restore CS8618 { AddRange(source); } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added - public RecyclableArrayList(in T[] source) + public RecyclableList(in T[] source) #pragma warning restore CS8618 { AddRange(source); } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added - public RecyclableArrayList(List source) + public RecyclableList(List source) #pragma warning restore CS8618 { AddRange(source); } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added - public RecyclableArrayList(IList source) + public RecyclableList(IList source) #pragma warning restore CS8618 { AddRange(source); } - public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) + public RecyclableList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { _memoryBlock = Resize(_memoryBlock, initialCapacity); _capacity = _memoryBlock.Length; @@ -227,7 +227,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -258,12 +258,12 @@ namespace Recyclable.Collections var sourceItemsCount = items.LongCount; if (sourceItemsCount > int.MaxValue) { - ThrowArgumentOutOfRangeException($"The number of items exceeds the maximum capacity of {nameof(RecyclableArrayList)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); + ThrowArgumentOutOfRangeException($"The number of items exceeds the maximum capacity of {nameof(RecyclableList)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); } if (_count + sourceItemsCount > int.MaxValue) { - ThrowArgumentOutOfRangeException($"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableArrayList)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); + ThrowArgumentOutOfRangeException($"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableList)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); } int targetCapacity = _count + (int)sourceItemsCount; @@ -315,9 +315,9 @@ namespace Recyclable.Collections return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } @@ -393,7 +393,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.NoInlining)] public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_memoryBlock, 0, array, arrayIndex, _count); - protected static IEnumerable Enumerate(RecyclableArrayList list) + protected static IEnumerable Enumerate(RecyclableList list) { int count = list._count; var memory = list._memoryBlock; diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index 4d914e9..5b17dd3 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -525,7 +525,7 @@ namespace Recyclable.Collections _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } - public void AddRange(RecyclableArrayList items) + public void AddRange(RecyclableList items) { if (items.Count == 0) { @@ -747,9 +747,9 @@ namespace Recyclable.Collections return; } - if (source is RecyclableArrayList sourceRecyclableArrayList) + if (source is RecyclableList sourceRecyclableList) { - AddRange(sourceRecyclableArrayList); + AddRange(sourceRecyclableList); return; } diff --git a/Recyclable.Collections/RecyclableLongListExtensions.cs b/Recyclable.Collections/RecyclableLongListExtensions.cs index b4073e4..3ddb0ea 100644 --- a/Recyclable.Collections/RecyclableLongListExtensions.cs +++ b/Recyclable.Collections/RecyclableLongListExtensions.cs @@ -132,16 +132,16 @@ namespace Recyclable.Collections public static RecyclableLongList ToRecyclableLongList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableArrayList ToRecyclableArrayList(this IEnumerable values) => new(values); + public static RecyclableList ToRecyclableList(this IEnumerable values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableArrayList ToRecyclableArrayList(this T[] values) => new(values); + public static RecyclableList ToRecyclableList(this T[] values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableArrayList ToRecyclableArrayList(this List values) => new(values); + public static RecyclableList ToRecyclableList(this List values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableArrayList ToRecyclableArrayList(this RecyclableArrayList values) => new(values); + public static RecyclableList ToRecyclableList(this RecyclableList values) => new(values); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RecyclableLongList ToRecyclableLongList(this IEnumerable values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize); diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index f8ff04a..dcf5e28 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -10,7 +10,7 @@ namespace Recyclable.Collections private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private bool _disposedValue; - private RecyclableArrayList Memory { get; } + private RecyclableList Memory { get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T Dequeue(RecyclableQueue queue) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs similarity index 88% rename from Recyclable.CollectionsTests/RecyclableArrayListTests.cs rename to Recyclable.CollectionsTests/RecyclableListTests.cs index 192f624..fb77c5a 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -3,14 +3,14 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - public class RecyclableArrayListTests + public class RecyclableListTests { [Theory] [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); // Act if (testData is long[] testDataArray) @@ -21,9 +21,9 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataList); } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) + else if (testData is RecyclableList testDataRecyclableList) { - list.AddRange(testDataRecyclableArrayList); + list.AddRange(testDataRecyclableList); } else if (testData is RecyclableLongList testDataRecyclableLongList) { @@ -53,7 +53,7 @@ namespace Recyclable.CollectionsTests public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) { // Prepare - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); // Act if (testData is long[] testDataArray) @@ -64,9 +64,9 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataList); } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) + else if (testData is RecyclableList testDataRecyclableList) { - list.AddRange(testDataRecyclableArrayList); + list.AddRange(testDataRecyclableList); } else if (testData is RecyclableLongList testDataRecyclableLongList) { @@ -96,7 +96,7 @@ namespace Recyclable.CollectionsTests public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); // Act if (testData is long[] testDataArray) @@ -109,10 +109,10 @@ namespace Recyclable.CollectionsTests list.AddRange(testDataList); list.AddRange(testDataList); } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) + else if (testData is RecyclableList testDataRecyclableList) { - list.AddRange(testDataRecyclableArrayList); - list.AddRange(testDataRecyclableArrayList); + list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableList); } else if (testData is RecyclableLongList testDataRecyclableLongList) { @@ -154,7 +154,7 @@ namespace Recyclable.CollectionsTests testData = testData.Concat(testData).ToArray(); // Act - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); foreach (var index in testData) { list.Add(index); @@ -170,7 +170,7 @@ namespace Recyclable.CollectionsTests public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); // Act foreach (var item in testData) @@ -190,7 +190,7 @@ namespace Recyclable.CollectionsTests public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount) { // Act - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); list.AddRange(testData); foreach (var index in testData) { @@ -207,7 +207,7 @@ namespace Recyclable.CollectionsTests public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); _ = list.Count.Should().Be((int)itemsCount); // Act @@ -224,7 +224,7 @@ namespace Recyclable.CollectionsTests public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); list.Dispose(); // Act @@ -236,7 +236,7 @@ namespace Recyclable.CollectionsTests public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) { // Act - using var list = new RecyclableArrayList(testData.Concat(testData), initialCapacity: (int)itemsCount * 2); + using var list = new RecyclableList(testData.Concat(testData), initialCapacity: (int)itemsCount * 2); // Validate _ = list.Count.Should().Be((int)itemsCount * 2); @@ -248,7 +248,7 @@ namespace Recyclable.CollectionsTests public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable testData, long itemsCount) { // Act - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); // Validate _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); @@ -261,7 +261,7 @@ namespace Recyclable.CollectionsTests public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount) { // Act - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); // Validate _ = list.Count.Should().Be((int)itemsCount); @@ -278,7 +278,7 @@ namespace Recyclable.CollectionsTests } // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -293,7 +293,7 @@ namespace Recyclable.CollectionsTests public void ContainsShouldNotFindAnythingWhenListEmpty() { // Prepare - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); // Validate _ = list.Contains(0).Should().BeFalse(); @@ -310,7 +310,7 @@ namespace Recyclable.CollectionsTests } // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -329,7 +329,7 @@ namespace Recyclable.CollectionsTests public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); long[] copiedItems = new long[itemsCount]; // Act @@ -345,7 +345,7 @@ namespace Recyclable.CollectionsTests public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); // Act var yieldedItems = new List((int)itemsCount); @@ -360,7 +360,7 @@ namespace Recyclable.CollectionsTests public void IndexOfShouldNotFindAnythingWhenListEmpty() { // Prepare - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); // Validate _ = list.IndexOf(0).Should().Be(-1); @@ -379,7 +379,7 @@ namespace Recyclable.CollectionsTests } // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); // Act @@ -398,7 +398,7 @@ namespace Recyclable.CollectionsTests public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); // Act & Validate foreach (var index in testData) @@ -418,7 +418,7 @@ namespace Recyclable.CollectionsTests } // Act - using RecyclableArrayList list = new(testData); + using RecyclableList list = new(testData); list.QuickSort(); // Validate @@ -451,7 +451,7 @@ namespace Recyclable.CollectionsTests public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(); + using var list = new RecyclableList(); // Act foreach (var item in testData) @@ -471,7 +471,7 @@ namespace Recyclable.CollectionsTests public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -493,7 +493,7 @@ namespace Recyclable.CollectionsTests public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -515,7 +515,7 @@ namespace Recyclable.CollectionsTests public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -534,7 +534,7 @@ namespace Recyclable.CollectionsTests public void RemoveFromTheEndShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) { // Act & Validate - using var list = new RecyclableArrayList(testData); + using var list = new RecyclableList(testData); long deleted = 0; foreach (var item in testData.Reverse()) { diff --git a/Recyclable.CollectionsTests/RecyclableLongListTestData.cs b/Recyclable.CollectionsTests/RecyclableLongListTestData.cs index ac7111b..bffd458 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTestData.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTestData.cs @@ -45,7 +45,7 @@ namespace Recyclable.CollectionsTests { yield return new object[] { $"int[{itemsCount}]", CreateTestData(itemsCount).ToArray(), itemsCount }; yield return new object[] { $"List(itemsCount: {itemsCount}", CreateTestData(itemsCount).ToList(), itemsCount }; - yield return new object[] { $"RecyclableArrayList(itemsCount: {itemsCount})", CreateTestData(itemsCount).ToRecyclableArrayList(), itemsCount }; + yield return new object[] { $"RecyclableList(itemsCount: {itemsCount})", CreateTestData(itemsCount).ToRecyclableList(), itemsCount }; foreach (var sourceBlockSize in BlockSizeVariants) { yield return new object[] @@ -68,7 +68,7 @@ namespace Recyclable.CollectionsTests { new object[] { "int[]", Array.Empty() }, new object[] { "List", Array.Empty().ToList() }, - new object[] { "RecyclableArrayList", Array.Empty().ToRecyclableArrayList() }, + new object[] { "RecyclableList", Array.Empty().ToRecyclableList() }, new object[] { "RecyclableLongList", Array.Empty().ToRecyclableLongList() }, new object[] { "IList", new CustomIList(Array.Empty()) }, new object[] { "IEnumerable with non-enumerated count", Array.Empty().AsEnumerable() }, diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index 4f8669c..83fa082 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -21,9 +21,9 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataList); } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) + else if (testData is RecyclableList testDataRecyclableList) { - list.AddRange(testDataRecyclableArrayList); + list.AddRange(testDataRecyclableList); } else if (testData is RecyclableLongList testDataRecyclableList) { @@ -65,9 +65,9 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataList); } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) + else if (testData is RecyclableList testDataRecyclableList) { - list.AddRange(testDataRecyclableArrayList); + list.AddRange(testDataRecyclableList); } else if (testData is RecyclableLongList testDataRecyclableList) { @@ -110,15 +110,15 @@ namespace Recyclable.CollectionsTests list.AddRange(testDataList); list.AddRange(testDataList); } - else if (testData is RecyclableArrayList testDataRecyclableArrayList) + else if (testData is RecyclableList testDataRecyclableList) { - list.AddRange(testDataRecyclableArrayList); - list.AddRange(testDataRecyclableArrayList); + list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableList); } - else if (testData is RecyclableLongList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableLongList) { - list.AddRange(testDataRecyclableList); - list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableLongList); + list.AddRange(testDataRecyclableLongList); } else if (testData is IList testDataIList) { From de3f06421fa401c0ebe4ef5b6c1c41526259ac70 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 21 May 2023 20:44:33 +0200 Subject: [PATCH 283/305] Milestones updated --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 379d601..50e1b98 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,8 @@ 1. 🅿️ `CopyTo` 1. ✅ `EnsureCapacity` 1. 🅿️ `GetEnumerator` - 1. 👉 `IndexOf` - 1. 👉 `LongIndexOf` + 1. ✅ `IndexOf` + 1. ✅ `LongIndexOf` 1. ✅ `Insert` 1. ✅ `Remove` 1. ✅ `RemoveAt(int index)` @@ -72,11 +72,11 @@ 1. 🅿️ `RecyclableSortedList` 1. 🅿️ `RecyclableStack` 1. 🅿️ `RecyclableUnorderedList` - 1. 🅿️ Rename `RecyclableList` to `RecyclableLongList` - 1. 🅿️ Rename `RecyclableArrayList` to `RecyclableList` + 1. ✅ Rename `RecyclableList` to `RecyclableLongList` + 1. ✅ Rename `RecyclableArrayList` to `RecyclableList` + 1. 👉 Add support for `ReadOnlySpan` 1. 🅿️ Convert `_memoryBlocks` to `Array` to allow `ulong` lengths 1. 🅿️ Convert block indexes from `int` to `ulong` or `long` - 1. 🅿️ Add support for `ReadOnlySpan` 1. 🅿️ Implement `List` interfaces 1. 🅿️ `ICollection` 1. 🅿️ `IEnumerable` From 853a69fa4d41acaf638f2f83ec062cdb142a2a72 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:01:25 +0200 Subject: [PATCH 284/305] Tiny cleanup --- Recyclable.Collections/RecyclableList.cs | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index c9b3c7e..19937d3 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -350,26 +350,26 @@ namespace Recyclable.Collections if (enumerator.MoveNext()) { int available = capacity - targetItemIdx; - do - { - if (targetItemIdx + growByCount > capacity) - { - capacity = EnsureCapacity(this, capacity + growByCount); - memorySpan = new(_memoryBlock); - available = capacity - targetItemIdx; - } + do + { + if (targetItemIdx + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memorySpan = new(_memoryBlock); + available = capacity - targetItemIdx; + } - for (i = 0; i < available; i++) - { - memorySpan[targetItemIdx++] = enumerator.Current; - if (!enumerator.MoveNext()) - { - break; - } - } - } - while (i >= available); - } + for (i = 0; i < available; i++) + { + memorySpan[targetItemIdx++] = enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + } + } + while (i >= available); + } _count = targetItemIdx; } From 8bef46f1cf910ed94f69cfff5e067e305c020729 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:11:09 +0200 Subject: [PATCH 285/305] Added support for running benchmarks without baseline. --- .../Benchmarks/RecyclableCollections.Benchmarks.cs | 2 +- Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs | 2 +- Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index a17bae3..69928cc 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -66,7 +66,7 @@ namespace Recyclable.Collections.Benchmarks )] public override RecyclableCollectionsBenchmarkSource BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } - protected override Action GetTestMethod(RecyclableCollectionsBenchmarkSource benchmarkType) + protected override Action? GetTestMethod(RecyclableCollectionsBenchmarkSource benchmarkType) => (Action?) GetType().GetMethod($"{benchmarkType}_{TestCase}", BindingFlags.Instance | BindingFlags.Public)?.CreateDelegate(typeof(Action), this) ?? throw CreateMethodNotFoundException($"{benchmarkType}_{TestCase}", GetType().FullName); } diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 1a12327..4cb0b6c 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -94,7 +94,7 @@ namespace Recyclable.Collections.Benchmarks.POC { Console.WriteLine($"******* SETTING UP TEST CASE FOR BENCHMARK {{{BenchmarkType}}} *******"); - if (BaselineBenchmarkType != null) + if (!EqualityComparer.Default.Equals(BaselineBenchmarkType, default)) { Console.WriteLine("******* SETTING UP BASELINE DATA *******"); PrepareData(BaselineBenchmarkType); diff --git a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs index 2123d94..602cd79 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs @@ -52,6 +52,9 @@ namespace Recyclable.Collections.Benchmarks Console.WriteLine($"******* PREPARING DATA FOR {benchmarkType} *******"); switch (benchmarkType) { + case RecyclableCollectionsBenchmarkSource.Unknown: + break; + case RecyclableCollectionsBenchmarkSource.Array: _testObjects ??= DataGenerator.EnumerateTestObjects(TestObjectCount); break; From 9f0a4c39ba5d39e0d8c44daee7ac190cefe77348 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:11:30 +0200 Subject: [PATCH 286/305] Tiny cleanup --- Recyclable.Collections/RecyclableQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index dcf5e28..ed00fe4 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -81,7 +81,7 @@ namespace Recyclable.Collections } public long LongCount { get; set; } - public bool IsReadOnly { get; } = false; + public bool IsReadOnly { get; } public T this[int index] { From 1fbf0ea1a15bab3abd734665e3070b9b6d23a9c4 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:12:40 +0200 Subject: [PATCH 287/305] Threshold limit for parallelization increased based on benchmark results. --- 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 274a911..1fa29b9 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -4,7 +4,7 @@ { public const int BlockSize = 16_384; public const int Capacity = 1; - public const long MinItemsCountForParallelization = 786_432; + public const long MinItemsCountForParallelization = 850_000; public const int MinPooledArrayLength = 128; public const int MaxPooledBlockSize = 2_147_483_591; public const long ItemNotFoundIndexLong = -1L; From c4e633e7d3953018bb90cde047bfeb0149e8565a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:13:30 +0200 Subject: [PATCH 288/305] Additional benchmarks & benchmark params added --- Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs | 6 ++++-- Recyclable.Collections.Benchmarks/Program.cs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 4cb0b6c..5ecdb2a 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -40,13 +40,15 @@ namespace Recyclable.Collections.Benchmarks.POC // [Params(1_000_000, 10_000_000, 100_000_000, 1_000_000_000)] // [Params(192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144)] // [Params(524_288, 786_432, 1_048_576)] + // [Params(262_144, 393_216, 524_288)] + [Params(850_000)] // [Params(RecyclableDefaults.MaxPooledBlockSize)] // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000, 33_554_432, 50_000_000, 100_000_000)] // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128)] // [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)] // [Params(1_048_576)] - public virtual int TestObjectCount { get; set; }= 524_288; //131_072; - public virtual int TestObjectCountForSlowMethods => Math.Min(TestObjectCount, 131_072); + public virtual int TestObjectCount { get; set; } = 1; //33_554_432; //524_288; //131_072; + public virtual int TestObjectCountForSlowMethods => (TestObjectCount / 2) + 1;// Math.Min(TestObjectCount, 131_072); //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] //[Params(10, 20, 50, 100)] diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 3c05b5f..d74f842 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -41,6 +41,7 @@ internal class Program _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); From 29cb16793a145b3bf2e5474c2348160e8368bd67 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:21:41 +0200 Subject: [PATCH 289/305] RecyclableList.IndexOf performance improvements. --- .../Parallel/IndexOfSynchronizationContext.cs | 64 ++++- .../IndexOfSynchronizationContextPool.cs | 6 +- .../RecyclableLongList.IndexOfHelpers.cs | 234 +++++++----------- 3 files changed, 152 insertions(+), 152 deletions(-) diff --git a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs index 91f0c25..4a53190 100644 --- a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs @@ -1,22 +1,71 @@ namespace Recyclable.Collections.Parallel { - public sealed class IndexOfSynchronizationContext : IDisposable + public sealed class IndexOfSynchronizationContext { + private readonly SpinWait _participantsSpinWait = new(); + internal bool _isItemFound; + internal int _participants; + internal bool _returned; + + public void AddParticipant() + { + _ = Interlocked.Increment(ref _participants); + } + + public void AddParticipants(int newParticipants) + { + _ = Interlocked.Add(ref _participants, newParticipants); + } + + public void RemoveParticipants(int participantsToRemove) + { + if (_participants < participantsToRemove) + { + ThrowNotEnoughParticipants(); + } + + _ = Interlocked.Add(ref _participants, -participantsToRemove); + } + + public void RemoveParticipant() + { + if (_participants == 0) + { + ThrowNoMoreParticipants(); + } + + _ = Interlocked.Decrement(ref _participants); + } + + public void SignalAndWait() + { + if (_participants == 0) + { + ThrowNoMoreParticipants(); + } + + _ = Interlocked.Decrement(ref _participants); + while (_participants > 0) + { + _participantsSpinWait.SpinOnce(); + } + } + + private static void ThrowNotEnoughParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are not enough participants."); + private static void ThrowNoMoreParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are no more participants."); - public readonly Barrier AllDoneSignal; public long FoundItemIndex; public Exception? Exception; - internal bool _returned; + public int Participants => _participants; public IndexOfSynchronizationContext() { - AllDoneSignal = new(0); } public IndexOfSynchronizationContext(int participantCount) { - AllDoneSignal = new(participantCount); + _participants = participantCount; } public void SetItemFound() @@ -33,11 +82,6 @@ namespace Recyclable.Collections.Parallel _returned = true; IndexOfSynchronizationContextPool.Shared.Return(this); } - else - { - AllDoneSignal.Dispose(); - GC.SuppressFinalize(this); - } } } } \ No newline at end of file diff --git a/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs index 9d7f65b..935b71d 100644 --- a/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs @@ -22,14 +22,14 @@ namespace Recyclable.Collections.Parallel context._isItemFound = false; } - switch (context.AllDoneSignal.ParticipantsRemaining) + switch (context._participants) { case > 1: - context.AllDoneSignal.RemoveParticipants(context.AllDoneSignal.ParticipantsRemaining - 1); + context.RemoveParticipants(context._participants - 1); break; case 0: - _ = context.AllDoneSignal.AddParticipant(); + context.AddParticipant(); break; } diff --git a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs index f702481..d84126d 100644 --- a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs +++ b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs @@ -8,161 +8,102 @@ namespace Recyclable.Collections public static class IndexOfHelpers { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static void ScheduleIndexOfTask(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) + private static void IndexOfIn4ConsecutiveBlocks(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) { - _ = context.AllDoneSignal.AddParticipant(); - // & WAS SLOWER - // _ = ThreadPool.QueueUserWorkItem((_) => - _ = Task.Factory.StartNew(() => + try { - try + if (context._isItemFound) { - if (context._isItemFound) - { - return; - } - - long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); - if (context._isItemFound) - { - return; - } - else if (index >= 0) - { - context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); - return; - } - - index = Array.IndexOf(list._memoryBlocks[blockIndex + 1], itemToFind, 0, list._blockSize); - if (context._isItemFound) - { - return; - } - else if (index >= 0) - { - context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 1) << list._blockSizePow2BitShift) + index); - return; - } - - index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, list._blockSize); - if (context._isItemFound) - { - return; - } - else if (index >= 0) - { - context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 2) << list._blockSizePow2BitShift) + index); - return; - } - - index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, list._blockSize); - if (!context._isItemFound && index >= 0) - { - context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 3) << list._blockSizePow2BitShift) + index); - } - - //itemsToSearch -= blockSize; - //if (itemsToSearch > 0) - //{ - // if (itemsToSearch < blockSize) - // { - // blockSize = (int)itemsToSearch; - // } - - // index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, blockSize); - // if (index >= 0) - // { - // if (!context.ItemFoundSignal.IsSet) - // { - // context.ItemFoundSignal.Set(); - // _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 2) << list._blockSizePow2BitShift) + index); - // } - - // return; - // } - //} - - //itemsToSearch -= blockSize; - //if (itemsToSearch > 0) - //{ - // if (itemsToSearch < blockSize) - // { - // blockSize = (int)itemsToSearch; - // } - - // index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, blockSize); - // if (!context.ItemFoundSignal.IsSet && index >= 0) - // { - // context.ItemFoundSignal.Set(); - // _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 3) << list._blockSizePow2BitShift) + index); - - // return; - // } - //} + return; } - catch (Exception e) + + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (context._isItemFound) { - e.Data.Add("list", list); - e.Data.Add("itemToFind", itemToFind); - e.Data.Add("blockIndex", blockIndex); - _ = Interlocked.Exchange(ref context.Exception, e); + return; } - finally + else if (index >= 0) { - context.AllDoneSignal.RemoveParticipant(); + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); + return; } - }); + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 1], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 1) << list._blockSizePow2BitShift) + index); + return; + } + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 2) << list._blockSizePow2BitShift) + index); + return; + } + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 3) << list._blockSizePow2BitShift) + index); + } + } + catch (Exception e) + { + e.Data.Add("list", list); + e.Data.Add("itemToFind", itemToFind); + e.Data.Add("blockIndex", blockIndex); + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.RemoveParticipant(); + } } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static void ScheduleIndexOfTaskSingle(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) + private static void IndexOfIn1Block(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) { - _ = context.AllDoneSignal.AddParticipant(); - // & WAS SLOWER - // _ = ThreadPool.QueueUserWorkItem((_) => - _ = Task.Factory.StartNew(() => + try { - try + if (context._isItemFound) { - if (context._isItemFound) - { - return; - } + return; + } - long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); - if (context._isItemFound) - { - return; - } - else if (index >= 0) - { - context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); - } - } - catch (Exception e) + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0) { - _ = Interlocked.Exchange(ref context.Exception, e); + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); } - finally - { - context.AllDoneSignal.RemoveParticipant(); - } - }); + } + catch (Exception e) + { + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.RemoveParticipant(); + } } private const int BlocksForEachTask = 4; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long IndexOfParallel(RecyclableLongList list, in T item) + public static long IndexOfParallel(RecyclableLongList list, T item) { // & WAS SLOWER //long itemsCount = Array.IndexOf(list._memoryBlocks[0], item, 0, (int)Math.Min(list._blockSize, list._longCount)); @@ -176,8 +117,6 @@ namespace Recyclable.Collections // (long)(_longCount * 0.329) => most efficient step, based on benchmarks //Iterate(this, context, item); - // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. - // & WAS SLOWER without int blockIndex = 2; // & WAS SLOWER @@ -191,7 +130,16 @@ namespace Recyclable.Collections while (blockIndex < lastBlockWithData) { // At this point itemIndex is limited to blockSize range - int. - ScheduleIndexOfTask(context, list, blockIndex, item); + + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + // Task.Factory.StartNew(() => + context.AddParticipant(); + var startingBlock = blockIndex; + Task.Run(() => IndexOfIn4ConsecutiveBlocks(context, list, startingBlock, item)); + + // & WAS SLOWER + // ScheduleIndexOfTask(context, list, blockIndex, item); if (context._isItemFound) { break; @@ -204,7 +152,15 @@ namespace Recyclable.Collections { while (blockIndex < list._lastBlockWithData) { - ScheduleIndexOfTaskSingle(context, list, blockIndex, item); + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + // Task.Factory.StartNew(() => + var startingBlock = blockIndex; + context.AddParticipant(); + Task.Run(() => IndexOfIn1Block(context, list, startingBlock, item)); + + // & WAS SLOWER + // ScheduleIndexOfTaskSingle(context, list, blockIndex, item); if (context._isItemFound) { break; @@ -224,7 +180,7 @@ namespace Recyclable.Collections } } - context.AllDoneSignal.SignalAndWait(); + context.SignalAndWait(); if (context.Exception == null) { try @@ -245,7 +201,7 @@ namespace Recyclable.Collections { context.Dispose(); } - + return RecyclableDefaults.ItemNotFoundIndexLong; } From c97651eb5ccb25429adab59e58fc9db977dc9a80 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:22:02 +0200 Subject: [PATCH 290/305] Task creation benchmarks added. --- ...TaskRunVsTaskFactoryStartNew.Benchmarks.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Recyclable.Collections.Benchmarks/POC/TaskRunVsTaskFactoryStartNew.Benchmarks.cs diff --git a/Recyclable.Collections.Benchmarks/POC/TaskRunVsTaskFactoryStartNew.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/TaskRunVsTaskFactoryStartNew.Benchmarks.cs new file mode 100644 index 0000000..546d108 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/TaskRunVsTaskFactoryStartNew.Benchmarks.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.ObjectPool; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class TaskRunVsTaskFactoryStartNewBenchmarks + { + public int NumberOfTasks = 100; + private static readonly TaskFactory TaskPool = new(); + + [Benchmark] + public void OwnTaskFactory() + { + for (var i = 0; i < NumberOfTasks; i++) + { + _ = TaskPool.StartNew(() => {}); + } + } + + [Benchmark] + public void TaskRun() + { + for (var i = 0; i < NumberOfTasks; i++) + { + _ = Task.Run(() => { }); + } + } + + [Benchmark(Baseline = true)] + public void TaskFactoryStartNew() + { + for (var i = 0; i < NumberOfTasks; i++) + { + _ = Task.Factory.StartNew(() => { }); + } + } + + [Benchmark] + public void TaskConstructor() + { + for (var i = 0; i < NumberOfTasks; i++) + { + new Task(() => { }).Start(); + } + } + + [Benchmark] + public void TaskConstructorWithKnownScheduler() + { + TaskScheduler scheduler = TaskScheduler.Current; + for (var i = 0; i < NumberOfTasks; i++) + { + new Task(() => { }).Start(scheduler); + } + } + } +} \ No newline at end of file From 21c82d80a26c6d80619753e46b24ab66af04e56e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:22:32 +0200 Subject: [PATCH 291/305] Compilation error fixed. --- Recyclable.CollectionsTests/RecyclableLongListTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index 83fa082..2fedc2c 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -25,9 +25,9 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataRecyclableList); } - else if (testData is RecyclableLongList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableLongList) { - list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableLongList); } else if (testData is IList testDataIList) { @@ -69,9 +69,9 @@ namespace Recyclable.CollectionsTests { list.AddRange(testDataRecyclableList); } - else if (testData is RecyclableLongList testDataRecyclableList) + else if (testData is RecyclableLongList testDataRecyclableLongList) { - list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableLongList); } else if (testData is IList testDataIList) { From db8ee72c59e503cec779885ce62d6bce34dd531d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 20:02:50 +0200 Subject: [PATCH 292/305] RecyclableList.IndexOf performance improvements. --- .../Parallel/IndexOfSynchronizationContext.cs | 58 +++++++++++++++++-- .../RecyclableLongList.IndexOfHelpers.cs | 37 +++++++----- 2 files changed, 74 insertions(+), 21 deletions(-) diff --git a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs index 4a53190..99fcd77 100644 --- a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs @@ -3,6 +3,10 @@ namespace Recyclable.Collections.Parallel public sealed class IndexOfSynchronizationContext { private readonly SpinWait _participantsSpinWait = new(); + private readonly SpinLock _participantsSpinLock = new(false); + private readonly SpinLock _indexOfSpinLock = new(false); + private bool _indexOfLockTaken; + internal bool _isItemFound; internal int _participants; @@ -10,12 +14,29 @@ namespace Recyclable.Collections.Parallel public void AddParticipant() { - _ = Interlocked.Increment(ref _participants); + bool lockTaken = false; + _participantsSpinLock.Enter(ref lockTaken); + _participants++; + if (lockTaken) + { + _participantsSpinLock.Exit(false); + } } public void AddParticipants(int newParticipants) { - _ = Interlocked.Add(ref _participants, newParticipants); + bool lockTaken = false; + _participantsSpinLock.Enter(ref lockTaken); + _participants += newParticipants; + if (lockTaken) + { + _participantsSpinLock.Exit(false); + } + } + + public void Lock() + { + _indexOfSpinLock.Enter(ref _indexOfLockTaken); } public void RemoveParticipants(int participantsToRemove) @@ -25,7 +46,13 @@ namespace Recyclable.Collections.Parallel ThrowNotEnoughParticipants(); } - _ = Interlocked.Add(ref _participants, -participantsToRemove); + bool lockTaken = false; + _participantsSpinLock.Enter(ref lockTaken); + _participants -= participantsToRemove; + if (lockTaken) + { + _participantsSpinLock.Exit(false); + } } public void RemoveParticipant() @@ -35,7 +62,13 @@ namespace Recyclable.Collections.Parallel ThrowNoMoreParticipants(); } - _ = Interlocked.Decrement(ref _participants); + bool lockTaken = false; + _participantsSpinLock.Enter(ref lockTaken); + _participants--; + if (lockTaken) + { + _participantsSpinLock.Exit(false); + } } public void SignalAndWait() @@ -45,13 +78,28 @@ namespace Recyclable.Collections.Parallel ThrowNoMoreParticipants(); } - _ = Interlocked.Decrement(ref _participants); + bool lockTaken = false; + _participantsSpinLock.Enter(ref lockTaken); + _participants--; + if (lockTaken) + { + _participantsSpinLock.Exit(false); + } + while (_participants > 0) { _participantsSpinWait.SpinOnce(); } } + public void Unlock() + { + if (_indexOfLockTaken) + { + _indexOfSpinLock.Exit(false); + } + } + private static void ThrowNotEnoughParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are not enough participants."); private static void ThrowNoMoreParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are no more participants."); diff --git a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs index d84126d..62d367b 100644 --- a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs +++ b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs @@ -25,7 +25,9 @@ namespace Recyclable.Collections else if (index >= 0) { context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); + context.Lock(); + context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + index; + context.Unlock(); return; } @@ -37,7 +39,9 @@ namespace Recyclable.Collections else if (index >= 0) { context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 1) << list._blockSizePow2BitShift) + index); + context.Lock(); + context.FoundItemIndex = ((long)(blockIndex + 1) << list._blockSizePow2BitShift) + index; + context.Unlock(); return; } @@ -49,7 +53,9 @@ namespace Recyclable.Collections else if (index >= 0) { context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 2) << list._blockSizePow2BitShift) + index); + context.Lock(); + context.FoundItemIndex = ((long)(blockIndex + 2) << list._blockSizePow2BitShift) + index; + context.Unlock(); return; } @@ -57,7 +63,9 @@ namespace Recyclable.Collections if (!context._isItemFound && index >= 0) { context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 3) << list._blockSizePow2BitShift) + index); + context.Lock(); + context.FoundItemIndex = ((long)(blockIndex + 3) << list._blockSizePow2BitShift) + index; + context.Unlock(); } } catch (Exception e) @@ -87,7 +95,9 @@ namespace Recyclable.Collections if (!context._isItemFound && index >= 0) { context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); + context.Lock(); + context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + index; + context.Unlock(); } } catch (Exception e) @@ -176,7 +186,9 @@ namespace Recyclable.Collections if (!context._isItemFound && itemIndex >= 0) { context._isItemFound = true; - _ = Interlocked.Exchange(ref context.FoundItemIndex, ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex); + context.Lock(); + context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex; + context.Unlock(); } } @@ -249,16 +261,9 @@ namespace Recyclable.Collections } } - //if (blockIndex == list._lastBlockWithData) - //{ - itemIndex = list._nextItemIndex != 0 - ? Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._nextItemIndex) - : Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._blockSize); - //} - //else - //{ - // return RecyclableDefaults.ItemNotFoundIndexLong; - //} + itemIndex = list._nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._blockSize); return itemIndex >= 0 ? itemIndex + ((long)list._lastBlockWithData << list._blockSizePow2BitShift) : RecyclableDefaults.ItemNotFoundIndexLong; } From e3ca5b4f8740c8e2d6b3e6943b8b727431228709 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 4 Jun 2023 16:46:26 +0200 Subject: [PATCH 293/305] Tiny cleanup --- Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs index 62d367b..1092d51 100644 --- a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs +++ b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs @@ -146,7 +146,7 @@ namespace Recyclable.Collections // Task.Factory.StartNew(() => context.AddParticipant(); var startingBlock = blockIndex; - Task.Run(() => IndexOfIn4ConsecutiveBlocks(context, list, startingBlock, item)); + _ = Task.Run(() => IndexOfIn4ConsecutiveBlocks(context, list, startingBlock, item)); // & WAS SLOWER // ScheduleIndexOfTask(context, list, blockIndex, item); @@ -167,7 +167,7 @@ namespace Recyclable.Collections // Task.Factory.StartNew(() => var startingBlock = blockIndex; context.AddParticipant(); - Task.Run(() => IndexOfIn1Block(context, list, startingBlock, item)); + _ = Task.Run(() => IndexOfIn1Block(context, list, startingBlock, item)); // & WAS SLOWER // ScheduleIndexOfTaskSingle(context, list, blockIndex, item); From fb689e84d69130e4287e8a949ef37de1e2c74b77 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 4 Jun 2023 17:21:26 +0200 Subject: [PATCH 294/305] RecyclableLongList.IndexOf stability & performance improvements. --- README.md | 140 +++++++++++++++- .../Parallel/IndexOfSynchronizationContext.cs | 56 ++----- .../Parallel/SpinLockSlimmer.cs | 26 +++ .../RecyclableLongList.IndexOfHelpers.cs | 157 +++++++++++++----- 4 files changed, 298 insertions(+), 81 deletions(-) create mode 100644 Recyclable.Collections/Parallel/SpinLockSlimmer.cs diff --git a/README.md b/README.md index 50e1b98..1f3c896 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,145 @@ This is the direct equivalent of `List` class, except that the arrays are tak * All classes provide large storage capabilities, in practice limited only by the memory available in your system. * All data is stored in blocks, with the provided `int blockSize` or the default, currently being `10,240` items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list. * There is no memory copying to increase the capacity of the recyclable classes, unless the no. of items exceeds the no. of allocated blocks. In that case, a new array of blocks is created to accommodate . If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes. -* ⚠️ `IndexOf` is not guaranteed to return the index of the first item found. In most cases it will, but there may be scenarios when the index of subsequent items on the list is found. That may happen when the no. of items equals to or exceeds 786_432. In that case `IndexOf` will parallelize the search using tasks. +* ⚠️ `IndexOf` will automatically switch to parallel search mode. Collections smaller than (currently) 850_000 items are guaranteed to find the 1st occurrence of the `item` parameter. Collections bigger than (currently) 850_000 items will be scanned using parallelization. If either case it'll return the index of the first matching item. This note also applies to `Contains`, because it internally uses `IndexOf`. The value of 850_000 may become configurable in the future, unless it proves to result in much worse performance. + +The parallelization utilizes default `Task` scheduling, resulting in non-deterministic order in which search tasks are executed. In most cases it resulted in significant performance improvements. Below you can find the benchmarks for various item counts. + +```csharp +BenchmarkDotNet=v0.13.3, OS=Windows 11 (10.0.22621.1702) +AMD Ryzen 7 4700U with Radeon Graphics, 1 CPU, 8 logical and 8 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestCase | BaselineBenchmarkType | BenchmarkType | TestObjectCount | Mean | Error | StdDev | Median | Ratio | RatioSD | Allocated | Alloc Ratio | +|--------- |--------------------- |---------------------- |------------------- |---------------- |----------------------:|---------------------:|-------------------:|----------------------:|------:|------------:|-----------:|------------:| +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 1 | 16.60 ns | 0.060 ns | 0.056 ns | 16.57 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 1 | 19.20 ns | 0.137 ns | 0.121 ns | 19.16 ns | 1.16 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 2 | 27.64 ns | 0.188 ns | 0.176 ns | 27.65 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 2 | 31.90 ns | 0.102 ns | 0.095 ns | 31.89 ns | 1.15 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 3 | 28.35 ns | 0.146 ns | 0.137 ns | 28.42 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 3 | 32.63 ns | 0.177 ns | 0.166 ns | 32.55 ns | 1.15 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 4 | 39.05 ns | 0.112 ns | 0.105 ns | 38.99 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 4 | 44.01 ns | 0.168 ns | 0.149 ns | 43.98 ns | 1.13 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 5 | 39.77 ns | 0.163 ns | 0.152 ns | 39.69 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 5 | 46.72 ns | 0.943 ns | 1.258 ns | 46.87 ns | 1.17 | 0.03 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 6 | 51.14 ns | 0.202 ns | 0.189 ns | 51.10 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 6 | 59.75 ns | 1.076 ns | 1.105 ns | 59.75 ns | 1.17 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 7 | 51.60 ns | 0.126 ns | 0.105 ns | 51.58 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 7 | 58.40 ns | 1.125 ns | 0.997 ns | 58.38 ns | 1.13 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 8 | 61.22 ns | 0.301 ns | 0.281 ns | 61.22 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 8 | 73.96 ns | 0.397 ns | 0.372 ns | 73.98 ns | 1.21 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 9 | 62.21 ns | 0.074 ns | 0.062 ns | 62.22 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 9 | 96.22 ns | 0.712 ns | 0.631 ns | 96.16 ns | 1.55 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 10 | 75.18 ns | 0.244 ns | 0.228 ns | 75.07 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 10 | 83.85 ns | 0.224 ns | 0.209 ns | 83.74 ns | 1.12 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 11 | 74.93 ns | 0.214 ns | 0.200 ns | 74.86 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 11 | 83.78 ns | 0.484 ns | 0.453 ns | 83.53 ns | 1.12 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 12 | 86.86 ns | 0.316 ns | 0.295 ns | 86.88 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 12 | 106.61 ns | 1.246 ns | 1.224 ns | 106.88 ns | 1.23 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 13 | 88.02 ns | 0.273 ns | 0.255 ns | 87.90 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 13 | 96.74 ns | 0.074 ns | 0.066 ns | 96.73 ns | 1.10 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 14 | 100.74 ns | 0.344 ns | 0.322 ns | 100.79 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 14 | 110.46 ns | 0.359 ns | 0.336 ns | 110.27 ns | 1.10 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 15 | 100.67 ns | 0.405 ns | 0.359 ns | 100.55 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 15 | 127.81 ns | 1.472 ns | 1.305 ns | 128.04 ns | 1.27 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 16 | 114.05 ns | 0.520 ns | 0.486 ns | 113.75 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 16 | 141.99 ns | 0.510 ns | 0.477 ns | 141.82 ns | 1.24 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 17 | 114.63 ns | 0.432 ns | 0.404 ns | 114.41 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 17 | 125.91 ns | 0.363 ns | 0.322 ns | 125.79 ns | 1.10 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 18 | 127.90 ns | 0.404 ns | 0.378 ns | 128.07 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 18 | 140.47 ns | 0.452 ns | 0.423 ns | 140.24 ns | 1.10 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 19 | 128.56 ns | 0.375 ns | 0.350 ns | 128.41 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 19 | 152.84 ns | 1.948 ns | 1.822 ns | 152.48 ns | 1.19 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 20 | 150.88 ns | 0.538 ns | 0.477 ns | 150.97 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 20 | 184.59 ns | 0.871 ns | 0.772 ns | 184.61 ns | 1.22 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 30 | 217.63 ns | 0.967 ns | 0.905 ns | 217.71 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 30 | 244.89 ns | 1.417 ns | 1.326 ns | 244.56 ns | 1.13 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 32 | 266.32 ns | 2.378 ns | 2.224 ns | 266.71 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 32 | 310.60 ns | 1.946 ns | 1.820 ns | 310.93 ns | 1.17 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 40 | 362.07 ns | 2.741 ns | 2.564 ns | 362.60 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 40 | 401.02 ns | 1.604 ns | 1.500 ns | 400.31 ns | 1.11 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 50 | 400.35 ns | 3.445 ns | 3.223 ns | 400.41 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 50 | 485.05 ns | 3.031 ns | 2.531 ns | 485.54 ns | 1.21 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 60 | 582.48 ns | 4.689 ns | 4.386 ns | 581.95 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 60 | 664.89 ns | 3.413 ns | 3.192 ns | 663.75 ns | 1.14 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 80 | 845.29 ns | 7.416 ns | 6.937 ns | 843.90 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 80 | 915.71 ns | 5.696 ns | 5.328 ns | 916.91 ns | 1.08 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 90 | 994.91 ns | 15.228 ns | 14.244 ns | 998.73 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 90 | 1,074.04 ns | 12.721 ns | 11.277 ns | 1,077.75 ns | 1.08 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 100 | 1,130.91 ns | 20.393 ns | 19.076 ns | 1,130.99 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 100 | 1,185.56 ns | 23.173 ns | 21.676 ns | 1,184.10 ns | 1.05 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 128 | 1,610.08 ns | 16.899 ns | 15.807 ns | 1,604.90 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 128 | 1,753.55 ns | 17.254 ns | 16.140 ns | 1,752.63 ns | 1.09 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 256 | 5,750.75 ns | 28.609 ns | 26.760 ns | 5,758.04 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 256 | 5,893.43 ns | 29.453 ns | 27.551 ns | 5,887.99 ns | 1.02 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 512 | 21,669.95 ns | 427.575 ns | 399.954 ns | 21,713.08 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 512 | 21,674.73 ns | 68.941 ns | 64.487 ns | 21,668.86 ns | 1.00 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 1024 | 69,740.31 ns | 838.846 ns | 932.375 ns | 69,475.32 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 1024 | 68,980.90 ns | 450.707 ns | 421.591 ns | 68,923.79 ns | 0.99 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 2048 | 244,266.95 ns | 2,449.277 ns | 2,045.257 ns | 243,462.06 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 2048 | 239,887.40 ns | 854.306 ns | 799.118 ns | 240,015.48 ns | 0.98 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 4096 | 904,644.52 ns | 3,379.464 ns | 2,638.463 ns | 904,380.27 ns | 1.00 | 0.00 | 1 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 4096 | 915,459.26 ns | 6,529.766 ns | 6,107.947 ns | 917,327.34 ns | 1.01 | 0.01 | 1 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 8192 | 3,513,716.89 ns | 62,893.465 ns | 90,199.928 ns | 3,474,476.56 ns | 1.00 | 0.00 | 2 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 8192 | 3,486,690.89 ns | 14,705.600 ns | 13,755.627 ns | 3,481,303.91 ns | 0.99 | 0.02 | 2 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 16384 | 13,736,865.85 ns | 120,702.128 ns | 106,999.292 ns | 13,714,744.53 ns | 1.00 | 0.00 | 9 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 16384 | 14,167,800.31 ns | 43,856.428 ns | 41,023.330 ns | 14,149,225.00 ns | 1.03 | 0.01 | 9 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 32768 | 53,875,332.00 ns | 245,220.180 ns | 229,379.108 ns | 53,809,450.00 ns | 1.00 | 0.00 | 60 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 32768 | 55,149,804.00 ns | 273,037.835 ns | 255,399.760 ns | 55,076,500.00 ns | 1.02 | 0.01 | 60 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 65536 | 218,431,663.33 ns | 4,265,864.273 ns | 4,912,575.640 ns | 214,920,550.00 ns | 1.00 | 0.00 | 200 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 65536 | 217,449,610.26 ns | 493,374.080 ns | 411,989.702 ns | 217,368,133.33 ns | 1.01 | 0.02 | 200 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 131072 | 936,350,615.38 ns | 2,290,617.990 ns | 1,912,769.764 ns | 936,031,100.00 ns | 1.00 | 0.00 | 600 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 131072 | 928,423,416.67 ns | 1,034,931.818 ns | 808,006.689 ns | 928,198,750.00 ns | 0.99 | 0.00 | 600 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 849999 | 99,588,892,028.57 ns | 998,348,445.797 ns | 885,009,893.256 ns | 99,218,977,100.00 ns | 1.00 | 0.00 | 600 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 849999 | 102,323,847,080.00 ns | 1,028,672,431.300 ns | 962,220,830.472 ns | 101,982,058,200.00 ns | 1.03 | 0.01 | 600 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 850000 | 113.8 s | 2.26 s | 2.69 s | | 1.00 | 0.00 | 600 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 850000 | 110.3 s | 2.11 s | 2.07 s | | 0.97 | 0.03 | 399750776 B | 666,251.29 | +``` +As `RecyclableLongList` is foreseen for use-cases with big data, you should automatically benefit from it in many use-cases. + * `Insert`, `Remove` & `RemoveAt` methods raise `NotSupportedException`. This is by design in the current version, because it involves performance degradation. I'm planning works around this in the upcoming weeks. If you need that sooner, you're welcomed to file a PR with the proposed changes. # Architecture Decisions diff --git a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs index 99fcd77..ddcde32 100644 --- a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs @@ -2,11 +2,9 @@ namespace Recyclable.Collections.Parallel { public sealed class IndexOfSynchronizationContext { - private readonly SpinWait _participantsSpinWait = new(); - private readonly SpinLock _participantsSpinLock = new(false); - private readonly SpinLock _indexOfSpinLock = new(false); - private bool _indexOfLockTaken; - + private SpinWait _participantsSpinWait = new(); + private SpinLockSlimmer _participantsSpinLock = new(); + private SpinLockSlimmer _indexOfSpinLock = new(); internal bool _isItemFound; internal int _participants; @@ -14,29 +12,21 @@ namespace Recyclable.Collections.Parallel public void AddParticipant() { - bool lockTaken = false; - _participantsSpinLock.Enter(ref lockTaken); + _participantsSpinLock.Enter(); _participants++; - if (lockTaken) - { - _participantsSpinLock.Exit(false); - } + _participantsSpinLock.Exit(); } public void AddParticipants(int newParticipants) { - bool lockTaken = false; - _participantsSpinLock.Enter(ref lockTaken); + _participantsSpinLock.Enter(); _participants += newParticipants; - if (lockTaken) - { - _participantsSpinLock.Exit(false); - } + _participantsSpinLock.Exit(); } public void Lock() { - _indexOfSpinLock.Enter(ref _indexOfLockTaken); + _indexOfSpinLock.Enter(); } public void RemoveParticipants(int participantsToRemove) @@ -46,13 +36,9 @@ namespace Recyclable.Collections.Parallel ThrowNotEnoughParticipants(); } - bool lockTaken = false; - _participantsSpinLock.Enter(ref lockTaken); + _participantsSpinLock.Enter(); _participants -= participantsToRemove; - if (lockTaken) - { - _participantsSpinLock.Exit(false); - } + _participantsSpinLock.Exit(); } public void RemoveParticipant() @@ -62,13 +48,9 @@ namespace Recyclable.Collections.Parallel ThrowNoMoreParticipants(); } - bool lockTaken = false; - _participantsSpinLock.Enter(ref lockTaken); + _participantsSpinLock.Enter(); _participants--; - if (lockTaken) - { - _participantsSpinLock.Exit(false); - } + _participantsSpinLock.Exit(); } public void SignalAndWait() @@ -78,13 +60,9 @@ namespace Recyclable.Collections.Parallel ThrowNoMoreParticipants(); } - bool lockTaken = false; - _participantsSpinLock.Enter(ref lockTaken); + _participantsSpinLock.Enter(); _participants--; - if (lockTaken) - { - _participantsSpinLock.Exit(false); - } + _participantsSpinLock.Exit(); while (_participants > 0) { @@ -94,15 +72,13 @@ namespace Recyclable.Collections.Parallel public void Unlock() { - if (_indexOfLockTaken) - { - _indexOfSpinLock.Exit(false); - } + _indexOfSpinLock.Exit(); } private static void ThrowNotEnoughParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are not enough participants."); private static void ThrowNoMoreParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are no more participants."); + public long FoundBlockIndex; public long FoundItemIndex; public Exception? Exception; public int Participants => _participants; diff --git a/Recyclable.Collections/Parallel/SpinLockSlimmer.cs b/Recyclable.Collections/Parallel/SpinLockSlimmer.cs new file mode 100644 index 0000000..a603b25 --- /dev/null +++ b/Recyclable.Collections/Parallel/SpinLockSlimmer.cs @@ -0,0 +1,26 @@ +namespace Recyclable.Collections.Parallel +{ + public struct SpinLockSlimmer + { + private volatile int _locked; + + public void Enter() + { + if (Interlocked.CompareExchange(ref _locked, 1, 0) == 0) + { + return; + } + + SpinWait waiter = new(); + while (Interlocked.CompareExchange(ref _locked, 1, 0) == 1) + { + waiter.SpinOnce(); + } + } + + public void Exit() + { + _locked = 0; + } + } +} diff --git a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs index 1092d51..21826af 100644 --- a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs +++ b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs @@ -1,4 +1,12 @@ -using System.Runtime.CompilerServices; +// #define MAX_BLOCKS_FOR_TASK_2 +// #define MAX_BLOCKS_FOR_TASK_3 +#define MAX_BLOCKS_FOR_TASK_4 +// #define MAX_BLOCKS_FOR_TASK_5 +// #define MAX_BLOCKS_FOR_TASK_6 +// #define MAX_BLOCKS_FOR_TASK_7 +// #define MAX_BLOCKS_FOR_TASK_8 + +using System.Runtime.CompilerServices; using Recyclable.Collections.Parallel; namespace Recyclable.Collections @@ -8,65 +16,105 @@ namespace Recyclable.Collections public static class IndexOfHelpers { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static void IndexOfIn4ConsecutiveBlocks(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) + private static void IndexOfInConsecutiveBlocks(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) { try { - if (context._isItemFound) + if (context._isItemFound && blockIndex >= context.FoundBlockIndex) { return; } long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); - if (context._isItemFound) + if (index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + index); + return; + } + else if (context._isItemFound) { return; } - else if (index >= 0) - { - context._isItemFound = true; - context.Lock(); - context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + index; - context.Unlock(); - return; - } + +#if MAX_BLOCKS_FOR_TASK_2 || MAX_BLOCKS_FOR_TASK_3 || MAX_BLOCKS_FOR_TASK_4 || MAX_BLOCKS_FOR_TASK_5 || MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 index = Array.IndexOf(list._memoryBlocks[blockIndex + 1], itemToFind, 0, list._blockSize); - if (context._isItemFound) + if (index >= 0) { + ExchangeFoundItemIndexIfLower(context, blockIndex + 1, ((long)(blockIndex + 1) << list._blockSizePow2BitShift) + index); return; } - else if (index >= 0) + else if (context._isItemFound) { - context._isItemFound = true; - context.Lock(); - context.FoundItemIndex = ((long)(blockIndex + 1) << list._blockSizePow2BitShift) + index; - context.Unlock(); return; } +#endif + +#if MAX_BLOCKS_FOR_TASK_3 || MAX_BLOCKS_FOR_TASK_4 || MAX_BLOCKS_FOR_TASK_5 || MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, list._blockSize); - if (context._isItemFound) + if (index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 2, ((long)(blockIndex + 2) << list._blockSizePow2BitShift) + index); + return; + } + else if (context._isItemFound) { return; } - else if (index >= 0) - { - context._isItemFound = true; - context.Lock(); - context.FoundItemIndex = ((long)(blockIndex + 2) << list._blockSizePow2BitShift) + index; - context.Unlock(); - return; - } + +#endif + +#if MAX_BLOCKS_FOR_TASK_4 || MAX_BLOCKS_FOR_TASK_5 || MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, list._blockSize); if (!context._isItemFound && index >= 0) { - context._isItemFound = true; - context.Lock(); - context.FoundItemIndex = ((long)(blockIndex + 3) << list._blockSizePow2BitShift) + index; - context.Unlock(); + ExchangeFoundItemIndexIfLower(context, blockIndex + 3, ((long)(blockIndex + 3) << list._blockSizePow2BitShift) + index); } + +#endif + +#if MAX_BLOCKS_FOR_TASK_5 || MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 4], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 4, ((long)(blockIndex + 4) << list._blockSizePow2BitShift) + index); + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 5], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 5, ((long)(blockIndex + 5) << list._blockSizePow2BitShift) + index); + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 6], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 6, ((long)(blockIndex + 6) << list._blockSizePow2BitShift) + index); + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 7], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 7, ((long)(blockIndex + 7) << list._blockSizePow2BitShift) + index); + } + +#endif } catch (Exception e) { @@ -81,6 +129,21 @@ namespace Recyclable.Collections } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ExchangeFoundItemIndexIfLower(IndexOfSynchronizationContext context, int blockIndex, long newItemIndex) + { + context.Lock(); + + if (newItemIndex < context.FoundItemIndex || context.FoundItemIndex == RecyclableDefaults.ItemNotFoundIndexLong) + { + context.FoundBlockIndex = blockIndex; + context.FoundItemIndex = newItemIndex; + } + + context._isItemFound = true; + context.Unlock(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static void IndexOfIn1Block(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) { @@ -94,10 +157,7 @@ namespace Recyclable.Collections long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); if (!context._isItemFound && index >= 0) { - context._isItemFound = true; - context.Lock(); - context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + index; - context.Unlock(); + ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + index); } } catch (Exception e) @@ -110,7 +170,27 @@ namespace Recyclable.Collections } } +#if MAX_BLOCKS_FOR_TASK_2 + private const int BlocksForEachTask = 2; +#endif +#if MAX_BLOCKS_FOR_TASK_3 + private const int BlocksForEachTask = 3; +#endif +#if MAX_BLOCKS_FOR_TASK_4 private const int BlocksForEachTask = 4; +#endif +#if MAX_BLOCKS_FOR_TASK_5 + private const int BlocksForEachTask = 5; +#endif +#if MAX_BLOCKS_FOR_TASK_6 + private const int BlocksForEachTask = 6; +#endif +#if MAX_BLOCKS_FOR_TASK_7 + private const int BlocksForEachTask = 7; +#endif +#if MAX_BLOCKS_FOR_TASK_8 + private const int BlocksForEachTask = 8; +#endif [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public static long IndexOfParallel(RecyclableLongList list, T item) @@ -146,7 +226,7 @@ namespace Recyclable.Collections // Task.Factory.StartNew(() => context.AddParticipant(); var startingBlock = blockIndex; - _ = Task.Run(() => IndexOfIn4ConsecutiveBlocks(context, list, startingBlock, item)); + _ = Task.Run(() => IndexOfInConsecutiveBlocks(context, list, startingBlock, item)); // & WAS SLOWER // ScheduleIndexOfTask(context, list, blockIndex, item); @@ -185,10 +265,7 @@ namespace Recyclable.Collections int itemIndex = Array.IndexOf(list._memoryBlocks[list._lastBlockWithData], item, 0, list._nextItemIndex > 0 ? list._nextItemIndex : list._blockSize); if (!context._isItemFound && itemIndex >= 0) { - context._isItemFound = true; - context.Lock(); - context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex; - context.Unlock(); + ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex); } } From 79c3eeb33fb9941658d667a0172d69df2a742b89 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 4 Jun 2023 17:26:52 +0200 Subject: [PATCH 295/305] Test cases updated --- Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 5ecdb2a..8565d27 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -36,7 +36,7 @@ namespace Recyclable.Collections.Benchmarks.POC // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 100_000_000)] - // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000, RecyclableDefaults.MaxPooledBlockSize)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 850_000 - 1, 1_000_000, RecyclableDefaults.MaxPooledBlockSize)] // [Params(1_000_000, 10_000_000, 100_000_000, 1_000_000_000)] // [Params(192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144)] // [Params(524_288, 786_432, 1_048_576)] @@ -44,6 +44,8 @@ namespace Recyclable.Collections.Benchmarks.POC [Params(850_000)] // [Params(RecyclableDefaults.MaxPooledBlockSize)] // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000, 33_554_432, 50_000_000, 100_000_000)] + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, RecyclableDefaults.MinItemsCountForParallelization - 1)] + //[Params(RecyclableDefaults.MinItemsCountForParallelization, 1_048_576, 2_097_152, 4_194_304, 8_388_608, 16_777_216, 33_554_432, RecyclableDefaults.MaxPooledBlockSize)] // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128)] // [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)] // [Params(1_048_576)] From 5b961c73d114e370451c5eee1c0fa58b5f641416 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 06:39:30 +0200 Subject: [PATCH 296/305] RecyclableLongList fixes to LastBlockWithData calculations. Tests extended. --- Recyclable.Collections/RecyclableLongList.cs | 28 +++++++++++++------ .../RecyclableLongListTests.cs | 18 ++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index 5b17dd3..14b7ffd 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -37,7 +37,7 @@ namespace Recyclable.Collections public int Count => checked((int)_longCount); public bool IsReadOnly { get; } - private int _lastBlockWithData; + private int _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; public int LastBlockWithData => _lastBlockWithData; internal long _longCount; @@ -50,6 +50,7 @@ namespace Recyclable.Collections public int ReservedBlockCount => _reservedBlockCount; public int BlockSize => _blockSize; + public int BlockSizeMinus1 => _blockSizeMinus1; public byte BlockSizePow2BitShift => _blockSizePow2BitShift; public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -470,10 +471,15 @@ namespace Recyclable.Collections _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; if (_nextItemIndex == _blockSize) { + if (_nextItemBlockIndex > _lastBlockWithData) + { + _lastBlockWithData++; + } + _nextItemBlockIndex++; _nextItemIndex = 0; } - else if (_nextItemIndex == 1) + else if (_nextItemIndex == 1 && _nextItemBlockIndex > _lastBlockWithData) { _lastBlockWithData++; } @@ -501,28 +507,32 @@ namespace Recyclable.Collections Span itemsSpan = new(items); Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (targetBlockArraySpan.Length <= itemsSpan.Length) + while (itemsSpan.Length >= targetBlockArraySpan.Length) { itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; - targetBlockIndex++; - if (targetBlockIndex == memoryBlockCount) + if (itemsSpan.Length == 0) { break; } + targetBlockIndex++; targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); } if (itemsSpan.Length > 0) { itemsSpan.CopyTo(targetBlockArraySpan); + _nextItemBlockIndex = targetBlockIndex; + } + else + { + _nextItemBlockIndex = targetBlockIndex + 1; } _nextItemIndex = itemsSpan.Length; _longCount = targetCapacity; - _nextItemBlockIndex = targetBlockIndex; - _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); + _lastBlockWithData = targetBlockIndex; } public void AddRange(RecyclableList items) @@ -822,7 +832,7 @@ namespace Recyclable.Collections } Span memoryBlocksSpan = new(_memoryBlocks); - int lastBlockIndex = _nextItemBlockIndex; + int lastBlockIndex = _lastBlockWithData; for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) { if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) @@ -831,7 +841,7 @@ namespace Recyclable.Collections } } - return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + return Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex > 0 ? _nextItemIndex : _blockSize) >= 0; } public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index 2fedc2c..3f2f53c 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -45,6 +45,7 @@ namespace Recyclable.CollectionsTests // Validate int expectedItemsCount = testData.Count(); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize != 0 ? 0 : 1)); _ = list.LongCount.Should().Be(expectedItemsCount); _ = list.Should().ContainInConsecutiveOrder(testData); } @@ -88,6 +89,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Capacity.Should().Be(0); + _ = list.LastBlockWithData.Should().Be(-1); _ = list.LongCount.Should().Be(0); _ = list.Should().BeEmpty(); } @@ -144,6 +146,7 @@ namespace Recyclable.CollectionsTests } _ = list.LongCount.Should().Be(expectedData.Length); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(expectedData); } @@ -163,6 +166,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData); } @@ -181,6 +185,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(itemsCount, "we added so many items"); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize == 0 ? 1 : 0)); _ = list.Should().ContainInConsecutiveOrder(testData); } @@ -213,6 +218,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); } @@ -572,11 +578,13 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); } _ = list.Should().BeEmpty(); _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); } [Theory] @@ -594,11 +602,13 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); } _ = list.Should().BeEmpty(); _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); } [Theory] @@ -616,8 +626,13 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); } [Theory] @@ -634,11 +649,14 @@ namespace Recyclable.CollectionsTests _ = list.Remove(item).Should().BeTrue($"we search for {item} which should exist"); removedCount++; var expectedList = expectedData[0..((int)itemsCount - removedCount)]; + _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.LongCount.Should().Be(expectedList.Length); _ = list.Should().ContainInConsecutiveOrder(expectedList); } _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); } } } \ No newline at end of file From 00a70188935e20b7a7bc7efa76c0b84c77974a85 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 08:57:14 +0200 Subject: [PATCH 297/305] Tiny cleanup. --- Recyclable.Collections/Parallel/BarrierSlimmer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/Parallel/BarrierSlimmer.cs b/Recyclable.Collections/Parallel/BarrierSlimmer.cs index 2c5e594..4a859b6 100644 --- a/Recyclable.Collections/Parallel/BarrierSlimmer.cs +++ b/Recyclable.Collections/Parallel/BarrierSlimmer.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections.Parallel { - public sealed class BarrierSlimmer : IDisposable + public sealed class BarrierSlimmer : IDisposable { private static readonly ObjectPool _defaultPool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); private ObjectPool? _assignedPool; From 2157d8b6acadae40a881dcac687d040720acf25c Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 08:57:54 +0200 Subject: [PATCH 298/305] RecyclableLongList fixes to LastBlockWithData calculations. README.md updated. --- README.md | 92 +++++++++++++------- Recyclable.Collections/RecyclableLongList.cs | 28 +++--- 2 files changed, 76 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 1f3c896..8086cb5 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ 1. ✅ `Resize` 1. ✅ `this[int index]` 1. ✅ Port `RecyclableList` implementation to `RecyclableLongList` -1. Optimize `RecyclableLongList` +1. 👉 Optimize `RecyclableLongList` 1. ✅ `Add` 1. ✅ `AddRange` 1. ✅ when source is `array` @@ -67,16 +67,16 @@ 1. ✅ `Resize` 1. ✅ `this[int index]` 1. ✅ `this[long index]` - 1. 🅿️ Hide not ready classes + 1. ✅ Rename `RecyclableList` to `RecyclableLongList` + 1. ✅ Rename `RecyclableArrayList` to `RecyclableList` + 1. ✅ Fix failing tests + 1. 👉 Hide not ready classes 1. 🅿️ `RecyclableQueue` 1. 🅿️ `RecyclableSortedList` 1. 🅿️ `RecyclableStack` 1. 🅿️ `RecyclableUnorderedList` - 1. ✅ Rename `RecyclableList` to `RecyclableLongList` - 1. ✅ Rename `RecyclableArrayList` to `RecyclableList` - 1. 👉 Add support for `ReadOnlySpan` - 1. 🅿️ Convert `_memoryBlocks` to `Array` to allow `ulong` lengths - 1. 🅿️ Convert block indexes from `int` to `ulong` or `long` + 1. 🅿️ Add support for `ReadOnlySpan` + 1. 🅿️ Release 0.0.3-alpha 1. 🅿️ Implement `List` interfaces 1. 🅿️ `ICollection` 1. 🅿️ `IEnumerable` @@ -86,41 +86,69 @@ 1. 🅿️ `IReadOnlyList` 1. 🅿️ `ICollection` 1. 🅿️ `IList` - 1. Make sure that `NeedsClearing` is used & items are cleared in + 1. 🅿️ Make sure that `NeedsClearing` is used & items are cleared in 1. 🅿️ `Clear` 1. 🅿️ `Dispose` 1. 🅿️ `Remove` 1. 🅿️ `RemoveAt` 1. 🅿️ `RemoveBlock` - 1. 🅿️ Replace `Math` class usages with `if` statements - 1. 🅿️ Replace `a - b > 0` & `a - b < 0` comparisons with `a > b` & `a < b` - 1. 🅿️ Replace `a + b > 0` & `a + b < 0` comparisons with `a > b` & `a < b` - 1. 🅿️ Replace `a / b` & `a * b` calculations with equivalents, where possible - 1. 🅿️ Replace virtual calls with static calls - 1. 🅿️ Replace `blockSize` sums by powers of 2, minus 1 - 1. 🅿️ Remove type castings, if possible - 1. 🅿️ Add type casting to `long` for `<<` & `>>` operations, where required - 1. 🅿️ Make type castings `checked` + 1. 🅿️ Release 0.0.3-beta + 1. 🅿️ Add support for `ulong` indexing + 1. 🅿️ Convert `_memoryBlocks` to `Array` to allow `ulong` lengths + 1. 🅿️ Convert block indexes from `int` to `ulong` or `long` + 1. 🅿️ Final optimizations + 1. 🅿️ Replace `Math` class usages with `if` statements + 1. 🅿️ Replace `a - b > 0` & `a - b < 0` comparisons with `a > b` & `a < b` + 1. 🅿️ Replace `a + b > 0` & `a + b < 0` comparisons with `a > b` & `a < b` + 1. 🅿️ Replace `a / b` & `a * b` calculations with equivalents, where possible + 1. 🅿️ Replace virtual calls with static calls + 1. 🅿️ Replace `blockSize` sums by powers of 2, minus 1 + 1. 🅿️ Remove type castings, if possible + 1. 🅿️ Convert generic methods to non-generic + 1. 🅿️ Overflow review + 1. 🅿️ Add type casting to `long` for `<<` & `>>` operations, where required + 1. 🅿️ Make type castings `checked` 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableList` - 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field - 1. 🅿️ Cleanup `RecyclableLongListExtensions` - 1. 🅿️ Convert generic methods to non-generic -1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableQueue` -1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableStack` -1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableSortedList` -1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableUnorderedList` - 1. 🅿️ `RecyclableList` - 1. 🅿️ `RecyclableLongList` - 1. 🅿️ `RecyclableQueue` - 1. 🅿️ `RecyclableSortedList` - 1. 🅿️ `RecyclableStack` - 1. 🅿️ `RecyclableUnorderedList` + 1. 🅿️ Release 0.0.3 +1. 🅿️ Implement `RecyclableQeueue` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableQueue` + 1. 🅿️ Release 0.0.4 +1. 🅿️ Implement `RecyclableStack` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableStack` + 1. 🅿️ Release 0.0.5 +1. 🅿️ Implement `RecyclableSortedList` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableSortedList` + 1. 🅿️ Release 0.0.6 +1. 🅿️ Implement `RecyclableUnorderedList` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableUnorderedList` + 1. 🅿️ Release 0.0.7 1. 🅿️ Optimize `OneSizeArrayPool` 1. 🅿️ Review locks 1. 🅿️ Measure multi-threading performance -1. 🅿️ Optimize `RecyclableArrayPool` +1. 🅿️ Review `RecyclableArrayPool` 1. 🅿️ Review locks 1. 🅿️ Measure multi-threading performance +1. 🅿️ Optimize `MemoryBucket` + 1. 🅿️ Convert to `struct`, if possible + 1. 🅿️ Find out if there are better replacements + 1. 🅿️ Release 0.0.8 +1. 🅿️ Optimize + 1. 🅿️ `IndexOfSynchronizationContext` + 1. 🅿️ `IndexOfSynchronizationContextPool` + 1. 🅿️ `ManualResetEventSlimmer` + 1. 🅿️ `ManualResetEventSlimmerPool` + 1. 🅿️ `SpinLockSlimmer` +1. 🅿️ Release 0.0.9-beta +1. 🅿️ Cleanup + 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field + 1. 🅿️ Cleanup `RecyclableLongListExtensions` + 1. 🅿️ `ListExtensions` + 1. 🅿️ `MathUtils` +1. 🅿️ Optimize + 1. 🅿️ `ListExtensions` + 1. 🅿️ `MathUtils` + 1. 🅿️ `SystemRandomNumberGenerator` +1. 🅿️ Release 1.0.0 # Characteristics of the classes @@ -303,7 +331,7 @@ Having protection against invalid state of the object is an important part of so This decision is driven by performance measures. Considering wide use of the lists on the hot-paths, each additional check & operation starts counting. I've decided to eliminate all the checks for the sake of performance. If you feel that you need additional protection, feel free to inherit or reuse any of the provided classes to provide that. ## `this[int index]`, `Count`, `IndexOf(T item)` etc. may overflow, but... -All the classes included in this package support `long` indexing and addressing. The `LongCount` property will always provide the total no. of items stored on the list. Since `Count` is just `LongCount` typecasted to `int`, its value may overflow. +All the classes included in this package support `long` indexing and addressing. The `LongCount` property will always provide the total no. of items stored on the list. Since `Count` is just `LongCount` type-casted to `int`, its value may overflow. But `List` addressing is kind of currently limited to `int` ranges anyway in practical scenarios. As such `Count` overflowing shouldn't be an issue when using the classes with existing code. If you use `Count` in your loops, it's recommended to replace it with `LongCount`, tough. You don't need to make any changes in `foreach` or other iterator loops. They are expected to transparently use the new classes & yield all items, beyond `int.MaxValue` range. diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index 14b7ffd..242974e 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -940,10 +940,6 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; - if (_nextItemIndex == 0) - { - _lastBlockWithData--; - } } else { @@ -951,6 +947,11 @@ namespace Recyclable.Collections _nextItemBlockIndex--; } + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } + if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it @@ -1004,13 +1005,10 @@ namespace Recyclable.Collections CopyItems(this, index); } + if (_nextItemIndex > 0) { _nextItemIndex--; - if (_nextItemIndex == 0) - { - _lastBlockWithData--; - } } else { @@ -1018,6 +1016,11 @@ namespace Recyclable.Collections _nextItemBlockIndex--; } + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } + if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it @@ -1044,10 +1047,6 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; - if (_nextItemIndex == 0) - { - _lastBlockWithData--; - } } else { @@ -1055,6 +1054,11 @@ namespace Recyclable.Collections _nextItemBlockIndex--; } + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } + if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it From e2ef281af61e96aa06d4e0170e5e17554b8a687e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 08:59:35 +0200 Subject: [PATCH 299/305] README.md updated --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8086cb5..5ad53f5 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ 1. 🅿️ `RecyclableStack` 1. 🅿️ `RecyclableUnorderedList` 1. 🅿️ Add support for `ReadOnlySpan` + 1. 🅿️ Implement list versioning to allow data change identification 1. 🅿️ Release 0.0.3-alpha 1. 🅿️ Implement `List` interfaces 1. 🅿️ `ICollection` From e5d617eaece53206d7aaf08e9ca01b4100541fa2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 09:07:48 +0200 Subject: [PATCH 300/305] Not read classes made internal --- Recyclable.Collections/RecyclableQueue.cs | 2 +- Recyclable.Collections/RecyclableSortedList.cs | 2 +- Recyclable.Collections/RecyclableStack.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index ed00fe4..070ebcd 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public class RecyclableQueue : IRecyclableOwner, IList, IDisposable + internal class RecyclableQueue : IRecyclableOwner, IList, IDisposable { private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs index b947e9d..d1d9ede 100644 --- a/Recyclable.Collections/RecyclableSortedList.cs +++ b/Recyclable.Collections/RecyclableSortedList.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections { - public class RecyclableSortedList : RecyclableLongList<(TKey Key, TValue Value)> + internal class RecyclableSortedList : RecyclableLongList<(TKey Key, TValue Value)> where TKey : notnull { private static readonly IComparer<(TKey, TValue)> _comparer = new FrequencyTupleComparer(); diff --git a/Recyclable.Collections/RecyclableStack.cs b/Recyclable.Collections/RecyclableStack.cs index 50d7140..b885708 100644 --- a/Recyclable.Collections/RecyclableStack.cs +++ b/Recyclable.Collections/RecyclableStack.cs @@ -2,7 +2,7 @@ namespace Recyclable.Collections { - public class RecyclableStack : IList, IDisposable + internal class RecyclableStack : IList, IDisposable { private bool _disposedValue; private readonly int _blockSize; From a2ff080ff6434d22a8cfa66f1eed3fb0ecbe72d3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 09:11:02 +0200 Subject: [PATCH 301/305] Compilation errors fixed. --- Recyclable.Collections/RecyclableLongList.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index 242974e..e1ac32c 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using Recyclable.Collections.Pools; [assembly: InternalsVisibleTo("Recyclable.Collections.Benchmarks")] +[assembly: InternalsVisibleTo("Recyclable.CollectionsTests")] namespace Recyclable.Collections { From 3ae44a0d2126cddfed8b2c9daefdd872160da8ef Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 09:13:32 +0200 Subject: [PATCH 302/305] RecyclableQueueTests extended --- Recyclable.CollectionsTests/RecyclableQueueTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Recyclable.CollectionsTests/RecyclableQueueTests.cs b/Recyclable.CollectionsTests/RecyclableQueueTests.cs index fbbeea0..18fe0a8 100644 --- a/Recyclable.CollectionsTests/RecyclableQueueTests.cs +++ b/Recyclable.CollectionsTests/RecyclableQueueTests.cs @@ -16,6 +16,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Should().HaveCount(_testData.Length) .And.ContainInConsecutiveOrder(_testData); + _ = list.LongCount.Should().Be(_testData.Length); } [Fact] @@ -26,6 +27,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Should().BeEmpty(); + _ = list.LongCount.Should().Be(0); } [Fact] From 66adffe4e07ebf4f1639cddeec286ead8521b067 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 09:18:19 +0200 Subject: [PATCH 303/305] README.md updated --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5ad53f5..4ca2a1b 100644 --- a/README.md +++ b/README.md @@ -70,14 +70,14 @@ 1. ✅ Rename `RecyclableList` to `RecyclableLongList` 1. ✅ Rename `RecyclableArrayList` to `RecyclableList` 1. ✅ Fix failing tests - 1. 👉 Hide not ready classes - 1. 🅿️ `RecyclableQueue` - 1. 🅿️ `RecyclableSortedList` - 1. 🅿️ `RecyclableStack` - 1. 🅿️ `RecyclableUnorderedList` - 1. 🅿️ Add support for `ReadOnlySpan` + 1. ✅ Hide not ready classes + 1. ✅ `RecyclableQueue` + 1. ✅ `RecyclableSortedList` + 1. ✅ `RecyclableStack` + 1. ✅ `RecyclableUnorderedList` + 1. 👉 Add support for `ReadOnlySpan` 1. 🅿️ Implement list versioning to allow data change identification - 1. 🅿️ Release 0.0.3-alpha + 1. ✅ Release 0.0.3-alpha 1. 🅿️ Implement `List` interfaces 1. 🅿️ `ICollection` 1. 🅿️ `IEnumerable` From e03daf337f0eec825cf68b9c787cc5d053191974 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 09:44:20 +0200 Subject: [PATCH 304/305] NuGet packages updated to latest versions. --- .../Recyclable.Collections.Benchmarks.csproj | 2 +- Recyclable.Collections/Recyclable.Collections.csproj | 6 +++--- .../Recyclable.CollectionsTests.csproj | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj b/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj index 912d3fb..59e656d 100644 --- a/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj +++ b/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj @@ -10,7 +10,7 @@ - + diff --git a/Recyclable.Collections/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj index f920bca..0dddff3 100644 --- a/Recyclable.Collections/Recyclable.Collections.csproj +++ b/Recyclable.Collections/Recyclable.Collections.csproj @@ -7,7 +7,7 @@ enable true Recyclable.Collections - 0.0.3 + 0.0.3.1-alpha Marek Lemańczyk N/A Recyclable.Collections @@ -19,7 +19,7 @@ LICENSE.md README.md git - RecyclableSortedList added. Minor improvements & cleanups. + `RecyclableList<T>` and `RecyclableLongList<T>` implemented. NuGet packages updated. AnyCPU;x64 @@ -34,6 +34,6 @@ - + diff --git a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj index 4514a44..215afa8 100644 --- a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj +++ b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj @@ -11,10 +11,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From fce08f923dc06f6b20722ccde152d32e3b1091a2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 09:59:18 +0200 Subject: [PATCH 305/305] ExceptionExtensions made internal to avoid conflicts with other packages. --- Recyclable.Collections/ExceptionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections/ExceptionExtensions.cs b/Recyclable.Collections/ExceptionExtensions.cs index 43167e4..e0a4f10 100644 --- a/Recyclable.Collections/ExceptionExtensions.cs +++ b/Recyclable.Collections/ExceptionExtensions.cs @@ -2,7 +2,7 @@ namespace Recyclable.Collections { - public static class ExceptionExtensions + internal static class ExceptionExtensions { public static void CaptureAndRethrow(this Exception ex) {

G zpns|9;*nB=gRVn@N**(Uvp&MJhOqmluDH$ZEcni)<2G{L^8pTI!TLMm+W@ttVasl+ z4|(cC(qkRR!Y(xfc0W0ax!DA(R^;pvRnRti4z#s3Jzt*MqrU2%$gvL$*~iN0j#zBs zk8H<4I$rWJ@9kt@F~)j>XfwLUUc@pRgoZ34_7E~A6id23ECk@hgeh?AOi~LG63KJ+ zw%N8jSbDGG@`P9>(s5I0JbJ)6AYC+q!T{Rn*(TT-6 zZWg88c_mJgo*@#7@->byXCLy?Z|jS$uC~^9M^V^sEEAl|*dIoL!a>n6q0ev*c3}YN zZg+FuacDA?MuNF@p2buo>vGFO@Fb=qvHNKEXb z<4N*=BL|_ryjjw*%qD#Ajpaxml))HqEDS;}UUf=XrAx$JGjAZ4-j?f%;=FQtv_&HK z5+TI8A0tWReS(rdp3M|{xiH+jWf~1#Xa!@FG@OEAgTi#jT0Rhrests;RLRONpbx~} zZoDfYlt5h#L^!qy;=7C)LusOFz5D#SDeCFlhx3v*1JZ)-NNMb2z z3rQJE;>t*k=b2ur`TX%8SIy4E<=@@;u2Lk>k^=>X$j?VE;{`kI&mg2^zteL5g&}mU z`O+)qCt}H88s6Fe-v1t>t~?7RK{)@d-=Odl9Re)lr#*gS!w&ZT>~T|61|$F?`jv>Dw(D z{Tw{Q$PD2k&;;YHEfP)a`JvuiLx7eW%)Y}e(e1W5177-hR<@=3{$rY!-j-QTPcEhv&|P5?_XLP5e-z8uc(y18w5I_3QR2& zL~ym^`jsOhyQ|h6Mi`@7Z9Fz0;H0J)j)Kcu$o$dywGuw_!y+Qy1H9eN`c~++*7e8R z*h$Z@Ge+Ho=1enpb611cN5kt4Rfp~8j3a_DElqAeqNXHwtgrK46eQ^xkZOhHjJQ5C z$E;Uf>KRbqt1lKt`g3_34VRYOrAqk|*k@OO`$7YQ324HEIkB+kGUUFz@>sh8b`JSg zjSJx;8h#KWFF;(XCxYvni*1PLrWiH6ABK;V%Vd`vruLE38S$_y+rHG)8}H|fol*g^ z8nzetp7|a1H-7ffO*OqKMK@~mO=Jf|uJ%JJT-nIy0EK8d9+VIe8K2Y_CL2Gu4h2mS z`*(?eKyjaEz`nQL0a#ViCea8fmo|+W7A6Cr@u*}a$eqOFTnPgf(`;f+!iGYA(ksv>T3L|<Er)x!tA2f*W~ep7W|$e{QsG7S;k>OP!P#LLJ{1z&fKGe7`Td$)}0pS}9zHzKxTa z84e@Z{F7^KR1NnaH#|V)g!QxQxC;}ANxwx*TmFNJUi&P9HUv~4mEDw)1*?ksT z+TB-4vOLKfj>aYjW=TMeb#mpyzcs$2oNnh4Vpn@T_u+EOF7?$^shBQp5F464 z4h-UEIE+4Q3Vb-Yn^o|UH=}c+uh#bLbF!^Ufx+SJ!SZ>#wmRw3*Z~dgoER%0YMUtO zr#5J!oOX{vo>p|HvSUyI>nL^k<$n;0;*Mt#kUgAG{3yP(Rp~A;ZH4Ti%WbOh0QT#- z8$^V(f3A|dU#yO?P5xPOu5y|tfdnNap%l@ypXn;k7u=8Lk^)+&#@WT|Jrhwf)?9+o z`yKpT{7e`0R0|9O8xqt>))_{u+D5ZSRi*a(r3T2r-I%Ig&x@egY#lF#7n2}$-X7pPp24={QPH1Q*; zM&@hXg=r>AR7M_-4vj^b{cCZ)iY8la_N{tG{^jWOY@(vl-+?}!O%VBcUBR4>m=R{!Jhp%D)oiO?<@I0 z7RpQw*&?Pj|1+zobXDzzm{(E|c}V6A?g+i^Wn}h4#13bq>~eOxg?P_0V`B{D#h?Ex zRuTFMdz8SpG~Vx83Mg}d=DND#?b<6dZBn~^-E%-?Z*3uJ{B*_$Hh~_=AV=GKL&o%Pu>1Q(ZOR`#zBEc zF%ok2E}D2>3nywDXRwNJas^zx%?x~CltA>H9OQ;J?k zz=Jo=uUm+W$-M?1RA}CLUiXbpk@=h_Y5X|=yQPhSJvD*%Ei_D_eT|WHd!L;os zWRaSX%}jRcKC&eVO&ygRVqBL7nLRs!d)bv@^=2C@vLYqZj86&37LZ-dEPFHgO{;xl zA^~GId&WYe9S-k-0<1S@mdFEyI=={iEG$ul$EWh(y)c;PX03S%t5n$sQ5G`axq*vd z3HkXji@Y9aoG%>V(MTNhD!>JhQLdhXDUEt6riucN;$L$E`9LKD$5Hp-sDzZ>?M6Vntv)VfvqJNq&0NZlz z$*a&?jRC)LS!cZobQI(B{=D)-K4@#-Pt6Tj&ehV?%&26X!8Ge1tzM0mTKS$+h-`?2 zO5>$biKND;v8V86wsTt$nL|H_tlvt zMct1y5we|T{an(eUeBRev#{5*ex%^AMgl}h`E4ZIcb`C$MtWT?I*lkDhs*`X{Jw4L zre&+5|GrShNw<9*Fo?){(OMiWjN6!d2O+mm{*on-;p&pc^sQ0xSW?O1<^c8At^@Y* zNDGIcpR7?)xp+!tp@9M}afM~EMVzNw)_FKTfd+w!7h!Db5J0D^M+Rr_@Ag_bPIN!! zz7M?;9D>}(j5x;~RI()lWO=Eev2X(pSumDtr4~D88zS`(Rr$t=FvmdW$6+ghf(8QE z)>58HJY10T7_WRr%N%p_#kG+>G$W3#!bj$9fO~cmshX7g155y!}7r8o94B1$7LUlUBzw|ucv-?%x5f6(@6&4t{%hr~!7};x6+B6|7Vtw0eS&MX z?7k$^!M`Ny zzYA*6_QZb+RRLz}ztNBk%%OC#KlC%e3ugA+=LXG}J=BN%o;A1Uar5TlQ1Hh{`2RvR zvwU;ur5r5Oe0NNO>?|?{0|G$!xdbh4t z0tBh)rI;&i(IZtZ=s{`@wqFo<;dSkQHqG!nX z-PNczkaq^d{Aora&M5W|1p5yHo3rm2spV{9_0}(2v1&`Ce2(iEQ$0;ZCd%yoyazCLK5S}!@aw)GY! z>b{?PMWJQR3h->M4$O5__@$+|SZ9NdIk=UjCpTdT zbXX3GJt3^SvK{Ig;LZUBk(FGSLa<)HNo9h33k4aBa_F6PyC@=RP8*C0r33=N z^b_cZJ9DqWfr!+QO)2k)YT%N19V!j-r`avym#4dsb=X@xB{7B{HMM9$Ay?We4U@2W zh9V;&IygZznsfhY<&Ld52$)C}kZ+aez(nexU>Rx_;ATTc7xEdqXsvOVDyH*&1G$>G zh$d$F*yBrpQ0)8%E6IF)z+{AcxkgCfwp7tW)(_q=)$R|F=43(VQG>Kfax|G|01|v| ze5Yq=uK`g#+s)3gIA|0ZS$)onZoZQ%ok8|c!GKlc41MLXfa5@09A<7sZ|Ew`d;I|F zn~8(cJYdoSQUAV)#7(>(*( zaLA+Fzj`%LU_>2Aj}=P$*okbALmsAYO#ZGa@~wqqZyUeJXr?fSR@28?4euU7i6Lj> z*v|;*Zi_>wId=Ct=2X#1olSsD1ud>#Q+2RzpkTqk)xhvY=>e{j#5qE)V_|X%mQAv! ztw?Uds?*`OT|gl*YRr$6Pv|W1P!T{Fc|!obm@YN#q1J^h%q-80m6N=bE7D~s87Y&^ z`J$O|oK{y(iqaru&uE?X3r-&Fse3~20Ji_)ovy^KVt)5K-2*EWzr9|IYy3WM8@BCLTlBHFyq!^L|1``1|S9i4BT;* zE?;wAlNCk}dUNw4J9XRHOmA44*}p=Fmq@$JjYd8zx1VHO=I`>F056+>fO9t1^8rwW zK$$d^+{FTh^VL}^bWEWzkIc;aQ=zQDT?;;Ey<5)KLNuDbr4bEV_JX_5zLIy%EXQ3vW|q3;xJ`Sv#aIQH$BfyyZATbl-RZC*5e7b^Jfjeqfv6G8*ff%@4sxOf-aL#j2O7k=0)X zF2xo6rFsYfyl)>=JOl6npG;Ff6_Q<@xKZk?F_g_OUvlchJ`R&23`|YgW^7LOTKGml zdJaYrzM~y+vwV89dZM<-c`qZTy1DOwBU@BlBS(b(39UO%qKlY zX)%%FmzFKw^Y*E5ARLFl$2nIAdKOr4vBem=)c5vxs5%s?mofQc7U9Q9HlWgvIYQrw zltdeHy}2oZ;Og2hYiAH0EPX=IgrDORHL2!>Iov$=^W0N0ubnMrd^JA%GvqL0;$|q3 zAc}-GEgi3Q_%E60>!I#JiR@;bH%GMmADtBS;?mPM9_{7@8six*yOE^-2gK1&s{Lw+ zPVFwvMYYBvA*&i>(=wNcO9i&MoDLdE1RSnelv5>m{Lgn=5XA@W7i7| zzzK)2t#HzVuOXKESAIZ9S_e^UM~gd_hfN44^e{e`tgg9AOvPw?vU*i{;9Ng6y_W5z(^N|xojs)oO_K@kSz36AAdtL{ zXYfY{4AM;AvyLjJG$<-YpF6ja01aYXjYA}786+#>r0EvO9KAf$kTBAhS=tK~t)fys zxUYH-?)&$`-Pe6elK1+U>^b+U1CDp6wwz-=7GQBowDMjOy*Oy?!`?jh!_@*M*ON`c zMMv(PF2#c--;`7gue&>_>8F$nmin3n^akE&!U(h2FOwEDKG=&~Abs+BcaA7&Lyy+J z%%by5#nU*gN!*Wj)d?TX+i?9Yb8YiD^9p)@%!Liq3Hv0{{lUvBc^0f{J=2ZN-ir05 zBQBvm^&pQ*c~XnmTyShu6W#;4-$qa<3Eq>8x$_M93J;lD=MivBU+d zE5vJJTD` zS3wSJmJYb*p?kNVEY_+zhUACQPZJ7UFHxfkZW+JD?M+cr;@8`gxO6gN7h08{jl<-n zev~uQ5PLvV=^i-y9Y=~X^6(6X_l0>qXQj^~H_F*?67NtQA>k1YdwE-IruTwXb(B>E zQqg%eC@d^$b>M`9Kln^(iz7XAYwU71MRE@Mpt0{~vhG)M%DmJoU1#elOo_T!J3hp< zq)5Cqf8r7?@HOVZMNXLG-kO2*cVv;gAx1J>Q=9pH%?O#2lZ4@2M!wn1iJvjI?KdS=8F;K-3YnIZQ<2^rxX(hBQN2 zK0&YQOm$2m{j;-Z!81M}UoK<69(BV8jg0)XsQhKJnk+>$cdm~}<{J#D7ve|0#F|E? z^C1W{+r`(}m-A$w{{kOXtauwU;pj8YJ=jiDUXI@%^!qG8SrL0M#yvcAW`^;jT6tm) z+#h$CUKi}*COh7Yx)Q5}=hB5dc%if!To9-YvI#85lPjMq|4y2%J-`!Y8O}{s>Nu(q ztR)s6P(s^^OW^Yge3J8|@nlCfqLh(h%W)%-BREez-9sB(mK3$FE8h?mWKtTT3WML` zGx-F1&ZzttTtBW&>|be{l7~qGJ`| z9NH}*<4s)WzGT`%@E(>XwSl35Ni8TRc2=l<*&btL^9{podFlc&*B~eM4_3hm@X_QnJiR0jh$SuIs3v*;$qH7TC6^R{|&n=3+ySb1>NwN3J zIAm`q8e7mNzEY+*GCeT<5gi;w=v~GjHb{vZrWv}qSO}ZPyyXDTaZdV0w7@-lz#E;v zj!I=TRBkuXmur7~TQlLp2@6XX4T?(`evs*kn-gK<=>4mc=yjNDV;`o$n3Ovk{-d9f z4=(9ega45)Rqut_lYy#T~lkRArtC3huHV5JNi=vny6z-JG z^XV2DOyOD4l~J~@EL;_+tjW?wKk*|0?w6W0lzq9(ICRa6(v^cu2<~L*`IJF2(?UNL zv(OGEdmpgjE%W-Uft$COL6?wi?9W7Pf@FoqQm<+{o97Y6oT8mX=J8l9HQIbNL86>V>~WkZqviXpOFr?jgaka%BEvwhL4^nG#szTmo3Vp5E(8=Ye7FdnSaYNg=3%{l5 zD*KO_-FdKVDU4^z{%~~II^f~7;n*5f4X!mGy~R!#n*vktIj_-?)Yydet)0U?aI0H` z#E`zWgt+fFaUY(4gyALBSDV>oDU5plRw#t+du;Xx)IM7Ci2T>sKEZ39{q=c$6!fyW z&8wsxj2*hmP0FqN8nV)m6NfLQg>09m)#5>$?1IVo3zC@~@4*iT939l93zcJ9$<;4+ zy!;FU*(;tDoqOW%Jr`XOpRKq+2kv2+*h$!kh3?zlNbt-0T5z+_Y!nW4`6 zh*|lx11m<^BAhVq<8bNid9F;ZVRhHgN_wry;tTJcyA zW_G#w#kS74qV-&E$@sX&2>IpmG!2M33LW~3p@>)}q5VjO(Z2XR)7jQS4!+2Y_~wLN zIg7jpod%dncOT&k1d#B)?Fl&hN!C}&OTMGnYjy_weGPW7aMMuRNk~}UtPxQhped?g zMbkBVI3AMIxOU-3YSdu!?TR*r>3J6+njl+dg&|7u2)${xU>ysV%2&y&A08&JCgaa{ z;GlNh3dhKN+7N$gY-334&7Sp!e}(I4dT$LmGPaO#@1kaVsi`AGSE{@nI~Nm&;AmYo z549IMUe9H>u_8L#rTksjs|FtvYSyE|PvLYQb-~3h`t6OvS%2{qq1Qp4B4*yKP;oSR za5?==-hERqqUY7KqHS{k9FdJwlya~vs0mO};Pr?;__E2X;ot}}xJFg={6KP+9_2hi zc>UBgh9V0r+u2{ElDciD*GAm%YzqMax?^7nMLYKH5}hn1I_KV4bc9^rzZ>w z)`^9_qzO5?XD-)u^P|S%XAOVTWYNLQ5WM+KigskiT&bNSP|a2zcwe3ZPgKvc4!?CA zIF+R-gq@BUWA}@Djrrk}(L544N-tJUCR40YdY$Mc9m?XY-%7Z030c@cW zIG&6l=Ujs%%P-+Q!u{GXCo~eCQDw#b=xs!7)TuXK-ZR}59N1m^V8&Lw6P~OxP{1Y9 z|JslN8_t^X`eKW^!Oc!X`U;V-Se1*wyDpl15?B;|)moM-sDUAdo4mD&&@r{}< zJam)~liCzZ8t`nJ$VE@jl+;JZVMTip8Ggsdn zUCmP%6RUhlVCTHf6TqQhM|HYN+R0wF_o}Y3QOwC8BD@h=m{4V<1`vYCjNNHr9)s|&$(D6t7K>yXY)4s&lktQFPNr8 z!ceW!Y4*t(M*)fhsLHWkHVrCY;7sJ_JT{L^yiEzkq=`7KK&w`Q*J{KSV(YpwnJkx{ zLC+Idq@u^?{d|O3yg=plLy5cnW1KHABC`V2QIz^y9{ky0Wb*&sU{uRPjpv+|Gk$-2 zGCXgtS|&s}jm`x33vUmt-DoLyrmvwd_(InbDGLNrorm&;>^9zyCy|qU>b!%a&(ir- zPs6)c?4#DZr{hulGip9&2ThPD&);;6Dtl_L=!ibhNKgcypDx4kZ7x7@+D{hGy?V;8 zej%}Q?u(T67pj0FYq1-G0^~#$u>zH};C|4n{uwK98o`P^R^FS-^%-7=8<&yl!_P{5 zSnhj8IQO{N_9Saf#JfmRL(uPIHvdOW53q8nHfDCuNbBrWDkHgf$Ytar{W;}+eF+&{ zATeFmF_CfCz1uysIK5kwFlx1}bw<>tI$cVY``ydsU_*`G02a8yMFx7E-2>9}OMa%$ zIh5s3`57|Z3c3uA0Tf=|Pw6rd6iXv2%d-E4%&5rvKam;Hf62Zrzn_UE7#aS~VVWQ( zoQW*8HP}vAY{cs~oXve|^85Am#8)O4 zj>R0l_``WmTdSrcpU_tiEYYbxkm?+ulWRSxPQT=2))QsDeC8GmXOquU!6&|$hF&t^ z9G;0Zr+1(itIFuAq_naJ_*(X!FxGi{^-*j)n|4Hftdy|8hKfUW;w_zssK&V<2|S1U ze3Tg(CYV;7;q|8`6ul1h1(kYQ+90bFP#%1^p4v4xqL z>gAzuhl+DRzfi&0qny3+XF53F*!kZqNqEqu61miM#wXy$7fO6D8MO3_fcBh8 znTO$q$b;)89?WN`cod-xROjJD0_D|=W=szoy_za7;5u4oj(#m=eRA*S=}2*uU#Oz# zB=EiFD%g`==EgD#d-}p^1)68WbXU=bYzd!%r_Mc6=?|IyY_3r956FRG7M~x5Px8h~ zx6t%EQ=Xh_E4>BC#L?~~So}?QZBP1fWSL+?k^*BnE@(vUl(^kMu!H>9ZLp(EW zk#y|S^td1@-B;;w?l|8#xH2yKD>&kYsz~hO68DMZ6!-Q$ti0azp|^Z;PkHkwu4|Hq zB8+hcdAy%7mg9HNz1AV_S*y&;2!Pdr;#t+JRLD$?*W&?<^8*~Gd)bx8kfYhIhVj|D zRYAk}xeUCqqM-b9PdH5V7%Ub?JOeOU9^qu)FqA(zQHZfrh|opR3;4G-Q4ef_QLVGV z5E%wu)C8xI(q>8Ns-|SS+6?;Q{EvyvjWsyYw5FA12{xR2$iD}1e$$I=y@(X+Hu^kv zlFX6gLreqrMz?QFpKJDdaxI)jW{9 z$qyJIre$Z6S6T$O>%kM5miC;SF`Wf0~3<&L)|JHi}{?~XvUpb&Nf;7Q@;){|YAZ|=v&>7@V(l4K_ z(|+I2{~KZ~p=lCeo4U95dkQJr_5Cfd1TAk4)IGL>wnYPz5sb z4EE4M&9kdI`dWoOK2DG4SO%^!z^>C3Jh5n{{ z-qxJ<{p9;Vc+~Ue(T3*B7~^5Z42mhRw_$G}_|WF)bp&GPj6iHuqF|Xe?|nJlkohbB z^$+cg2#>2(>KZa*gwPj{dk%EdH*J`?OUu9+6K1Hf*^Fb5f{=YdZT7eW>xW|3+9INI zAZZ>PSb<)*3N^4ZTTOx6<$xUT7lw%8m>~k$gjxNxA7Z0x?SDmxh-k~k%^{U8p#TGi zDlKD{4$Lnch1BC4dgwu)L(TUe6>M~fLQXb1Iez@2AsA~>z3@v`azZHAVD)K`;on%wG8DLibQ} zjA^{;oRON|vaW3qja%N8Z4tCT^mee{jk5Jd^y}-al^ba7ca6s%V8* zwOvxjDf0Wnk4cx}b#nhGZp>%9Ob}O5p!%19k;dj%XJ0BELi-L}Hw2#JAekn8&{&uf z>W3gH`IlWST=WVpWwLRJehz133ZA~jmhCew>*L@(yP3+FDedwcjiOVlZ&5PWjx+gu zs#v^oyT-?;x`CMZep8%Nj^#}R!^N_q%pKHu8ANUjdHdChH!ED7zUZuUXxSBkYf71? z7y^7m5XNSA{AcO|9Id_*2@*bB+z$~MB1$0f5+ugks!+f``|?H39!f%(fahYov{C*X z2}Uo`P+UqdZc#~7{`Nz!9=#fhNU_diZt2+J(-;tS-Tt`^*J3sL!}$f~_AK;8Io;Yt zg)`d2%dguTnF@nt_><#hK&HIq)W2kp4j)td!LqGcxOL8;#5Ld|PMKFJg-=LHK6OQdY72Q1){UA++bRTtl*m$nh^kj{{#f%^dPZW*`W(o^s-sqmIMSj3(9z>rP zz*+IVMlR@-?9$GqhU4*V>gnyre3$QwAPjsR10Pv1 zHRPX_5kiXh*dNW3M~TE_9b^_h{8e#~-X?(V`sLa(%kH(u^BoA18N5O!mtzkVZ3QFb zkmB)smWd~TR%)AeKSG4cZeEq?pn#&%SLC_-LCYjs>R=lEs-`5uGtKZ1NnK-=s_Q%2 zO^xquv{iXc0El!y4|u8nw$W+1C$sCW{8t#sI<={2d%qtSyoc1|h>F>zzD?URbB$@V z3K`)@|Iv-hkp5^f<{|g7q_&{FuWdTiA+!El>X94X!t>x!ZGIwJBB5y}+}E;qgZ;2% z3OH`&%5e2&^W5g$O4GM~sK2P%uCczO9udT711x3f2T~S4X~0a2Tf5D-HA?Bex+GU{)1zm~aA!={7wDb(QI1U<(%fVN0 zmADC>R<91f(ASgx+6-bJwfJuLITAWuI0l-(J7?vN(0lsrylSBxL%E_u=jb`^BvUDE zz0`yRvmqBwT`#b&f#4*8FU?BaiUirX+Ab;}_<@wL8;?@e0cijB@a5ikxROG+i@0^B3PVDlAAAKmWy-C?}Cp?ybp4d!n)?A zB^+5M-@ASOV!&WY^jP7;hsYn14GeKU%+%Z%-R${2$V=2JB!t9P;QUx;Y>JsFh;*^7#l=&71she zuWr8`TnrW%Lvs`R$k!ZJP8eKG85 zs}pWaT7>T@5_U<|CggCr2LbHJr&@_6oo4O+R|@jO?R}RuWQw_Vf0=zij+<7|>V>KP znEcxl6x`K9Zkv?tc(d|Ktg|5~(UpuNnnS?|KxDtVkc6-ljW%|c2T3d(o;Y#_zew-? zynW}m}XETu$wp&}G};1p1Cij4{@|on~cLjVyla+q;0Va!o6P ztu~o6A}r^@_2~ViHfyyH4Sgk)0Wapxh8I9;<-%}0UG0NVzM58k@TzSkMkd^w@%A#sw@$>7m7eL9~_HXy2pO61v;8 z3(*?HyetIao`g1Ke0v1EYUZw8mg?QfDb^;a6@M~t%IuipDC(ZgyZa*XYs|0lYdQA7 z0od3Q#|vAoN3)lHD3E?msNfdaXgfSTjr)0{;n@{YYOVpZ&|G-=IR>hmGcpSz66P2q zX$pyk=0!sx>BMwVu~;G&@KxUkyVBxx%o)*mtpt_3M8&ote`tXg2eT!DmcZI3^l;Dk zYMFOl)s$36s{BDJJ!4-n%SJ#z^3_1UXuWI1YfVl-a|bynWl5g;uo~mdL;=qt8>LMZ zpZWVPv8viJ1ZZMOE2e zFk8mOf(zPjCJ(jr-@p=;Br*zek-c6TNW zIc5bGPU52OJMZn7(qx~6R#>Nt#^(=zTuQ4Sj^^-tkryOz+HQ!%$O@L=)AqGvq0jR= z;VG$dzw?iP<|xO3a3yHMusT6a*UfNo$ORL2$2;SoEFY%lj~X<@t_gb`n10<*eMi<4 zE5|pq{MMF^PMfO^Y|E7+eALX=jG*(lZ29OxAQEEPl?QW4vIiFBb+3ogqX5 z%nz7}lnpPs8wv$8+E;#&OPwH(>Js}*o}?;&PAaY{r^>;9iKU0bIU>Tn)X+puUvZ^WE4Y(8U0MPG3uNpkaPYZjV|kMxl7xu5a+31LeLD!u`{b5W?HEc zSArG732OvSnh|^)ye40}HDKIn>hFgo&c()3eA=3{_ElL3mZDxMy^VZsN;7+@1KJAQ zWReJ=GupLDXCYsX+)r>2$yl^-8_Ryu(eB^?*uXrKFa7!$P}_==2)~p@KZxH@s6sUw z58Dhj7up(-jVmT?iE~sYIas$RZ&V)B^3b~BK;kLucSsPJ$}Y?57yzLWe<%2@&wF!y3B(KrJLY|f%j2wnedb5K9CsL0Mi_f)@z$~6X zD#%haUo%6MRu&j|?&=VbO%03DV=kl_8ZEldH(J_4I_e3RybPPiIDz+!cL<>fteo&U zbkvBWSJEbUBa34JYMB=Y`6J3Jl4&hKheh~78lS-CaEV0i3NlnI zdJ-z;R#5Og?lpLrMqXM9x}CKAf`AMbU6rDP#;z%MB@CPBDTLhLkoCam>18p!6{(3{`_+*j*^@r`#qZgwD(LN++e?-w zK=Y4A|Bd&g1r-6oAE0p(x`6$q=gbLw5*miOx~df82=4DqBzbJKBeHs~lF$5Q+}*`& zzE6(!1>){?9(`_*IQrW3uep24R0*+``#zgpuLu`p3EhwwkrAW%$7+!)HT{``oAFH5 z6PFM{HyRWeMXo(Vy;FMogSzNun$(3AcE^}>PFR9}%({CTO^`6a++a4`Nh^ylDRhrS zcF;@i^{~+$0GUBrGx=@JmIEb3Uo_q{6)p#SyX?6Sc2ykWNeLj_w!w*hV za>yST>^Fj-`Zn-y-Q=L3fW;$%;jjPw8hc@!g7@ta1V&cjqwPjJrKKJznVyxh4)kOosER-#Rdn27fO#dgofI!RwS-+=|yf`Ce~L zp|E|yp_V+2hyYZvVIq#J*vOw_U>UbCMtB=Udgl0H!5C`%G}@3#C2HrGPGO&nCGjIq zLA+F-s;92;HzxDYmTTy^^Pg4QYH7l_X^wqL)v=|VuYfw~-g56d8KGl&6XiF#(g#AO zP1!>f+6E^V$xnIuc(}$Q9DGAel)R>%;2l>e}ady zqS4eJ%@B`Xr#uB#7!{4YZOKUuTOTDXQK=x5mHy_s=6|JA z3Yq74u24J?B(GI>5_c(A_}-JYGFa10|5d6sbB1b#Ppnmuezp4#GNVVtbeHz+sNOsZxe=DkTdMkt@pHg_%Ookr}C;y z10o@^DRnzgVgpR_|FVjWqzOJt{Y2;uGS5PAwDlt9WT-?}$nT8^O zUH>F-kkVOg_|dZ88=*{9lHg{2 z9yqFC@mhrWRzCC}E2=|ASJiHRh&~zk*?ax(qiDpRyUrgOd+mzo zRr2F1wv9<^eB(%sD2^ofC48;)N)u!v!w!HR@=o;?j43{Ywu)uomoy|QB9_aNFdP+> zv5B5qLwAQM=fF+*sgHRYYtAjU$T!jII|%pP6k$*6bSs%yOvI^k-frR7@0_Gz4edSz z!`fcnmygWKQ+Om(kt4Fi)f)(|ri=T(ZlbHMe&#x^zO%fq$TJF~LwVWJ*fW#tR<*%X z&V7*t)JVjpxHYm9i?poZQ%29Bt)R75Rc9#GVcWCj2!C)HCg9XHR$8P_a{xH?8JN4T z?%n~Y0YOZ1h~H?71|izv>n#Y$2h)5gKMo42hiGFOgjb6E-coVF8!JcMFmcWBl_k%W zEK0*Xdpe|`a6(ni#7mG%XbMWiFN_SL2NosyR>v!9(e=#N)U>|kS7ik`bEe_9*}0)Q zo;0%6nbs&AWSi3!9~4JXmFs0o>1SDKz7)@W<^d|%Vr8F0Op_tJN;2^yJ0Av$T7>h3 zSFz2j88i9Nr8PnPu(1M$GUAz*)@Ay&O1c&Lyy;czC>3_jikrx!qgU<{c=W}ZJ>Mm$ zN=8rn=dz3CMi=M!%PUha7Gl@B2jyvCG7`}-SdCkbd1bn6Lb|>u?3$T z&94v7Ga3AWEM>#C@xkCfzn-Vxw{XgOU zy!WOetP!77$yTq@!7{+k>xEZX42#BmUVI$e(MPqs60h=I`)_t!hF8(8r|#-r^%(9I zGB;b0;8c%i82XMU5x;dwV<$0`E!Y#9_a+merm)Lh#xHOCnr52Ell05<^2HkB7jBC> zaDUq3vHoZgn{FYKIwp=l(NYU^CaNcJD^Nlo;;kL9kSG__2F?{{5+00i4pPaCiLP|oO0L;N zgF0Df*OIzLZjw@t-c=GELcC*C??|NiPGR|3>^biIL+{YMw_{3ZHQEdG7Nlq6PHseV&gxvVJan`5Joi1vu6v;7C%2HODh>+Owp07tdr-^+c>& zPmeY$6}V-b7hGcI!ZZe&)$cfyynB7`owE+wkP3gU1227#6Q;{*d~za5r*ULOX!Stn zkp{UZSlAc3Tm@kR?ss0{x)0Q1{i>uWT%<8!c9m%(Mnpr`=6Xw<_CZab&DNrl9d;@# z`k>IrWH4y~&xF)v>a*~5Fb-HKE{DZqyt>lmL-W^iHi}sZdWKjz;%?n|j*w!ABjcs{ z&N87#Wj|pAt$68~OQt7H&VlqHbSn+N%`zF?{!{+fZj0z7A%TC}K+*-5Lz2J4=LgEf z?ju{)Ccl@ltMX7A6K)^&23>WK=Hw6~l8M>zVwYk|#AIH0ms1m)5#h+W0u*dm5kVW# zkQ<5ZI>N0i6kPo1LS9?9?^4!dhN-2ZilSfx366zD*v*S9f)DXDR^!khXIhiG>ws7? zHPx6g1h9EaH3clsSjTFGn|z>}@3`{%*oRaBjbUcO7=O_ro&MZH1?t#rcAU<9x_Yev z$K$OFgOk|~RTp|id1lQS;{c8SkH``@x$NCrbGUkRpmua`f`X6!C$2l?GMZ z`;teBUow5<+WwRNwJ#UHG5@xcwc{mJ*X$Ybd$;w-+Oz+)9Q{te*#EFQ>VGXq#NdSg z-)1=~TA(jQ+Ka|E;Gf;c@0}%(sxfDr7PQkG zg;HPN>o5++8rtQy4)g zp0$^}z;q3D#}FmzIu@y_S{t4=Lr(Cs6;S=n9Pw>gN#@I!t_-Dn@Vzj?0*syHnQ22f zqnXv8JLf=amh)K98biK7(2n{Khq3KD0XaomoYMEGy_48n;5ym!Q2EN}$}$F#>~YW@ zYlc>6mz79Z?R9w}L6$Pla~_~N?W+`tICA=Czy@Qu;OPxuC3#TBmhTH-r5&+N%n8UDWaNRRWyAR1g|5U@(L6HG!xlSK&Ch+ECLkG0-%6~3Mkxnhn%VQJiHQ-E|J zo#_R^>L1inB;)f2E+Y16w|nutN$7<3xGetp4KaE#J_-qwb|jbWCP<8gi$8YQ-E2V* z=Ypqw0Fl9$&JcvNYG~~7oCp9oLls*NP&aR{RG`^R>JSNj3s^>T{3smgSS86cNa-&) zl}{4)0BU9%AL{@Y%iXCsqc4uNNS_5^bFJKh<&I3-XR1Xq@#IB8(&aN`VpzQ?2sa%l zya>;%F7!^OTjUYvz2aph_@VyGSy{|WcJhw)#}#eChD&U>^T>Frmd?^tLvx+3juRGNxtc8genNGvH{zGd?Q>J-4tP5PHdgGlmYzC%>=RcqVKFVlPu2nib!c5t25|vGm}X zb?rc!BvwO%yg&Xp+LRK#rIJv+ps)U~5Qp?2O(NQ;N{QulVWbfm_HuHtUT5u4nEt^Ao^ z8Y(ffVaPl6_;h)s`0L|n`sW9g0|%#M{1t32Jh~?H(gepL>bsmI`|q!z z3!!{gsNG$1&X}A*TU~H;ToxTw8xymf2f>!}r|-RVBYhxTb!j|(MRx|-pfJMyp7>`(Zm{;kBG&y#atxY=V9jXBZNkqO70cgaC z<7h}Y4|3_f7yh7U3X42y`)vmuo420^6>w;;kR2GkATYydynYe?-S-|hjmAbDQF~vF%L%A9F{bah z)Yt=VS5m02i@mSZjmwdu?(3XxBexXtG`wsodlnq>N z{6F`)dAObQy0M{Vv49x53;KPIB~AE9^y9Q^T;W|}=cIqOU#K^hPSL#Uk6-#zXSVTX zNriFZU~E75HEvNI?Jm!3d-SEt!T*-BdiEI%NmuahJww&Ca<7@q^Jc-9g_cGBx-@3@ zy)K;2JQb`=eh0p1ASu(n?XO=K!ZUHrXmvW6$VfP;N85h(qZ2$P%vk#Pt?Ssv5$`?y zJWmDlQ?-Gj396lv!}T%8jmpSb&L4)Q2S$1`)$f)pI*pmOPrgAQekm=|HJ%H*^gV}^ zJ0kjQWR34)=QGp&hej&vD&W#s?|#y{?Igl!ODLkyd=Q&qnS0E}=bA}xSiziAj0}V? zeF{EGi~;zPSobl$WCeu;m7Pca@K);IX8BQfaX)SL*S-sXZPCB%h<@TriWkgzD~1U< zL7y9$ebO$)qk}E20_(1+*f%7};rNTD!qU580}WNkj%q2jvr)`x&{NWaIf{)=$^>Cs z%Z+6w#w?j=#BY&>Ucyb;MB&y7b9iYs&TNfJG=H|@+l4u{t)v?wx8wkF;UYER&5!_< zj5p9I72e`2?#1JRVj!G3Sr_M2%qf-x9QwK5*a?d>=dHNL<|WnRH8F2e#LaNF$-g+Pk*f>VRce<7JRoFnp7 z;{Nkq>-#rB0!03%=D`P&2xR&L4y8W?YH`q3Z~bE&1H$eD+epXv$0nW&wc+L(lwZ7C zsPwZw}9^|6Ec3v%z!A3%cq8k zH#sft zU#xQnWE1xva_fL>3b#70wbN`!Y$Z&C*n50|STy*0L<-POI(w~EC7G9?S~xmfy67Y> zS&n`E^D-NQTKP)&4WRQJKhb$2l0q$zKOB#G{Mf<^k*Yz4^~slS-YM&qC^!Ohlq}`f zRn485GQR?X9RqfC|MutA5hN`J2VdV`RO#JXLflV!ylE+**3-edKX>t+hk^2t8yQYPWNZyX}8t6{WTkrE8=19p8qx+z}V4B+6LzloW1vz&~8}i_a0r-eRP;%X%1(Y?Hux@ZH_{vrQtM zR`KJX#ho(}ju6k3%I+%IGK^{$So{TQ6pocP^9DzN8?Ni@do4-`A}Kxg4E-@FH5tp> z*Uzd{G{Sf#9IVxN`{D^Hl-$*BzMPwX%p_{dD$lDosg60YcatWvA@Cr zDDtkFrH%sqMoV7ZJk(lRg@wS-efV`ugkKSh0GC<$2E)O<{8xV7J9IXB#sih#u_nZ- zg>mMp6D8%~um7_Tg^9 zEN0syhix+kKBcQx#XY+fru#p$@EsisJ6Zco{ ze!xR4g)GgIAw7;q1ZS;6j9T@LNj3@Z(sMz7!sDW0iMFlq3jxtAnn3`T7?O7{;X*Y&YOrAwKu;q&Xr8#(f$zyNRMUsn(j@%TT7#XYSD8)EY6B$OrzZ z8S#8#C90LA@+7K`8!A}?2%LFT&WW+fowG|)_y&9a_xCk+wgg|^7TzK;0=j%WWMF%s zOJD5au;tz`5CC;W3%u(yJFC-@o6{fhhAsj;KD)Q4bRCI2AMKKu^#rk zh&?NDPIE<|4Fd0L+J=bA-o%#DBOR7a*`IBM+rkPSIb|gse$q7*8e)v9oEF@Z`$A~! z$iUI(6O_A(Eq#k$%>D`y^9kYS!|b4g2+T3ZmUQ6DB$fwHth1#3T@B0cA^c5?snatVp=KGO-&HEAy~X= z_PUJ5rsm$BVtx{+f8YeTd27U->vLQ)qw({~Cz}gfuYa|vES#V3DPfL`^r*-gkpHaHUsh-up|pf5u`c(ij$IxdL(%L4YaBbPI%TaR#8O-jt@ zer<7ZHIuO~;#s&uqY|(7DghP-nLr&<2e9aYVwbec9Jub0-3`FwHD?kZC5|$%qp|LK z+JFyQOjJ_~5j)#{UFbV{J($6D*;+3&UC{_CZnJ!y6I;U>BjN>wUWr|D=LH?<7q!wk zuw9r_Fr5WC!I~V)4jAigy_oEAN`*SfU$-tgZtcvibfX%*_{=$HjWzxf2K6xl722tm z#bOSYwNu$vgWWRr_h-^gFrA+2$p*bs%_J|*Ohs?ppUJ8HBb|{yzFc?-7d5#f%%$7@ z0_fpcX!reFIQrP3L~`+8aZaJ)@&R!l{azVEzq=6V#GTgm$YMxNN~$^>Y!y5s3`5hW zbW`Eg-dx5GpqvFM1x+f4PYT9qi7cSQp`cA zlmHxaPTmps)S_ClknbMl87j?cIsu%@^#{T>k&pJqBqY{Q)sAe@D~38$p>Gal-nWH~ zuS#4Yhtfk@{klx4Dej-|~&)1$M{Uz%UuX zl7Kontro>BG-xPx!&QW>TTeIh8e%LRS#SLc2F=Oy=Wo;{ZHjFrlvQU-={_6|y(SKw2+w$K=ug zN%%jOLpV=gyvsxzko~3h=*t!~zW;(eeLaXfsw3Ok&N8HRJ9vF`xOtBBdJtdGWj6OO zsia4rgN{U!>L=y5*625hLhD2>&HhPcKUJx7480R=Mps2bY>+-~YnYfk+$WShdtP~_ zrf$BbUShdPr`1jE?#vaTecq9CFn(HeAJ0gV#K6?h?ZC=}?mKA{kGQ`=t+mudGnx+b zi;pwB7mbC}amVpK5;F8AR9HLsP=5`O)7ZQiSHXCC?#mb+O_i<~;dWHS6B|8>Il_hI zP~L;O>zry0qDcS2R5_5duh`9PKY&x{p0RWIwYLqX^1KVyi*2XURr?80usf=?zC)tb7Py-sb#I6YeLSet!Qp06s5 zpV{n=1P!2TO%E9niy+c=@*I{O$~QmCl8sPnx{8_^HQqLyLHDY~zvrT;cHL3zrR`Rd zf1FyHp@%O~mWC|hsM$QjuGgB0L*skk6=il{nA|1b1`i=KSTsFC+34GPfHW;PKXRRi zW$hoBO&n(-t*1Ya^I`N>?JIgLYY%==SuAbNH~1zyK{?0$b^DJ|r0E%EF^EF$X|6cS z`Y_#+B)5cKiPZR&djjmzFnZw_6Mb@%o0M>Z-%YVsqu3u^O?WBUJ!b<0y{k<$!VJ+LZC#c7zji0(W?&PM+oKc3lYJe`AC`@aLit)I+jcmM1%ao#k|A@*lsbHuZkW{?DziGM(_G`&jNA2fDzDm;;&efUxr?+|@*B7_cGtGLCC6U{jq(zc#kLdm@b@f)-5xtt^h zRavWVd^x{ws7pRq6n$Feq!<%X*;Jgo5_Kda7Q0kGW;bz+7e$&pbpO^k|F7^OCQ5~Q zRvHvFI;C)vL?yU6x-X*OG=EP<_tml|bSwD{qWa*cISCkS^2W0NXjXntreb2@ z&nEQ{lIwFYj+xEQsw$i2Oppci`F4Oi~vpH)Q;n0C$;QIrgULQ^QUiX&?h_uS2S**MtqqoxDna3+yoR=E8eVKy$Gh2^NapBp4E?3?mDQ&Vz0*U!w<|& zy2EQ_syjf%vcgqY&x!u!5Ks?RY9tRgen4W6k!dkJ_M%u^nkp~%W#S8fb#>Muk$P$Q zGkLhnE!Z#Is;AH!$dCb9)kdS_jQrW{#y&%+oeB~r@pU;LceIgof{r%yw6!ZM=bR%= z?2NR_O8&+dVV(c?vLvs-W4(O{QGP_w8Z_3-v3F$Nk<#4rmi?X z9W{er&<$qE65^893MEVL)>Xq9;F}HF#^o>m@MzM4(2YCo^8E84>o>y*8=4p(y?`?W zTG~K{)-^J$Zh1uRsuwNO6AeQ2GK#adeM=)+$Z;PcjLsE%7`yhKRt=Csc7&7e=fn!I zJ~^J}NgQ%p5t27}Un5uquG^IJaYv`u0+vh%`8^>nI6&R znI!)VWeK~hwCyvw{Lc|89axBiltbcPdY9+vCX*PZG`AMvyBgi@hoHRuQ2>GTi|pu&Aw`!=77zA8h>gN{fXb?Wwa$fvs^jYsug^sQ<3yjFhqCp{8E zSWzb}pUiI2;RXzRjzSJ!KS_L$t zGZ}2^@I)$QUsj}B8m#(1u6ASL3PAntR5cz<3Zmy^bdWL31SpY14Qc6Qlw4(OX>@OM z<8o61ah8!@Rcs9-Vm5XgMa?49Tbt>Mal((Mew^eL)!Ond`s)?Aa2K0YcFfr6Y4tb0 zFCvJ&5}@%_9%ZO!<0j83P_yJ}^>G%7>klY4e~#e+rz#*(nvEKj&3yw{qxB`x-P(P|HVV@l>$OwejC8RHVyncee2K6;`eP(|1b{@ zN?u}z8q1+p&1_kn@b8;o7*cw8`%f;Z(TI0nSDs)(fVIceWTzX9iO!abJzB4ILCxm! zXQz>@-Nkuh0kQW2=(>5nwLgKr)|r*t;rSZ4xFPpEo zfcge9E>hOhIz|dDUIfcV1y@cL5DYT^^wBLTI9tkOJP-{c$W8Ija1bnCQSk$2o4=D8 zy}Xhc0oaW#yxb3$xfF(!jwXC;+Xfxtqb0%ZVJBNV>`7KB^M*-n&~Gxi=--g!?l>8b zsk43El;9OcT=Tk)e{c+alW&^T?_IvQvLDv-#f{grK*~DAhvw~J-0?KjHf;6SfM z5oLI_I9EoiWN}HSg?j#V1!Z)qsZ6P*8TxLSjQ6Lkb&lsMr{4Z1K{H0tHVM73=bSX9x0p3-p*r;LWH?a}0SOw%3 zxtNeu4O4P)vL{izldI>Cc20koCF;u;UgA3BPr)g(vjmhzaSoSCBf(27k~+X5#s57P zNhC|<3X4Q_vR#Ley_dcX|14r)1a_0D<#{GPLb4GVqiJ%a~U6p_&q|! zH&S9mA!hPXfDcV|W195L>^@@imgMF%5wT*^!7JcWf@+IgzA5;34wcz#Y(X2y{HVLz zZRB<$hE$6&JlfgmJ@DeMrT#)!iB(;*savNXewmWHH*7Yc_q`O1*L}dg-7?o8hT8lu zBvSnIv?woXtS4?e+;#h3gf;3z-9)&jLr9UAc>FSwgZMUSOSrDf!@y1h1CBwp+RMac4bdgsO$$J@rPE(nYR9eiH9@)Xr9B+^+u*-C; z1mik7w=b9K7E47`ZIiaxFUJ&ufSqU#yEW@rQ&Zrk|0B!W^;L?>(tB$MQvwxY#aD>( z=>k(Va(LwU>AT=qoEZqx5TAW&zuo6N{noP&x=?OppgXM49G@6Wesogo=*0c^9HbE@ zGyGXB5u!Q~<1zH<9G!mnIlSLhM0n+sHexfGqaJtPSYJCj_FbMcn@DeejwQpn{wuGp zbd6-`F2o6Qps#Z#-i*Iae$2|zeGg)|W5Hq==%7Aei8()@ah=5K~D{$93Q1yP60V)C3<8DbzjWidiVIVbf)D#LNt{Dh$dpMQV>-KvR1 zo-qZ37U-Whi!Da#?EoASOSITGb$X+Uk#H7XN zAsHZC8>nDxnKdq7ONm|x7J>gHU!0HNkSecEBy5%C7lCWUP&!N=E3cRvd2sQXx$!H# z=~R7L$NmIMLhV(^v$s$G!qzD(L<{a_HkO>fpHlFKS`^cI@ zu$&$NueluKwSVPk>#`S@->b|glcxs;b(#P0fh_Vt+?kbr8bJ!K2vWmf4OM1=*(th- zyrA4l4n@QvdjsOmO*V16t{dEyP-CvohX*+KkYL2S^J><2^$0vV||BoIC# z?##)7Qp2A@tBK7un^!kX-w3Sez%Oo%%*IJKK zwOefMziP@lh`|Fk^H0|#&U&H47qgaY(OYYmiXmTvzbJ-I5?o~KR2WkqaZEYH+E~87LH*;pQmFxz)qH~+s{_$f{=v|JGq&RY0W&Vg(pTU6) zeg(!}4UX}x=q&^3AUL3CMJ_OB+%Dn zC28qHw{5WmGtM`oPg&xB1ysn`rMVcsnX1TCI|HDB8o3M0kBC<>ZOJ@+(7Q=P2XEz< zOz0j39?0lXpgFF5ipzdt@emt7%YOE-V%a^!yoXb^{M9!ZsS&cw8NHQ}kgx&G8?xfv z&%9mjmp@;LgMtdPSY(7F33CCmfckq+Gb(d;2x4AuO-_DN0#b_WEL3BJd_>Npor0EH z?3lS%V=w}Nobb3QwN)T8I!f)>XHRTescHXXj6)w#=6ruQy|2T-hN!r5)}8je=35Lm z#rQs#YQ5YaRd3MRQ)v0D`29DElNO04RVWFT`ZxJc0ho6GZ7KTEgAoJv6!VWLo96+p zXcD}C6fu1Kk{PAH@g(O*wghTq8i5`5NT2` zPc1c(%tP6fDO@KAFb2DWg_N_XWOV=>6l`nrdL_rw8yc_H_$+%FYWr_Rp!=%<4<$;jeyZ+?AOg_P7V3DuXP{E5VQX06>~n zh3&@IpuRbN>HD$kA+5E?dfpiY2t8Tkfri7G)>##CTpJ4i%+Om=5-s;0Q^MvOFr~j! z?5V}SUxN3i@Amj*<*Q%xOhHnFX+c^(VYst;%LYB0eh6NXt%pc!mL~@$(0S2R3Dz9{pb7$%AI{1r^A?BN)Elw~MyN+192b<+tt zxX8bS9tmGDUvmYp3IH zC28o!ZLZCzLrMhs!%7S0S^N!iazuWccMvpI%eebqF0*B5iW`1!!^|Q0E-?=0DkO5w z^cpV<^N_r@bh@vAYh+Zw)&WZGzp`*^^D9XiSfr}84?#mvpqixo%xyjI~3Nc4ru^+FN&_D-Q(%}4n{-gf}WJ)1U zXjH)zlaA=XAcI_2u#?ws8`5Yzeuf5#i~PMXe%4RqAettPQiksJENf8y!_b@-3Q&vm zZ<0S5C#d^^IsNc0ujSA*&)_SG%N6^X00&(%V9;J=%>#P-`Zt{sX4L#7hx;tTA-YM; zZwsEZ2q(LK21^EN9j|MNlMBcwPHp6jsR8KMHM-f1qM>TJ>@pxbB56!ABwZ6!Fg_x= zT(twWk%3Kh&cXOhKD{Gkr6IR)S(~NdwMn4tB53!S_VhKWt()+st(XDF7--6!2&uy*(qQx zMVyLB#1|NN{3-t?0}gvzfCWANBiRUBTvD;8SFbMO+DjIVd{Ju(icd2wg~hUoUERqI zI{RYeI^F;%lt$y?5|vXT!R?s>jU3c3mW!j_VfvOW55q!(-mN*nsN*5B^$0YvWd?vK zBm^3a2;wDbLs7LThf*a8FJ5zYTw@HJMH6NF^WocaI^Jdv5s3T3+r~wF*B{nOxsPa= z?^b-8U=uK0RF6iF{iOaWm0rm^jq1~tYTPohHVdb#tEtNqHsvh3<{ z&RC4Z7GoN_9rqs!djw86gVbAceKJ2PWqxD<_A@CZXw-8qPQ$_1eH1{mGJQfB_YIQZ zo>d2N0B%lSB@k9-4IoN2V4N}hx@FKLoehK*j}Bzbh3(jVl7M^ov!o1COmv9>L>u<#det}5rK zgxF`Eldx8ejdw?<~1O%V6L0lzMFCVi}%onF#=E zw<7E^q z9jd0d&+6#P0!>9V7YsR-N^Ld{i&HM6LT`W+hX=0V24G$>%>`oMw7EKBu2q`mzelPUBfnGh1n_vBSAc5CXH$j9AHiG<}z$3^tN_nKdz1dblj12a@zE~L7YEU9a7M*OB&LS<0W?A`|^^S^qVRb9C$O0QG?d$|ou8tzT-F00iF_YQZ-{{qK!FM)ky$`&vDUOCl6V0 zukG@;4vWG2SlRy1r@*PlFgQ+wvf!}xd>?!^Rm&-qoSH3|*wxM3J7*{T$hKBtrjE`3 zqL1Pc>%aSJb-9hU}{PZQ6o@eG`#9IxKLP{vC=y*el@=L^d>8|P`(;}^nQ1qeiIx8S@z=U8o17KrV zdC=o6qLD^N>E_PQP>B++2&sg8H6R;3WOTsk@+tHkZVj=s;H)9OaB;Bcw<$%8l|TFI?vMg|V5W zld~%+YNInkx44L%PF}{0PKJeVh1;nS`doT5(VBYl0mmagb*y=5O2>1Y>-T)V`g*uPgW1#@n&j9JbOZn8dVov7+ZBf0(JiU-TN?n9pl}IZURpD;t7l zj-~4*+~p%TIfz~aIZWEpXy1Fx<`F|w)5nlFaE#@YT1V{Xi&)^?Xt!|bIeRB6a>sBl zovKG;%j^35p>Ui+VgrI6CeN)|KPRtO)DzG9d@60cg7N@7%>CeY-)tKqlzef{J_FtW zeaA1u={xhC9wTnVNdeXHE<{8;L3y1($FVw#&Mf(`I1Ntu&b{wal|at%cB1y-NbLlH zPHj%nKz4g-EnAwfb&Y&aD2n(3cEYo*Pu^G?9XGS$fJ{bnsIo0C!+mj!i@@po%WVJa zN*K+W#1L9RU%~lpX}Sy2EFc=MUn|cVeuunEP}xqD2*`edDw2M38Z8s>Dqj=xMY7uB zk9bIo90J0(<*a=lGUqQ-#;E$>QgN63kD+c(y?+|&CVgGn+k?4;9G=HD;XYAp8?}0d zEE1N(*}V=Nk)7DYM^n8&$oxRJ;Y~W$!k)fR(9WVnj?I84$^S7%j!<{262=7(`BQ|P zZaj^`)LgL^DEFxcHTITD8@YG~P1d6=wz%t*(y4f>_maMIZ{v>(3c|=cSnSx8f<)W9 zX(}#`f|5g2G}o&i)zJd>WM1#ggAo%ar%(3X{9!l6y??KrM;_JjlCJw1YM`_K`+9tW-NY>fqTqDJ-_ai={vtv_BQLFOgvzs~`o6xVIha9a zk*nX8A(q1*mDu>sf&cI}6w9gB22z%w*D`x^sox!tX5Bj?>We3Q(SSgVX*BY*e`4Gi zPpnxu)UKY>)>e6^45S*?Q%gd<0~6)Qp-JH;A{tnws>wrSkF#pX!-5V+N-&BvKoeT2 z?l9Xy_As6y%&clp<1)!@tejX~@>y2866FZ~k4WZ%@OAYqaEnvhua*8xW8Q);xv$|C zD9brAJFl}fgSh?c|~FJ5Ckw2_Q7T&H|67)+Y?xEWHzs~=u6 z^vjB1eDw4H@sm3gvucxa<6pK}L2pdhT)AGznJq<>b+%Ni!w^|lu=K5A`yw(OtCg&u z0nUb3(5YM;?uY&0-#FxN-S6HTl;WC4#8*yV6=5RrVPF8YJSfsZ`_m>vojJtokNk;n zQawc>9*&%wv}UB-kwukdxTWpH)Tz&ovJe2Kpx+Xx-oYM%+fziE5oJGzOJ0JvEUY6qs})PJv&thopY;-g4B;_7JW~{Fp4J8ZOcZjc*>Y%8gh*uy^`& z;OEP8>xV|k%*ezP8$0ohz-hDyXW0(0&%OXQcz{5fBviX3B0GK8f*+?kisjAxzC4nK zR#q`*NeQb-ckqEYge2eOM&c^5f)2h~l!j!A5r%2mJ)#9|WbA4nm>CAS0l5EzG-+J# zO$p2p?qlX>W-p1M4MCU6r+fx7lw2`rnDP3fdGGe|t)dSY z)|Lq+EV7+ZA900{0Q-=?j>y+G0nbJXruk(w&2VBX=}`1lnA@6@Ru-mBoMG)A{^cyW zTGMss0eBP)gQx&^&ZjK0Z|m!GDt(Je`^C08fdKDyvgwm4`2rF4#FShg2a*#}D-5+S zc*3<8BsFbut{x+L1wYpQNd)6!g}{~}j9o>{s;8?|2#xV@0&oa`Vh zmng;(wSDJsCtYeDD?Bv+{|5wVurnJm^1tS}LC@je(0XA1XdI|vGb317OKpsdl2pL^21`+`voezCfoqLS;0oQ1=wP?LQZ2_m#i?SGLB1-W$r-SUp`p*CAklq$4 z_~N|a;;j{WN;F7a!#Uy#8!bA&twxvk&%T2V9_>r162-|7Kl!o^S4zM%k9O<&tzZ2^ z6II){zxsAzaltP&xSsfbkA;SFq5sKIz0m+PJd024kGdJCt>Vvfwwhr7EWPVepz}ww ziz_kH|Gm{e$V7t%sdkIm#gmDkRuYQZWxSI$%+L^P(C=h<8 zhR27t*}Cs>_BHMMYP&e`SlmBaS^_VV+IBC_8XIlYABAXZ{iD?fjLhKdpbnbz)wh>{ zZMVcgV;ETe<&U!72s(FWzoQ;~fVyWuVjJ{*dc0U@q<{GZnl}y&u3Ktjv78z4I5|&) z?|_1V!)9Fes&~%%!D|be@0Ye3q<`y3o0q@MdYmM#Mu=A(C>R?y-!wwx37wNt9V6&& zxDkCw8B1TrO5MKvRPU$)Ma!7f>3BV?a zcO+;x>a9wDsc#0B@o$E^4^apVwh$HwCv;yeI(@APO3E$*0TGTxnX^<%<&E{=oasDJ zU9wEJ`5@q$(#(@MUOWH4F5VYgYW?{e83SpH(3HB_9xzI9G;X2k+}XCd!PXifBBrt23vX7GBR;! z>dqJHKPNG=QX_pRBnI1ujW*+#Nf_DJlC3Wk2y=GQb1$$AaJ12OsY z(~xG@Dr&)meIz1Ds&C@9n==O%VFV&^*?Szp>8#J8+N8br9mXBC6ZLu~*U0nh)FN`g z97f*z=Nu+^NacT`AT97^s-@%{6-V>6vr$*4PvvC74!PKDA#5giS=n6s5v-vOBq`P{B)MSSLQu=s!p+Oq3A8xb@Y8LZQ~=* zaBtdX+XXtcfj!G%HU^|jFfwy^b;=H zH9nneo|a6>G6zUV5`R%VAkP%oZ@&PBm7~yBcWtFr+K@ZOzi3C_23~8l^JJxNw_*6b zNpV?HM{drbMC!(*XXYK?o*ucfigOg_cOyqI6D7fiL+37hT9geM9WHHC?hZ^=uHP`!_j?EHRgh*MF_~@rUyjO-8lwP@-`^fut0A8`n34w z>ll=vJKYW2sLGLJS$He9`^+*WDiTBl13A>p2)E&uCX~pbC|k#(XxUJ-gBF+ZmL%$? z;9D`aHC4ELO|k%E%Rvxxq8>;!S(US<+a%#S=o`oAu6YLbN2MprUMLJVY!{b?EZtZ5qlLg$57|S??ndmkFqKVM5og zjP}1QT||MX;tRKvAk5)TOk3nYF-j+4WufAM*Nk_d;BN=2l!g>n$ePE(LnZcW`0Y9I zE|RXnN(W^r8;EhYv0d!F>cI&5ws%wjingyjLWW}~RYh?S1d3G*B=&vnp($m)0 zYs1Jh;yDxzTS)NA-A)o+&4eL6%(O2z?oxX^&32 zx3f(F>>3X1Is_A?xj-M<*c^-558_L+LvyVJi!fm zzsMZw;6Yrp_c}$kNghNlgnGI)t4;h1TI5Jl%dfedN37Gm7aBhU=^p>oJNp9hsC)+N z2eatoA2Ev%Hy%n7=>NsJ;i`!EYl2caWNY0!b5p%=!DQg3Xxb_-g=uPJXY5IA&x9lf zUOvdQCc>Akwm5OQ?rnSeV0*NOK5@TBarxTH1$<*k~FOvyw`uk19M$`@(8<7-=O&CI`T@A z^u6H&%s=O4Lp4ch=W`2v9;MsS2Q^PRJZ-~bUj1Rs8S+$5{5MbbS<*THGVa}t=rPIRXw(X2fMeLOr)1H|t&**6Dai6?R6*T;MSf6z* z?LoSz_7G}i9_Ka2=w zhBGkis9`+Ju&mzh1&-@9*&?!SGzqGAT5?wK({r<7L+!PWf}6?x^dN4nwjDwKQIz#c z-Ti7wg>H(tJ{pU1Xpu z&S%mCCaChy?Q|s&O*_Urxw<3lDmC8Dreq2{T^I(bO>x$y@-n=b;&&y}KKx2o^FNDcCOO`1nWprOa7}Cs1<;`H^|1y-q7c10cp`uy_ggoZ6E;l88fTj*80?r)q^7}@% z;k&&*Vb>juT|Vn?JG~k&)d9&(<)$N;Vgx6(-kKOJ;Bl{nn?@y{k^hYt_J^3l96|FC z0u#iF|MmO&xPpBa{TxJUfMv9UD{^pt&)mv$sqGw4WY3`v{qLH;@Ix-1-QRl?Wn5kf z#?nX3d}EcTp>?c=8Q`?OXz8TL5G&MBZjHu zq75&A^BG!QGTqV|x>*ohOo{m-R@-Da0l?57fapT7lUR#nlhaAgSdAG1Hs!-hhErQc zO)jAuE|Hr>q`rxI+sZ+f*HoLRe;0C|16^sDA`8u}1KVQj`+pylAi!69)TIlZ8qBzE zq9j${N5GFJhCM6MJlb6rqqlNzPl@Pp&&(KJ5>q~ybdU2RyH4VHn3zq!{^-k5ykW45 zXoo6PkCLG%7u?ih{P9O52IdkdqJ!+H0`Og%L-`q}-rkFc8{W7uB zovS(YtgXK5drdoxt_jm$t}2^vG$Gx@^4PaIUGi_=^8jYynfbf&3)Uk;apOaSuzKeg zn`bBo2Exx)La2Q{hLedrM|yRgL6?K*Iz}`+Zu;W^ybR3bgS(Gk3VFW^u!-D*V`4yK zG9EY%4qLNX6L^u$N47wr&9Fe@PqS-4+a@csr^5^9h0#P2fQ)PO*7^KGdYh*qe|Yk( z-Of&`YhUeb{~HIz6gS6bTMcGPDRu>%DN2N=chWbz96KN8N@ZZKzdOBChYJH)R@o$e zIg@9Xz3s-o$dd)*e85j9$12`7^pVBsG<06oCdyJ=sxlt&z%Sghr;1}caTU%^aQF4V zW2bl7d1xM~i6za$-uEZ03VPm$n(uDpP5*m^YCpPV?XiT_mNct`+ zLZ&N^DNCi7-eoNZ9Z`}W!8;9(pp_lc;}<0shUIFw2^qg7qCjqvw-8ujj&0dOPST`n)q_ z!b0+UI%pV$r6g}i4t*c9`8p|^UM1BCQz&j(yHwcXUyo0;+!}s5I23xOcTfC+>toIQ z`(o1*VxCe2tnZ~6xVSf4o$0aGOCQJJCKxPtcOX2&oY20Y>d1>R(EKm#=8h}D9>s= zvyZKACNnf8DcT}KJRchfM)v;9br}vK--HyO*`sZ z&K?)i+=F81{K9>Oi!tOKqO?3^$mra}{pR3jhJEnE0~Bv0O9 z_Gg6VG-j1G&E7+Pe9E>D(P>|1Ik2B&6VU&lFt$%X#wiIeK2*(qSJxMJ-WBF6?bldK zKa^i0IWXa9h>LF-&{TU@DFR;;pZ`NPCqc57w* zEb<0j;1S{OQr$w)JS&C+&V^?0aMvSxbO~Xnp(;9I7z6*A@T7*jwyM4@SyB8f3rf>F zh+6O7rEU6v`WaI(-gvisJ5D!}R5enFDodJQe^%&Dnss;`ZFTSCDqNHPJOxZ)YoooA zpse28N<6vnmD70Rxa0Tg(TrlR?f7;Kc`hL9Avqw8_mWW4M6|L0SO~>bbglA5Z1FK4 zm9mqmnJMM*K23ee-pG-~g@k^3s^{%lNhQr)Qk9bl8pkGfsd6%ETw3oP6qLvF2N|A( zX;VwZ96<{2P27pD*Q+&cM#M}RI%-u$bYSVjCd12PFKvRMDT&VHSKVcpLst?<$BYE= zXf&<}Y&d~#~U?yzM;ydk2+mXqOKQHt@j5Ycso&X?x7!>^1(v z#sXmz*=uLpU*DH#krC;7rS*7Db6%QD&2!VB<~x*G^(raf&|rpAWue>QCi3P@#oW)6 z0Z}OtGza-?@>+OC#OoH97uzCy%Le#A4tqc5BIqb;-spWx{v@!S6=ufaE5hLm&50?M zp+1~Zb(i5lu5uw?XC26n?Ax+L`7K9|45_MG?Dd>-`;EMTB6jTM6?F6IeEL~_e&D3lo%Ql=i1WI|X*0myl*sE?zJgLq~WBN_*3vr->E7QY)Hl@~q51R_KWD4%*(KDKp$w<4;qF5-2!+25T;SMm>a0SSeH%Nf}ci>UHT;HY^`-8mqR?IS!b4 z@Ym(O6O$3-IhQnfzyvja@#6u{o(Gk)B-4^; zQHAnoxH6`xpZyhQ-Y$jdgw-X%4f4)m+#mj?!u9SB#W6(@n&WCqb|P$UYShfAsAO-S zyKlvwK2j}Pbkw>edGU^!wJ)nyHRV|_>%Wi!K14y{k*K>}-sy1r96 z#v#$owtFU>d39u-mAS)P`K>sj+{Y^K>^8c5UNQREILf6EEg(l+AM&zB&+a%cq|d}y z*<|7qB`hP4Dq9ZLCaTw6@(&+Z?1E(XrZ8`rXIPqwC=OBOXs5|37R%sf@;e@Et9Z&7 zne3ZyR|e}=m`VAbIKxglys5D8vn{%7@+BLoYnsSv{2KKRgC4|gqWjo9Pkc{0!9IPt zDd+rVtXt@A#$0pBgU}iyY zvDTBEcgA0x<%l8+MRRan5Jk*CVhyim)T;m)&~9K$(Qx1d4z}9eR-qli_Kt}#5*iT- zmD~?lbxHjD+v5#;Jb^dUEZMM4bGCCeYJI33s8m?p(mRez1n?gfh>mTrn^@E*!Rhq- zsod?oTpSP07R9E0!l!%1 zf}szkH$|O13yn&z=a_Cj`N$boCzss9#+lK#Yvb3^f#5mrTGL|By7c5GU8lg2zT9XU z`g^q1k6-8wOn5#eac)f3Ml;6|KF-C!A<5CYk%FCUT0Ml|5X+)f=P+#YDH(BpAHHf8 zYr(L(S7gSFZ*O>-&tfet&i`A~>uIpA<;@sZ8-oXbhumHnM7w?^ zhikWBNhNtP-Fy^}uYFN_@1M6X#osH3LUg8$9Aj=rlAjNrD`@lF6G-%JeI)`$AiSLc zAx}6pF-0*fowjSb_wzy<6-|}3pajYY)f~K=>J#hz6 zORyfRd@bjHfa;b#MjJX=f6qV-U1gsCjL@yyXp&GoU0XoS4)a_3prv9nCoV#EaC0$z zmP{u%Vd=bb3N76IN$<4JL~_a0Hjez#{$bF*CwF^H zr`)VRhp3`zs;O(h=_;!#6C%Y9Ke%JrRk*28pCZI0GyObuv^tcdrDr(&=TpC&gf;dN zg_Hjxx#u9ObD(Iy$>_2D)>jvcn0SLP(m^PM+Iu(1@HaPnl)gB$YqSu*qN|cwefxoj z?$~*DI*z$%l!U*HKZI{BP0e*krgXlLh~ofr*=G2js43MBBZF&(JL)jy=8c>M({Suq z39n&!vyIlH#5($k$kidP%weTF?nyQ7!sW#$srzH zDoZ~pS&7wZMXH>{@JZ_yIk<$>)DAvY3D>q{7%|hb)Zolgn=Tam_0`?!GC%Rt{XYzPU3Aq_4xT zYQwSlIV7{$)aYBl*8Bzje(QmTakh=zyQuf^!qL0Z@6sQOi}#8Px>*Jk=JRqhq-U9+ zoqQ8cFD(PlJX?GmSB1xfaEs#Ap@_5`L%QySO;yQdG+#cmL@%~M;IONfSdpHX*4=`G z5Rvh%8ZPoLO!OmCSF8qvl(pYdc7;PKJ0D@gXW62xSF0hT44ECmH%JB~(+{Vzr<iFV2}-g_OHHxA0B;h=i0^;bupL2#rd`n{9~+J zcP>zSw}p^{yq>Ty5x1nXq5AQ2v*a)km-pObXGUO>+}CTLwi5Q7;2=H|p7KQ_`u6ok zb-+LVFvkZB-5bWmQxE^K%wN4G9)OVEU>ihntS7+8fc$D-n_^f`kBX6@yC*_Eig$?V ziR09nBUawq*Dy4o7dh(s=T-#T{={FKp#Hx%`aiGe5V#m3)hyXJcIPiI2G*&s&!jo` zI;TVK;=p;B8w>gI*$^W8x|mBC2pJ}@+nQn+CoZ>#*3X9CsC4-DmkaALJ^0Emo6pNX zUgykSAFWl=bY4F{n+`$KrKmWl`sb_W8@R&auc^AI2{zrwPmIu!NxmhJL5MG(5TRli znw$umm~&FPuwDI{*MfjRJJ7^>w<7|nsG+K!l0Ndg`Qu8uKm1LLD40)L(js*4_S&}H zm8GT|FEUx*qr*`6Th&_>pI<~$lXsFdjnG!c&QXaiG-MH<^lk(_;*8Dm*2@7V=S7d*0jD74G@|wiiiR?* zC5p}_%`+xsBiOR8Bp4XQq==)%PD2w#WgYM+8MJ(+Sl&#duGEW}%#QN55t(9<~{Wlp~-G<3Do1IJglx=h!v72JW=6-x>x zvjf)^ks36|$1BsYLEo9a>~p)M3SlF}inD0?tb~3$@2ESdtU_aqvbdegPLi<|p{;9$TFi*Kl_hwHpYrDCWEY%uJh@me$S1B9uB6YM zGQ_MD9DZ^0>UnMw8sR>koTWc&pz=;&W6B4cABIe@Sjo{buH)A?p|&u5b{d$gvBfNE zo-aT4_Bb-9M909FV*jy!xEhtcm(K{krG6prnzqq^)o#t=9m3q zX4|by<-PJ_%oG&qfusjQ{T<`^Z&^ulRPX{2xvg8@Sht5w`3yfVS%!bB+gwPdor~Y5 zRLO11ROyy;56^V%t@o0nQ+Yac-Q4fq0(zrz7o1bFbbh=^x;8nx)H40&sw#6gdo5&- zAHt1N>Jc&3B2cHZq~ey+b~|1x!k&yZ^_#aqb;n`CHd+^7Fi&s0y)*e<7Ppzt=!{LE zTRG}M@D?H`GTP(Q-Sh%_7nLQNyCsKTg zsy^JZUWYDUk^KCs0)@yXj@;x!Uo5LKdsY_kXci3Go1A|AkuVdc@NJV*jGGSuhs$td zDLftD@?;Rl{h|4tSP|o7_ql7mCtq?b3ql=4xYa`_?Vo*vm1xhzyg-z2`Y>?Kds!!P zILSX=w4$>A#=4;&`ZPZi+5^yPcDyGSv&4(oV_E^dX46BHEHE^xe%?+@mGMPRe$uEb z&;`wnDBVXeS_3;xw@LUYzmuWhR}=ugV^ z(u<$1LFSAyYClh57(I|#Eol)xwKjmim2$X%Osgju8cV}m+(NRFA|gPz7m1=(2o}qg zfzqt61G3EZl)MYvP-ZgeAewgKZI`K`uZH&xS`JF+{lBG0^PN5@Q8*IZ{vw?BunTX~ zAa_F!z53$0y*k;kL#5Gf~QaTN?5*@-x9@Wlilrtsus3PqQqE6)<*Kycm3PH*mXd-R* zv7S_XvE0ja9ewfA;BVT=c1@COd2*|btQ?lU=%}Ly6FTo9aZauxMz}A!=OMbV>OxKH zu@{Q0nafiXwsT^6Tm9MP&*@vv*OF(iy?CWzy;UiWYS+Yx3$rZJohz-u*dym^`X#%m z|8t7J24x>w+c;kW3i~(GfzR(Z#(2p#@r{ge%~WZ6A@sVbi#|arb15D2H##JUGE$#C znr}i@J50ga(PxfjVV%sR=|!x+2?qfGSDzpNza137!vv>%UEGM)puU-Y&rKz#w|s2pmdoqD zn7XeBA`nYr1WB4F0>1`3lif9nQ(2Rd^0YZO8i@2Cl)nYj4VR{S=-aU=b}Dj zIXRWTvzUZH zgkqvt9+@qJ>WJ8B4jK;p;=|*3Clu~oJSqVd{P6e%qQWXwlGSXV#%57%l@X=aC*SD>)9a>JE zulmSga{GQWhfM}H$KEq}aKj?_^i8dZHAF4*Cyp$#&_tMtd#AOYK6bmKDw|B1V`U2b zsO0MHVx;ir(bNuiR5SKt*}T6pqSD|MY|+0@h|aw8HUR6sL5k>})VI$tDtl6Rs~=XYw^Y4URW3}+4#b;BF>1M-hqcz|*x;A3tH?H7{FcE?$HaNiGSNoW7Z)biiA~)>)&h7s0iCvd$_0Ck1JW`z>YN9`)x7AxC#4 z9b78$ya}{Ta(<3)0yIH`1fA(z)z-m@;HOtKowLvT0?=AsXIDS{$98k@$>&EOVFw_S zf6|MYG2G*_&Dy(AW))YRx-aT(shjghl{?1=ygM=Q_up?-c?*Zyvl>jStb%HW&Se3A zFn8cY^XvjkQ7RHT^!jl4$%*y!2)d=mO}o4P%`~QVw+&xwH}xypEBi&)?A;9x zdF0__a9`FaKWX0LE_n&=x2`)x4x@Hw2D5!#k4lir?>MnqoZ2$UIIg19jt3FtK4X*M zpwwq{-|awWXj)Y0@Hq;X0tZvluYDCg2lkeHY->!P7ni5!9V3$VA?wTvegBqe*==DL zY$0^aRKaKl4qf9(M|y;cu^>malbDA0jn-3V3bD#k5D%swjv9f(Q~iFYcnA}pBj?1u zf?oJhLoE)KiSmkOQ^zAx=1M_DyAmhM`V^HYP+erFS3RGR4Da31mI{dl-GiD9RlU`N zo-U#@Wa{~C-ZyJ;i0YO3<>0xN3hR$J_cVC$I?;rR5yvOtDJ>Jhc!!>K~z4p1zb)M@Q(0v$| z<@l^#KuZVeK|xDDgjX!anBmFbY|z6`{5S@ zVUy)KZgQ7CV-AgsTAvpMksm0HGt){*!biY`2m&Uy`wpYeJ|B=;bdLC1FW*q11B{Zs zn=*{8Wb4^lAC(uk;ys6AVThM5;Tkz!rflA3YM2s1sc4cQ63y1pw`h&Bq%vJvty!i8 zx1Vi)Am95p0_vBh28MKu?^q$)t6e%QrPPcCyr%wxH~DfrD?`#P!vT@UJtaBAQ&LHm>6D0*W3AxJ z6Z2?ytBG25TQASxt+DFxmN|_^r|YZa2LI986TMz;`yMzxZ|P@xYb)jc+qOt^jlA>` zRxO3~7F=8v(0hIl0h*L+Z>1hd%%r6cm%bCguZ`lkE%v%5=u6c)V4FdS>b%N?Zjn>= z(M*Z-9AuCz3@uZ1rE{{XDdX~KaLx1d&*;8%ilaL?ONl}EX~9VsJnHTO=av#st~#qR z+QkS|D$kM_Cqu20c?vd5OebTke8x%P#m_YAP3Nz?TMJN&M(7mvdKxO1pCr(`cet5G zAu&hP9iPVBUuwgyrc`OPlb$zZX!ER?DDPoOz#x}qRV)Q*o3YzXP*PAO&bhIrSK#h;O z4+Gu_h$}X?VQHFyC7Vx?a^CkVV(wt8#9`VjVaWo@m@%=bWomh=Oikq)ci%5MK3@P+tPGLppMhe zEqGSrF@Qi}-kw&L_Gz+x*QLyX_^G$^#_**TOR7kyr(vG%N;r(7u}nCv<`Yn+ab@BxWhIj1H2LKyfWmpI;zQ!qW-A&dPVlN;Gt%IKhQ8<(lUdc%fPhnRDaO225Hz$zz+kLeh)iS~Zcd z$$I{JgyO?_u0502GxO4{`X9Jt)IN9I-8P;75Hp-d(_@)I0fjK*VoNXL!XIZhS?Ns- zLhzDM%(=VXr+md8Ok`EQ8t#E{lWws}aZeSu4hh5?^ms($e!u)S8mTN5-JR>x?{a42 zj65;mCY+#sE1t`cw>@mQSs?gIoLhLC`{q!X94D2K9YLJ@9HO?X{an!XPTg9DZvnz8 z3uCOeQIf(zQrw%D?27e8Wz20#SI*^83eJ~IKl0DIX@y7AY@R)p=c~mdbrMEfag_Cv zFeR-^03X=Nnb7Y&I9^ndY7$ia%|XzAHoS8SHYr*6rchkw+pD{kYWM)W7SOIDB)#y0 ztXMqRQsuiJrcmRTr)fpKBO`q``6RHoG00pB*5^sJqiGZe740d^{Pf83c0&_>nJ_w* zXcqnmODRTC0T&=}asySsy!3n;MLB7;{VQ_l>fDN;e;-o;QS9ftZ&FTqWXu=NANzif z9I;T3#C1JxGZ$9ITtJbu$el>BS|<;Q@6A~YFhe%yt#LtJB<8G9c3iN(n!;jD;bXSV$&Pd! zCom4wOS!2%c7dha#bjyKP61 z)|T#SZap5m%fk2y{m_+#%Tu5CW}31bJXZC^0|M6`H}cV$2m!zRB<5pHz*^+fl- z0IH7kt1ob4ofopS4;r=2OaC=(Ox(#tzxG<1yV@3eh#_?y$0?6nINt9iIQ9+8S}FY( zmQ;c1LzT(~FGMUiVf7GPCf2aHncM>3sPn#NmA?Pb^*A3-c(rW&L%!ScM*L2d5A0xK z6a_R)a@~dNFL$cu>S?W({*W-?=GDf_USC#dk-gzMVu2fYVcxR!k@M>tpfTe=wtIJz z-L^&r3-hwaxfF@_augAiHK64(v|I`j<4&(fk)%~~JHeIoZ37IwJp~mP?U`<-2-k)C ze6yKislCMR{g@mc|J+;r-8+0k8~HWDlqEamtUO(rhT!GbEYgV1S*?=t6?Nr@k#Xuyp6*eLVGRH*5oZ@Qe)ld%q=G4z>WmQ5M~0GX$YfUl>mLoqrIP-$%p6T%FoF48T1 zp*e;~>Q6^n9fY4W^7N2pv4)h_`;ja@Ak@*2O*2a^TKT}uW}A~JG{v>N^*AA540Uid z@+!5aZFk^7bD(QmscTz=`+UUn%6B+91zOtbi;|Z}Y+l?k+_+!&hkmK6JH?el!Dt@! z^6r7?8wJIw*`!(4a}ABQU1v?5a5wJaiGCMw4RIqQI;~H}YiKsc5Wr@} z%2m+Zc0?34jHXoVUM{dSsqZAEu!KlpM2C5FcewIci=uK#G4(R%rLRk2-oC5v!IT3C#tpOy=0(`_69ldIL6Jp;9eTltQS zob+3nQr4A_lF2hsBClY6<8}5-0RDX@&rp!{fPN3wI-z)bNQ*Qpo&A_uj1HE{O+I|>6Hv4XC5Ijrim9M#j6Q23ee{W zNn}UzWaAG;&FZIQr48lZY~bg;E%F0_1I0}iI*8IcXuAX_@jik>Cxri+G+uPJ+?Rurf`am#%r6q8l(3-*ruFABf1gD--m?BuDAo9STY#3+j*5O(vVeE|Ip0 zEUgm_KJZN`_@*`{8@}~T3Kl&A+U!H=onc;c31=qAlXZPgWN;tWy#FqDdUGUqT1}sm ztgXbqxdC=6+sGA7HAKZ0eex``(!0p>Zu>hyLS_l)%{kM9D9pJU)+Orf)etfr2e; zkLP*(mC1ctinwJKxPM%@x44b2e#VQ4*1tQWt3!{~!rQ})` zJS8~#ANN^^DS+z8{ERa0pSq_91NRJICVP`b^`{3X2l^4(J~d4l-DWE~06(!wUU=#> zA+RK&bZnst-~6+j3KQYiTG1u`q*Rjvnma3wKXgx)LK4XOWU#}tp85({XMYS`J~bQa znRP!agKU`QOf>Q6di&?~%JE^dhW~4_r?8r;5eTLlAvy^p?^$UrUuG3v;$zdO4=FwA zDG@C7PHmXgkpj{^1-Af*kZfJ*FGP;1nvbW%vwP5vpYHXNQ^CN6tNk9;%IAIliMOB1 zQ=?=Mk5oee7Hc|cp-P$Se(xv(~@iAiHOU0YX1 zMB{tJ?b^esp@?sF7eo7;a*<=XBtDb4>v+iohr0alGOZ#-k{ud2&?`K zH4cWDtA@0hNc`-Rb5xg8Nhp>3k`Z&f2GJXT6+kMex)_{xlwo(Q_1<0Ha#+!=T$J~% z;S22rs+`fD66k&2nLCx8Aq4OAWv;b%Dc#g%N>98?3X;&JSDthMnpqSdu5gs__yvsl zzJ2$B;l65+e37gNyRD$tETY(D%8i`Z-ZYj#KxwD2`2@KN7!pT=zC4&azt)l z5pFCC6Cht^wKHI~^+8wxX_TC^>_`Cjo&0K_`?#jbM7A$ZaZzF)TvWI!ye)^fw?1XU zaZ;SySQ_SjQ@-aU`ob+^FKS=pIx3^15?7%3!f$01#B9~Oyr+KpCQiqvLpZBIpIkmr zR6rz`{+32)WlW=%A*J%u8<*0(O(~ywLWRbxd4Co^3uT5PWOOL-NSgmIRYmaSl5!if z6qV7OY5l*I6oby4-g!TugW|(xNp1G0*&qAL6nCbjJ}VB`OJsf$y0q}y*Kne;uC5U# zdrS58D||+REQ+B7z?chf+ZlLV+6|+*@5?V!CSsjud^d|QtDsM`QS!ZPY#lm^>eVvO zQ#q%RYXUeAfmA;tIPM)fD4izvoS^#dPG-0o=&9zEZw_md9bKg+`nV6g!YtnKK~dgR*#AR?qns6f38z^}Fm;-g(m>HN^aI{_T7O<|y6{FB+Yh?kvfCArQ6QWD8`| z4`%n2usXHvRM&|{)#Tupu$;L@WlHn_J9bmq{sxa-aq#7B?*f8gznCPYvlRQeVSZUG)7N7$1b?T{WhDd={yAj#=2xjit;(IR z6-SrN_|@G1=u)hR9|4A9h^$)E49%fV)r41wJ$xAe25@-^f^!xbO%k2Jv^}jq4An;k}dk>?s&AfTn zytEk?Y$Ww%yzc&I0iwpJUwMVu)VH9)iRTE_@mi{mo|YDUP?k^@9p+b;biP(?SD{yo zO@77Vy{F~ft2yP{(m*FwZBPt!QjI`D(X&1qe>`S8lvF@FNcntihG6TB-1;{jJ1N^k z3}e?P{z>sOp|*I;A2|{L?<=^r37;s$=Hyefha%Q&If%*`w&F0y!{gchNg?HJlzXI* zaz@jZ7sGmo=;T;GfwCmp?I)$4+>uH8hgrf?brA@p8nMm!m1iV~yu!z*6jncb8|t4s zJ69*4`MK$lp?-K2QwEDix{3Qz8G4ybzJV_Q{z((>O!B`V<3YPY`t^L$|NlS#S8L1a z;PHM3_>RLO2v$qtzX_IWgP?1}qBeFvqI$_7BhLPO>30Fzyv3P+sB{HHJKeX7;k!QY z-SswZY%up8xuqR2*8Ho6iDS(|cFh7Vd=;0w;#;zh_-4}Rw#dhd>2Qq>Vb`*My@706 z*)yIvPIzf=8)$F8z!iNl0-8L(wNw&BZ~oEF^9I-);<3vw&(Z(G@BLu68CXWhdwgu| zlwh%@y`Zk@KR1w$hew_U1`)qD*&BU`v*%j8dw_A~y^7^O#rA_8Xzt2r*&d|jctl)) zJ2}9(0?V-``J$+qb2rQ9#q(W;edSQ}IiFjz_r1|IelKjH&X9e5Qq0PRH_8wx&Q-PG zu3`(i;s06i4rtV_vLs7g-RlEwyxFs^7wX~)3WO_+@Jmj2Gd^)=SoA;hq`oyfn){gjDk^<~N#MIp zX{m8vByPKv@@da~#+bEil!Mom_c;X}vCs?O^z0;VRTo`yYz(6>eY5BIW z9Zy@-9vbG0U#u^HAJ=}OdzK>n0lRcoB3~rh#?OX5b@_gcLKD?Om>S&YR>I?P|N03% zUlk!rdfrc9McbQR;c1sg5KYxF3Gr&5c<-i}*$C zag>JE4(3YNbi8~!O!DPY1SCy=Xw5XHcz0j{r=F^~$P}H+RC0W^u9dn<-cF!U0y{Gq zP7rrdXX|cde6_FAw>quP{x0@_#=Hks&E@;q!U8X-%LR6HqlaeMm(HI(+x3auImKw& ze6c?UelzrRvIOaqLXHCh-~UD6l>Y>`Z5cItPEDsZ_9a8bL;7GBK)_o(I_e%bCnSt<4Nu=GKmL1j~rW1M)44yHW%BAxDHzW zF2N#Kgw2X(duYF82`s0|^Pa+sF%t9yik-|sK82$?mHlq1=;fgz-{JVVak&Uo)7|RQ zcfGm?`-}qWwx8}o`jYX=R`L2g5!88Xldf0+$5L7|y60?>#1yo9giKtvjeE7N;OAxnrP=eYuB;~zG9VZXX{l1#Kfg7T$?)85RUV4_7YHL}JQ~2>ynk5>_Xw z^LY%A%|;VX6G`W-HWmK#c^c2DbbR{ceKWeQQRj7Tiw-5*|52l(972?;3)*c~Z_n@P zsf>^r`%3`Y#gpzWnX=E``T&KYe1TEisYWHvRLeWr|nWku)s0- zaRmn@%OfYv6)oG&^RiwanZ+}=iX9!fMnYddeisDL=s7;f8=(rAUAG6qrB}_pEt8 zi|;QQUMY2Pw}t5DA&RB8uRsIpkIvJ>iITVW=1D!7B-T>wICnh=Dk(Wo56|iv7k438 z^0)%lR^9PgCDtFlE&qwXY@aW zc#FQAr(F8gq%?%m$5SOP^Uo=9iK_CJ z+j(6U#rQrYOxaCj94ZX*Jaxb27>^e!y{pQ_-l|HDYmV<_?)5(X*J&9NS0^6CJ?NKV zV7n&1)aBh~y8bc?9<4EgGQ2fdtV4A*j-7S+7Qb$YF&ww6g+ThtwOxHXTV-+`_F=>Y z!V@&K-t|R-oJ*GEqLB*N9Cl@5mcmsLg(zV6T0ULU@+^0-i%L(oPnBT-pk z@uUmn3*$*uc=j-aMW+*In$PoY$E&aQk@t-2ScA__>)h&LXI|}lNwalbA8g0?_MYv_ z4&UWs%fcVh-ulMt^-R>tMeiHk8il?- zlmu!p!Yv|TZr=_7R+wU)4|Lv-Z>G_^&W5^I#9UA)WmytoA8Rp9ZD-nHj7dwo%w3os z7XZq=(>A6uBFE`2pL>d&9#t$0cwWS;z|=+aG~HYO$eF=Wy*BVf9z|mEkaJT;OSi`+ zT7dp@M&+6x0gxS&Wxj%jFJlyFfv}%p*UF?4NnEnTik0_pxTvH;u zes&o++kfx_6KBfrSE;h9=}>)ltMg2fG)0XRM7p~K8?Cwb`+}ftd`vE!C20_#b>)w6zn24J zAG@#ZdXaPNzFxQ?q*fBV-NLbu^87D;vMnl+J>}_evq7pN+WMZ`0k4T7k00r&&rW$a zS~RnmDn~Yoq^`zzF$f@Doye52mX#D_qJAyMp~jkew=d%yJutD8)hr)awECQH0CkANu;FWh`AXXvSo&JVZQ0s(d(zdNFwN-{_~v^=7|&*h%?(Yl!S$ zq(%L*5;!M;PSUSl5w9t59mKiJgk_+RNCY0E_vH8_95se`t@BS+nuZkibtM_aO{~`V zKUQ=aQn;!i6OgHV7oHQtd<7M#FFEw-p~=6qvzR@36#hwO#|iOxr!u*cy|*Tl-Ui#N z?et6MGLpxitV*+itjxbs?rl<^IE30Qu>yF!eD<<^o6`Z@H=_udk=-UKcsSaadas8cqT2F<$lnZyaU461n)~?i4vk<)>e)E20C^F3s zYHws^F&9OV9sk4~N%Z7|bX>)iOA?U&Sxsm=0Y{2~uJ4@0u>jTKnl94?st{U@2^gEQp;rds)Hj+j5M? zh5*JLE`b@``K5>oh=j(5N?2`65()z8y1?`4tF}Zf+NCxa0m~e#ImJH&6(L{oy*8tk!zCJRFOJCF_V0Kf2?lN0 zWhD1qyH~a39rT#Q+9ABA?ah%KifN`b!Xg}ycl1Dw2~N*R2Cs>QNkXp5L3JuhV)kum z_Lo4$BUY@E+)y(QL0QK!-?=9aqXOnR2shoE)lRRYZfk7r^}jg?uw>ji>#J6ObK6V( zdGG5t8RZx;Ji-&&S6F=dWG+lltvKFhexzmkmm7{+$m&R@MBl4#ge_n4Bfb{~V6@AS zoO_Y*lDakW8qk;vGmc~x)RZYEHyQ{dWRBY=-ywLE%&e4QkXZMTe2MYe?S=*Bkq6OQ zYnZ=M@oxue!1%d6-cF8kI_G$8&fNl?D=QE)4%C~3X2*KjlS{)6K7~?u*>|OPmc^n% zw~SIPS>yOV37d_oB0JK6jXcw=1d^jDGKfhlvz}Ef%uBY6FtmE$-$A8g_f$tUJdfJ8 zY3aN2j|}Z>H1>5M+t(kZGg`Ls* zS+sd_P-j2Bm!E(Rqf~o}M^V{ZI|GUEhxUS;-C*ZMGIe*x0DE+cse0pC^8|L2Xp)OY zrs3`->LCSK&9^o?Ib>oZJx+ZH?q&j}DjV8nPd8v0f7g5IntL2wx{ExC_<9<;#Gw(? zxVW;1Y4*_;)kXYm(e@?sf&!_(rDbYy^7k4m9#rP)TZL1_neYN0vl5Jv2w3v&GUH3d z1ptd&1cS* zxrhEX({BJ|j3mla(Va7DZJm!K0 zGTvs5B{?iwO29^t=2o;+?Eb3r@u4QyAR~h$Tx}yDb1o zqNem;$#q^qZ^u7u73S;Fog+|-HlP!s9m-A{IFjR4#>s-s*E82+*}K%Hof}36jXa0VJ^>)I2sJMZ=M%>z<0SP{ zH51=@_a{t8bl<-r-gliTgdcCs%dx0Ak)`y_Q+Q?9%d+Xkdnc-qxc}O(_@RQL{GU`% z1e$=OSg0diE`4msAcmLaax{^tA=e-a<8(Gl%ammFDskwd>JJxpvOyxFj^OytbxPek z0LJzNh4T}8e$x=kHt9B`atysrsu1gPhZM8P{R~~x{a45u zt4cA$7x-vs%+L{q?pDm-2CQwD%sBXk4Ss5$hCCE?(ljoAlNyRKF!-t4c|0r#fEqyz zWBG&gA$cs^yz@fs=aEI*i`pf>RCsm4c>c=*b~^8w3s1ZQ!lwmYu@RQnEGp-RV+IKb zYkD=z69H2s!)EkBX(GIEx0Ps1=|5W!6A#>y!mNRIjM7C?_0;{p;a3->1~}8P|Jr{b z?7XW%l8&GHe(@aF1^#mHmFHF+Ut=_|S{xJW8Bek*y9nI8zpKZ{7y|E~%jQpImccFJA{IY;DW8V~Wd5Z3jUx9EQo0_K3uk!n4d%}aLMXTYQ6fD}5abt-CbsNu0s1_( z$wqnuub}0U&}!y-lDWFy`hKo8wh8ZXQ)Q0RKBfnT6H+oei8rfDyU8nP%Dc+8i?vHA ztC9w(gg-_Lxn}NheCXK~t5#?-p)@3_Vp!ArV36WDVL|csAA~)_!Wfc*tK~D{#UuHIe?ih$0jUU-q^;{s5U-Fd3V^T`xf^{$?cWA|D_PfNNO#}K|C zV$KfYlTqmp(|le7=K8$~{m%3MR6H$>IDio*mNlKU6)~~s`I|#Yg42dxR7&U~chX9H z!tj9CV1&N-mYR;&{Asw%4!8KkMPcJ)hWM6vhe@FvcKW^9dFd~H_`sqHW@O-{05lBg z_HK-vK8G*OSHyVESQb=8eh#DFUJtL&@V5vONXl7<41EHEq7a2(E4yH`yG?VuTKU!orsg=oZFH2f^+S%Y6*q?BUtyQqy!4bxlgMb>#HB9U=f%{Lvf0lfZ55jh55V&1_=DMReBI;u^WxDrEVV@sx{}{| z3R^hs6pQ9kyb^C{tTbu5&g8l_7$pEmjxWKc;gb)&Fn$DqnJWB{YnP=X$ouR{&Iu~5 zwgvNGt55)nPhBeUrSpEkFLU>P4I?zPO05^OJ=+0%0tW4AK1^RUE39jf?#yNw)5SewgR=d zNB3q2t!UQ$BI58zD0)=evv?ti-J&e7@O4|k^3lbxxkG-}y2pbR=*6x9N75}V3p(9< zAl3%>bz$aUOw%{oZjw_S8V8zgz3*8>i8t84KCyj9IA2k;ncq#(^P{4Jc9aO?0k5K1SkzdZ?y+x&GsBQ+<9lU zpFC2vw$JqmRsLZ<&}*_4961fdOU>goU!_Y71d6Rogx^hv=d!p)n;K4JO2Y)T$Q4f;GvMS zRoLHDOa1zLP}7U&Mi~7C!F1rST^79WK=JAF;@R7$L!&cxlfoR#_Vfl*00+uXR`v<^ zgF6-J$|(YIKTc1&{byM$by;(%rFM!+#l_l;%eUS)xTbNh0A{c6Rc#SEm90jp*@*Cj zmmj8(k1&cpN7kz3LRkeMn0qU(F-o%lK)EX0^g>92f3^Oom@$dPKY@I_MAU~*$oQck zHQN6z-!3%RsjSWVs#o7dk6e5^vtSk{D!~Tzr~)hKhQ9>H3+V%|g2DbJ(MV8+KO3Lc zZpWZ*Y1rri)lwq-uA0-lUFUw}-%6XM@Tg=Pj%xBA<3c0#AKY^N+=Kfzot^?NEMV z?Vi)^G!X;7Etq%2rgnlvJ+sE%jq2T(&kp;yhEgw!%W>Qe7HHv@lsoPVD(v%Optbm# zh+Hdq=|Nnkg+ig1g88Pb?VB9Tz517)!FB`>lPcNySK<-0D++tZ8!6A^v(?Lqb;aXGSV zmvz{{FF1LMBORwth1RSGiZAdo&|W=IL~XThtg>6=Yi!TWY>nNOtz1*c+%a7wlPs#k zdIqIUYU5vSPnQ_qsHe(hAPeO`F0*+?eM01;)gCHURx8;s%R9)O$NNEv zP;g(Lzub-2tMtpK&gmHqCKbzaa7CT$W0Gr^&6et-nn4*_M_c~ilqm&DL>$pzF6DCc z`wy(kK5%`I34D}cY31ge*x`4Rm2#~22CkfiaW8)o>G|cOk)j_hpM9OI6=zcZ)bAPO z{XUtdX{aQTN1ndtUh(a|QP0W?a}Vwvce8iNHVPS~PrL0F{e53kxzrr(YmV9sx3~9x zSRUGhQIc6szG2ItE30wgH|FO^>AixHpAQQH(xoGwXym!+eErVot5y#GXAbw=)sueO z9p;BSy`d|SD+I1?zIx0*|7P&VniE4qfS5i{ui$#h`+v|XO}m8p5gQSACnfL%rp(?` zRBAElaXT90lv*}l_IIgLsz%7UO%r;Bg=|8k1x<)E_MO=ANSn}(yxAT1xQGjb0m?ccHgGzk~>{<H zkJYiJ=5vQrvwxwVSg-D8I0({?YmcrZV;2dv=%_9uikAyZmE&C3TM+vBpI6ZeF7amn zhW-U+;9sox5D`=)oaGW@&9mvb&kB86pmSeJhv>$a;zn1YsW*JGBFp@<(KIWw?vK4k z1NUe5hGi`n{95zr6G)ZS!V2DOBn8{I4s?0qP{lY)V&gV!B24s)Z=b#Y-HZWdS)war&e&kl zNH{P{AcnjLi92pg5zSV&y%@57wubFmviwx=kHrXI5B!sx3<{1#FXn46pZ~J^hO}UF z<uNcb;d@1w&u7?%{EYEfj z4!x^Foldeli2=D6o`&hxfPT9#@jvPIN;=-sKSek%=1ypDl!G6zQiHa{YCKv2JE_4Y zIn?ECzH|IiPXEd5oUby12z?fW^=G=itg5|st6BE9gN0|CYB$rk)5u?Hr+;BOAnsgJ zA8b9|-}>)=e;T(N`_&QOPMi3R3kwdT);?I`tV&cx+aY-u9J_+WfG6T-4DNKDQBltSD*Wobcwu3l~NIDmqluJJ;C3~H$bi1^$EPBXR zH%{g06OoTEHLR+`G&uNoYD@1lUP+pKF0vqz;D^@5b+v8268`j9*A@9&-;PhSt0FdY zEyYs4D22Fa(4KMBx0g6)1JmLH-LXQ(e^hWm^PhiCoa56Cr+-?f>430X%q>-P+K55A zex`GfFSVf%=`7NtgFSSbpZwTH^2V}ao<-zk66)FFocyM_oJ?=!HOAd8<<59X#tf&y zR!huT-*_3V<5S)5D@5*fZYNNjprp9_T!d-rOv>PEMXkVthh-G0Z^(M@Q zSzUjgt)z1{RMQfk`(@!e8L`-`ZAXf{s?Hd$>i_BJF&VFLoE)tCQuDkd!+uvK5@RWn zJlRj`{IOFeb5Ozm79AK-v&*UCE^pT>Bk0DmJNMq$a!%=+3k6m~#s02deb5efB6$gc zPG}mg@Qb%}$8;t%{gsNFMK{j3wR_1r6GfxHiC>~5484UQ$-O9QML=62P6^BP-!H@w z;oyJok?F}RHFW8TE_m`^49?Z!N?cWU{f4h^pQ6+c4A?|E)iXyIc}_jV>&_K;UlG^l z8EH5;E%f;?m>OpfElJW{ep>k=IYr!FY$cJp;eBpM z35Slu1pNM-54-3(RvgveSSw=5D$;*>Uv4C55IG@%g7Xn#R$~c;OovkP$kc~!vN!YM z=q+sv5aytEv*hunbn#1$Qq-XECoVE3k)vHO6g!#er%gH}C#+@HG$vgf19L^q9j6lA z?+)zOt{M1E6EO z-ogr7Pzdbd?XUV9%+dP!AY>bpgeYVJh1Vc+^InLoa7(cEuYYQQrV@rK$vS;vqPHG4 z$%u2`pVwymG|Jf7V*mWGj@12G>FM7*>5W`mRmjQ7u-7alzFinNqk^F^iY?TBjd)?s zI@jX5j-3vXE2$0oEM7fc9mj>K8vS08k6f^i z&fu$?SL`vloQz9Ei{QIZ{Y~SL_4Ej4(f(v?b&XV?zpw)+Pi*~NU>~xYy#5BjcdBO) z>*p@~UXekW@C#4lp0~1VAI@lxVeF|TmXO%N&*Iy+NS)4*Iisf9x0@@Hk>1Xv^+J5klzL&oGRd_Rbq4IEF8NYfG*+j>(%;I8~ ztz5q<#g7S4=|<-`y=eSPXfD=9R(x+7RYUVTqAxzf4%e*Q7B#S_l|xkV=iSW=7zU)H zZyD|j8NvZ|PSZ>bduxI410(lYqYCs3T(NJrqyp)yr{0QGpu<#mjx8<~ca|@W^^J!<}_hNlZ1JEai=(Xq!u$$JEKvPRtam0GK$}`)84T|VlePxEKH{O;m866>um>B+?UGWrgDD1NKTUrF!{Mq!)f8O_)f-c0Gjw-Yz$K5T@&oeiofi?u~yo5h0E;3A{0N;1lPLdL`#F%2abOO6@vlH|I8^;t(L!S&Q8P zpdWxON|h(w2$elA?OiYElySQZwP~S;2i$iX7~BCC>=h%^n1RAeid`jzFX=I$$5t?7 zvhQzH3)_C{0%eARVJ^iM^s86UH7n?4uf)gBHo?0PWl1|W9>um=fPn+#ztkXw3EfJYX2(2gYPEU51V;@u|!cBBRI z&rl&XSXTFWU+o>bPHe}b`@&5wwt4 zU>5KpB7pBj!1w5oYjk+fVky+Fw{ps4kSM*(LT+gy?J!#d@JWh<-FFOo?@%6t*X5jh zLdI6T1rNSzW5VKe_U6BiAP?=8Yvwn>6waTgzNG#Ra7Y6@b}*ce874js+u(z3AP-02 zl{4WQHHV9}hdb^oJHlVCZQAFd>82I+2;(RFervHuxU56=(*cO27^xA{A+Jn9faXV<)7vY%vPO+WQ zmHoUGi4Cz8r%qhhZXIkt1-4&}L|50MD{EaEXvbE;{|`IU37)UGvt}-I*}}?&w!sZL zKA%{{*T5>RL*DwDZL?acy^RFy0hB!kZ)PJg2&7xLzm}&wt}4^#j)KU~r10H_FH5Z% zDqSEIe&Opph+o-@Hw(PAP_yE;;(}S!Mk2IbmiOioupe>73wo~>ZyqBY8Du$^T?u0; z-(SeCboqG$5?76%;&z-9LGFp1vaH2ygLJ%tdgL?Hl_Uw}L(o!rmEaEBv4IJC<8a-W zPYr>tC6K5kv1m|GLD3lv>E(nn^1{G@_MG-X5k?MvsB*B;FeipyPr%GzR|69;2?>}{ znC3xGg6nPsd`5Cbs9Jbx+6U&0WDmSC({*0gCCfUi-*t-+S8MI%<+m(8@% zda!N1X6&{VHvIC<<*JPeZ1Wmes1X>Z7j_1vjos2VdR+_7w0k3`mdC+tj8*y}1#FGN z7Q#5Iumd0Lv+g_8^T*7lW9|CYd`KBs#KynoyW6p9`qltw;4(VZ%zXXE6j&ytl=%9Y zk@YuV=~kRTW*#$%FxivC#(!?i=493RnXMl0vu zm_v~5k)Vw7LDis9OW25Kr}~9I3UA~T=V7u+WB1-zxsQ6x!FIe?+ZhV?9XWUuu+Urv2$x$E8-_J$E9soKPVhE9X_52EcKGw&fiB zaBgicAG@Cq+scyDwEOxPzHJ2Go&fu>sO_o?bkJhwM14k}15>j*=?}*Yz@b{(^>8CW zZU&GNyQ6dB4vmy5RWnztAjr2~PhGP*;J!Qnfj>>BnJ}XSx9tGIv0XvJ`Jm#djD8B|wYW%sT}gofN*!^0|04IEQU-o^O3ssoiblc1NPe zc4BJxVu)lUx!%JoHE~t-W^;-=DyD;_57)V|>);M2#){>9{pRxGgB4QTzEml2O%k># zx#Bt}(h0x{>ab5qPG}|+z}u|etFH{(YCXKXrsp7fAWrMYNcK-(G8vw_wGX(qFP9?6 z?w8g<#jIfUyv$LiIhYkp0O0q4>jRuL+okHX8QoLO)y@39jsXlz?r`4<9Y1h5vIAe` z#-e%?TzW+gM?9#tuy5wvK70Ap%tujc?r$0~IIs6!JqVT(+jeIaALmT42WslgfIt!S zn&uS8it|9_;e5j3JjgoO#w~Dt4}MkA#taYCY)>&@Z-lIUF%qZnD((ogJSj^O^jS6{xXW)gJI_wuxkv^O1q(K5>imO zs-+0~5pZEWpvpOM8(5}yImYX-?beUf z6iE|?lQUvjxeTRnZOnp-Lr!BN+Fl?+%hma$vF`ziQufEzwQi@N}bnb@0_9y^re6ZfO zoKUn6iV|nAy?Jzw&IJUkpmgC@cZ!-UO9WLmWx(x1qe-)A2tqmLya=&VwB0w*OU1Q2 zF10&QHo|VoVK=?FT_;9f!apIvor^`+CX|4?uhzMRIYQM39Kw)!5W1;pu$eF2k5m$= zzBUnUq&fxrHNTx#8oIm>cWSZaoo<#6wd0(%>zq~~r$adl4sMyUL+px`0R47>52iK$ zzz-94ae-yDfiDNduLg}gm=38u9MM|CvAVIp4epzr!%FVj@fe0e>`STmwP}Vb)Mxk_ z5}ToYu!G&BMmmqkAr2U7w`prPz-*)~HEQ@`@71NlieANfI8NJ*#hl_p=(+`wz^-c4 zZfRh*k%uEtl7%i__&O_mon6kUb;u|I+%lLE2o*!4cdQPYtPaw&_tT)?>$wDOVNkO2 zf$Rf6R7%+Ia{)UQf-Y`t8eotRPhz8rS`GyLFl!rB`npj(ZFmKPj<*e4Ev^y2$`sh*+1 zt(_t`=BVd@#Qj~e3=+t}_j`|)sTceNRvd7W@@heH29xH^MA9r)p{!;Ft_|ostOBuc zr@i;qTX2JeG`Mt|v#|eoKk4Cg%;8W7xBJo_HEco+`Fz-^(+$G31-+eat0qQq>jp-! z>4Z3VYf6X9I8&$7$1HU1H;=&GV5wzze-gFZ%pSJNto#kQ%;Ps==LT!14#8Q)PVW=e zt`bsfIZlAGr6mP|eRxO!V_@i^&O?C2Js`NlfcYj>J0DZyD>WDAFb47jqGbANFu#dZd^|LDfCl zkF377y*%OoIDffaX3Oc9ymV zgUh|wuR`xUu_HPclV9_VwsvtmjT_#-Q8QI9op(Tu+;7dVp7sJcGd6GL+L7fq8=NPG zowCn%JA@!Pa!rE`s1(27m+@Q-q#-c1j8$y@J{~wUwd5@n@vA3?>%sQ4kn4W0ygJPa zquB$=)$wdsDWmc8!@aqf6VY#UEfj7p$J(HHV`MXXgM`U#pI}FAIt`wf^LPO~=ZjaK z%x>5M;-FoXIHPS5_?z>3jnD`Fb|W{8py_!zA5zV z%uH72t}sDZ6Be6DHf02=v9Ru6tl7~wa536b{NABtl7MVtbpE3>)x8I5{M%N47W%$` zW%w5Gn`(FsngZcu)xW%ov4stJk$r|Z7J9HB7rAwQ zz2l@|^%N_WBb43&v7!u0tJGNQCK|2)Zgdg%NU4=}>C<22Q6X>5_#tcDLm_rHz~)fH z*Ivm^?3CA8*)iU}T`Fo=adUPd;wOB1x6zm(x^$e>_1k??$L7NI@eulF1Owa!*!-wp z&+kb#1ro`xhbGhQ`-Ao)lX!Ffj}S||R`?^n5_r6(e*_oIMEfJM&>I~82rHxKIe}lN z0E%f!`uyMD>GCh84hQ(8nr z2ora)@c$w2z2m9=|M>5gLQ%$%70F)NWY@7Bn{ZM>_TFR_8Clul*qb7o5HholP1Ye> zW-=O5zt>%7NvJRXm`edKNXPWDro0JX8CnsHGo6hbkfcr$f$K(Vz_ zp-V{k+>LkqY+Wekt^2QQVC|gVx9+Dp3zt>_BpwqDb}Sxf6#;z`-8pCCP9Ol!a5BH3eC542hWB1Vgk@?;SpY9u?IYQnHHv6z zjX^7&alpD*%xBmznk3igEv)z74J!6?yY#_m{*wP?S0aS#l-kJa_s&Jv3q(|El0ZTl zF6%j>aOdYGs5NwGOp-Y@(@>Xnk*1p9&Z?K1{*l1B;F^?lgi^)Zi16>lxTDdnZLOli zm}13tUs%P=_pjS-!iEy41GtAVcxw1B9C0q3WFgK)@cAljmx3V zqVBWVy^m`B3)0ldgYU#lhCA#OwhtbgoFASDHZ%TB)K^-dWAnX0c_c#`LN=tO#ZC%oTe}FHjpJSzWUOxN@=@j6@OF%3iy zJ-QxA#JfCwiy=UCaiuqm9oK)i>- z1SXl;+x=yQ`Yg@inxAq>Qrm&dkJ|0@yN?~h=lp`0Ik7C+G+l0;@aqP_Wq>ugm*oqx z(@PoFG_Bqq8NhAzHI=eTY)m%&OZ9<*bU4Ytu~XpVv*`X%cQ)6kRcBNQlz7|n*-{o^ z_o6qQ*8j#CZHXp8ydb0gJMp4WTD=W7bl^N?s$jh>uz;6pnY|=eUkZxczpDHnffohw zzZt+qYv-~F$dnzd^RzFpMM^I-Jh2US5lJUx;ot*^ki^_GeHSC+?{Z+!#SU@{j%IWE z#tn?~s{xdNtZ3z1aH4-)GwY1E4$0Fy?3qg+qNU!%s3Gkh`V`RK&Ad*3i`!`;j!Oks zLtc&tFOR?|Zw)qGr%I0P`V1rpJID>N?g(I~R(T~h$Rr-OSm(q~Dzb4Acw>q~ETp6S zJqwMVZ`=QG(2G(EwwU}xAuBnS8vi|K9Gh!O+LyFRW3yI(^w0g2x?_j9Cf6e$Q8W3d z0Csn_)O&txR-2sxaaY(|bx14AWdapJP-fb+N#I(h$Gy=ioRN;e-HiWk{-)H6P;Vzc z#`Gb#hFL&)Q-XDagrnL7F9-gg7>WTt_U~7l#)b0oLV4yhMISrI6>iTfQgLJPd|qG0 zk_+z4q@>zRlGK(r3pw#5$HvjeOz~xZqLGF2TF=Qofoah@b056&V{piPHaJ(SK&%;J z*VmQt-fK#^lQC-j3KG>S^#M$Xyn&!be04JHZ+Uoz@D{-!2LMV8PDbGu6L!$s7s>%W&WmvRT-YP&C*kjh8}J&g&iADU)x}_;-c~c#Af}ssn>&_kb-wR z9$o_e=C%Pvcmbultr`s>s?=e0k*{Tb7#8_qo6(0oWag2-;Tt%>4bq?d8zr$gu?MSS zOeKm2%$AVh&hvyX2J;Lt+um1ZoA9k(<}+}_^;eeva?ZST%uWPgO2A6ozw{d0xYszX zh6CV=bY`3cZoXw}XTNq^F-ktyW|U$Fscc5gW!_?&PcGJ}{W&Q&#D)8GiFb(%!5Yn) zHHl#<@uiY#08})#&9a88B;o}-XhwZj!9kRz1bi5*1`$jU1_N;Cl9=#e^~*#Z7EhO) z1dg+N+b6Zf=|=T)tM~yF#`@cPky!^sma!M=7Gq{%A^oRU09#k_me`!8gL&6a0iIWW zWY(x86ik>)K`CZ*V5EUmc)^>a;T1NQl#ULta*qu0gGeVx{j-NAC_3O9E;BA*#~Bty(DRy!78;+ zV#npitmx7L=S}{|65h=aft(c}R94j1S8Ax7z<(N;!wbCOEtlJI2w;RmCtB1|BWGd- z7$ft--AI!CGZK2wWD#A|gh=P)O!wx))haDeSxe(}`h_8cR99N6s20^yPQ%^St?gYO zA6T5#19GLgHZiG2C`u|LkiJb?0&}Hj;{h>)k@22hN8jK@swk@-u1Eu zX?yFuoIN?em?U!cLul0QJy^eF!D=z{3%S;QSW?`p=FCN23y~+vF0(MOowiWXW?a?8 zpbZw&N5=bbSJ1#a^6gm~WDA$kg>2#Y0^b5u^G(LN`WEGzR<&O*xGpXA9lFAGDl*ZC zIDA+s3$a^+Y*fzQ1BK%*4wQxF&loQexA=NlWEniesj`Y^#-<2zW?Rr@^B21~9cYMU zbM3qcMU`97iL}QeQZpzjCL8PL%!Kw%z#8szfVjACss?2-8=k~k@g1a#=0uEkggfBL zv>!_3LLKsyA2G3eniNo*eDE@U_(ssa-VXKJhwS^@F*^M`-_Dd<;#bT1ul@3dXY6Ig zMMJQ2`scY}9eMrN2X$82tcsBLoyGHtez_71CW>C-jY_hw6*EuL_#FiYU$+W!AO}X7 z>%D4nJqYFZjXa61^9Kdt)Pw?ig5(CFc>MM2d8CojzoUh*g zMsyybJSx=@zNb1B-~R{Uz9PJ!&g62K4i(TP`PQV6&ha}BW9YBu2Nb`ge2RdFarOJ% z!9S(Uk)Ma+f=T~`6$jO=p-BK}!TX(h#aJ$QkG8!>u`BuyGQnoSJ31qkaJmW1yFkGV zj4peWN!1U2NZEMMekB%~dj2D&u|v)tXmTiD0);Rzfdh`+ZppaKhPC~A&LP+gc1vNc z^Q-780~Dcrc5;gfx?2_v{rr{H~L0U@nc7bCB8J-&0GrLd2#qY zq%}Svwa0Vm%67k;z-b8pZ+nl(k>pQ7(6B=Wm^KuO{`n{s5yWp>>uRkc_OND$*)^*R zdBTQ~M|^G_rI=w^{!e3Dl$TgdyRRYKtrC#)bsJA}tDvij9BW19C1e8!K({ZwnSa^9 zc_LIV@?&sCq!yZJCq zeQKK}SYb@agM93lsLr0KoD-{=@8vO@%S0$*?**uia*Q`jZZ$g>sfmou$lVeD0kKQw!M zve|LR+{Om<%MyleSc&i$=)|ra1DbRrK15%qlKS36U(Sqzr&(fXQDwF9p%0v`ipT59 z?0D%Qc0xcyzz2dttqKe4_`6ZjsgRrHBYk>u&ss(yreKsT?f7fNu<($4RG3g&u$ z9eobmjXv_e$UDJ za9NVU>LObM)d>V=fo_RaUbYN}uwv~3K32p@!t#FRXFieczcNVu_S4yOxOe5ccD^o+rBXK$7dHt`!g+MlO}&-$ve$SUXg01xLkhOyn9u^Y?yMiM z{0#9mEsYMCqDmUwZ!LW2E{MT_nY0<3Ra7>7Br7?s;MeOKEO9TEJYvJLQ=O}6mu9E&o5JDWBVp*wQbsmiqI*ZFr)8UzzTP*NMWE$KVE%3xuqw!-YY-D5K zRR}&nh_l?Lz0GX49$&=Ov(Nn1@}Z~8tXW^@XW6R#-%hXM*^;*Jj=%3yrW!Ry^8{K= z1}yY;C_-lrWZ_2nV&V9~x-G^lG{$e@sV`aMq5D;dzV)%5=II=k`Hjk9{*ApZ&d-8I_cOOv(23USJzB0;G(-m*XwYCp)hI zQpPe$e>?r%2(OVJaE!Q6m8&I|B2jBrMIQe7$JJ?g#eyWJE;0D}TVgcgB;|8?KF;On zzt5T-EG_fghAbt@u~adK$AwX1d`^TbM~Vv^?>D@I-XGf;YDMuLLBahkGNavVKZ}*f8K09)Fex55b%b>TNwN5IeP?TBF>GYYMB@`{JFm-U7%;Jk z=3KBPJerg7R!$RqePT_ZN-l z3KlVqfz9xQub62Wpymnjj zabEP2uHxAna8}h6dMF%f3HUt@PA(gIWtgq2dE5rozln9zOj0Ha`6}>zO|SgDUw-hk z=&bj=_a#_&AAjP*tMVs0Gg(mkM%WC0BM7RH%@Ti7GBUXkwP`U~{D8KkJgh;Q0N5fZ zdfio35UsKBSw=5zQGG(`EreyPNCXA$phIEwBC~$Vkq!Kqxq9M2FS^aIwS4Nk7Z)>vt3hQg4%fZYVATfzXB=n0&ZY}IOCku*zeu?J1y-#VZ zVeTJlU^Z?3D@XM&c9m3HMr=kaaD89;gh2T=E{p7-c`lh|03w?0=!x>d{rv1TC)LF1 z?UQFlYFy{+tf@_vbcN=>RvzRrmW3VZ%C;H&=Q;Gpm=pDi#c1f0YXo zN)z<=>H8*6)=>(%MPKA}8s5@H4|ZJTBv6oX8Q|nQ^5t{*Cu&Y}zRwIte{P_a@z{*K z#Xv{xsud4h;E(F-_K=DdH%N$Kchj=G`QMBf`)*JPmo9+W3V`jsl<>{_Fe-`hZH|iD zBPLxtxrXBq#Ph_}vR!+bM7uxcolLTQLU)^V;JLU{;KQ;c$W?DyMh_Whdh(RfmED#9 z<_wJQZtjs3O@>M}H}j0$6%xUix{?RbJK)zNt+8FB|MpD4F;RgIIksfQ-G)#8g(eBI5*JCSW z#YvERsM+6aG;UVq^l6mDgMw@9%!y={?!h|{Y*|*X(ZY##nO&F`Y5LMDk%n8(-glbs z@i%kf5TYIA)O|Qay;HtNNGMu9?N(ry0mfXv7xlG4x-4}1w@!5R({rlj0KMT~b zZ_e~{rURFHmNFSH04N5PVESW*3UoGT%yqe8fq8v756;Jz?w*((?*y@@w7hw9%rR=d z?>nmbig;c*v4~#d&GfAh%-he3BUJ?-=&%Q1|7wZ;-#qGzkzMcm+nUb#03S7|<7sPN zrhBWIDy3+6M4X5*Z672X3em&aEjoVz@A*?pRe=bnFGYEbT-Dy7YsF z-BmL}E5dSM4}PD?qjmyfJm-Np*Yhr3ul!&j!riEUeSD*7x`pM6d z3qY8>HBI)R+h2V+!36RB5;I5wh_l8HX|yVndMj6R3DBLlT7J@fXunfoeHx4&lkMX>7)jFN`bTK-v22RRY{bw76hWAlJ;@U&Zl2m zVD-Eo6`;_tAeQ~63AG-8a)k3LSO+&QgQ5xhyzvNZ-w;%`fb!tr%QOwO1R&Dx=|~BV z3gd}W7Xn4g#SNsWX?83fRs;`_8ZLs}n9_T9iAC1dFZv3Nk`>D^PxhYX)}Oj2VK`Sd z(sZd6XK#+&UjTFe3Uu&^5wYkCg=Bs{GW>~T6Ol(i|EN^4w6uzM%7VZor-vgFLs2<{hL6Mwh|l5A!j3BjU_)9 zkTdRe@S3EJeUXwIcr0dQ+WnHvENYLIVsP1-G!xx-4xs+zn99(N|J|w1&keEpDso2} znfTopg`|hQ==5*E+l8?HLh{M+j>y}x3@TuhYwU5fmNiPOzg~~n)DPgltr=DH)}en4 zWEVFk&Zw1bTz%~43e z+G!GL&{>qAR4Gf~6WH7w0gG+;3Wc55-@%h_ddix?%Qb3VJv zOIYF2X7uJ)pHZ>uuDY&TNJ=S|wFUrug&@G!jqI-4F6L$T;nPH%#xU=*)`?NuzTyY) zwimAlG_XB%N7CmLTIuK&uM>ItYPN~g&xgX3w8+hk1>s*CNLfZ96E(C3=1kyf?Jklq zNz%~f#>tgQMkQ&6rLuQm&@+#ESS+2VAIFhDyZM^nYnj(eR4Fp2KTac29#H2@8czFX z3oPHxb5FTYb^iTzs@mRQ$V@%5&|MI!YgZZ>^&4+me?=5GN%2Z)t1>bfa8w;1O?7&H z?9~cVJx@FuTsKsD(r6Q;A6vtzdB@Q5pp+TG!zO?ksGT=^CKsL&cbR5EV^v}pu&LP|Nf1-A%_~g1%S7ML3$Ad8I|sfWsLZ5B# zneQ&h>9aK90MG9=4(x+b9&gD>Ke!ri4NY{#60Uybmfk@zZ9es>1gA=+{T+%<{P`m# zg~Qm@I?uHX36JnS>)Q~uhs^!K!#L>$EYyd)Mz@Dk)_< zz-*L=XgtaXWRuL~TRI#SZwLMQVkJ`+2(rYGpRI)%Q2gr1J(^HCOkNv^2>VH^Ubhqq z*J>0|z~s~6I9Q=}@igps9D@wt_2p_@L%tLKAP2*eX{|r=9jf-oE7$`T3s6^j(=}yQZS*ct`fygy9>uG z3iVhYBG6K`2LIu#9v*V>53q)4{n)c;op3U)=qfD>asJl*u?`x|hr z{-v>kTb})0RW;!F&}ltJE4doD9BF+?gC5=8hTs@w>(b|f{~_n?=W1x>t5@uZG9Am7NkN@%i0X$xgTV34qNiaw_;L>7u6zv*FqtIp66e*P|m3S1w%7_Rjm>F z^kU=J2N8WSo}xF*#058+9Y$DGUp#2;@5M#&bocAp1gu_-_@xM{IJOds_xMl_g|T~E zg!5Alr8~6RxcUyG*fb6Qxg6SY5n793eVrKv=br@`a=3R**@V=5I{GPjAT^Xejmorz zKfqpGpbn<`>TxTclcIK4?33@`i5QuvEH<=1G1@k2f)_}kEM-gM>me3~Qyai@TP7mp zx}($hwNo{zaslR14Ddz#ab#<5BGJ1G&g^X&(h#o|_bD=G_h@J6paZL?yYWsNX zE1XvF0?lHNQq4Sie(M>P1VW~^c60k6euHUlt^Fmo$^z>`A(k$Db>l`{p0i@@mMi2r zxd}xlsuVx&1+_^R)++m$1)tzkb|33!heS_}xyH>$ymXys?dCW8UF_DBUQ0IXWo?Ok%aVK-M zcPzgQ=wtu{;RvC2wD_T-3~NQguAnu_xb`KU}X#InLfO+Wqb2& zLO`5c8DJ|aE*tw3VBowLE^{GBf05G{V}r07u9#(7&tO-&zM*AF`=|y<#sb|gc+$jw z*)ecBZ!9XB7>|__T}o{~XB$_EY_sB(^HT1!+#cJLd@~%up_WR>9xr6wL<*9D^{FqD zYjTWMXz+CPTgsZr=C3ZJkfyoUDx0f_`+2uYQ%k)P(`LHoOsDWJhdP)^hs0iA1GSnU zEh1ryIEe{S+W6vzH%StoStZN!co_J)%a*`TRch}$OPEg3hvYUDH!QzyEMmLEu@o{& z8<(y=z0!_Ta7C)h$blzJRx8Sc>k=-7UhrMhCmu?yWu##z%*V$vni;?tx%WeNX;QS+ z<7ynMdC4gcdKxryzQ9zyYr@w*qFkWP7b*OQ<&TQWRnHPv9Y(%#y8~4Y{?QhYejzl9_k?$||P{bg}(B z!2~N+kd}Yv(TRW2G$9>Qv(WdW|9;`E3BhWU6MI=$CdOK2zh)&EJ{FLS!YfKQ1LF#v zkjf>gPLV@F6hRZD64M{hKki!0!ScVtEgn7m?{JH>)GlL_4uW6#VF;5Bus8$xVPjLr zPi6b-`O~BFi5-oJJP@zevq$;VE-Ci&QPk>sejUh#Sp>cKO!m5QL(*tj;#|lP;6k%6 zlhD|Vm~tRfZp6WJQBo2_S7wJZrWM)ac4GbaU z{yU+j-+IV7>6Sxk?7}FX{`;%?hQf)zc0PqU;n11gxfuk~p-#xuIu$)Xm@}T9 z2Y8?oWha5)A5`e>79_Y;iG=dPF+m4V3v{qhXAhOCNfbQA<(Zs3CzF+8=yP^V{_(i4 z9ru2mh`z&mIyL0DrT`!quF^i7%yr0G_dF3WB?xSiZA9_jo>bIbg`v2 zp{BOYE)%Qa4>0Sb|CHYH0DCQLb#^t|zrLsIFS>>0lY@ZZLW+mI(N_0!{;Q5@Ric?i zmc`m%NmXH~Qj;Yc6+Rvt?{k>;MIS^%q@gTJYjw@+P{st|E#xSuFhcs<@}AkBFF=vN z9sLXbF5HCJPim-7v*4L&MnN14{>-ao1zo<#SJW+4T;2Mzitu)0ug9NC51538s4#AQjUKo?i;4Wii6@(KUc>@SBP$L#zrfSd@iNfQDXSFu&*o>MyufEJAm} zKVM|xUXvm>y=A8jiPxaxdE8X~7C}5r$ZgrTUe590wVf{3LINT+Jl<(#ZJ2uG7I!uN zJ^a`2&(%zSRj8!YAkj-%giX}wA~VPU_B2_B!hh#`PBk<9Nqd9{i3g#$U(gNWU36c$ zq2}lp5?vebAF9~kAAw#jwEj{J)D)-)eU-#B5@}Qv>hTds<0pxzQ>g4O#k1>`8P`M4 z?9;A;Zf4)zTSxLA|12bL0>!J;?2lsW*s_=6k$6T#JXy8&f3R>mnXjsXB>Y>F?_RoF z*S@m*yd8&33VhLh=ZCuX{hLz&15&&683F0|e-*D$4&wi^{kY%0`LurLtj!g-xkzQv zh%zGSo1_Fv zcjr%~sEW5zG>WCfI70gRgcqw5d}xe`7S*=L_ylk>LRbbv^GlnK6{OPzsHOCdtR)wH z4AEE(Yg^Ci?L>n(KdEX3c|*SOdxw%AWR$$KaiuBgv;toHVIjM(jTIs%(Ej_D=PiEJ zO=B9J)O^Q&ZdHe3DQMq4e3x)dwCEue=coI65ZSZy)Ki$X=JjybX$*#!@@nRxxT}^F z$U~ccRk7R+bFXq`aaa%zT>$Z67Y~pZjF&yO-|h96zY}tV6S4S4PpOMWioKNh>3*JKbGA#VU`rKotus258^qz4EZ|3Bf=WH%?v?J1 z9Xx6!;^}2icby!-ZY$zy6d@Ab8h7niuCHvkI!@)1UH4!(wAcz#&);8t!(u4V{`=0P z5CUFU2HLs|ygn*!aZ3O+RuUJ6lKWef+Nq#xBwgPWi4kXHSD1p><1%3x7* z-k1U&4{-vL(uxy!UV&G5n>Q9PKk)?T&|S!(jm|%1?(R_3&cYU z(c2>BQq#f2{g`|u&&S?oF7=s#ofQN2E9*JP4s{eD9h3%ehCy1W83hbqX`jVgHtz+f zrbR&bhR?f~vP)n&$IUikD-IOt_k)yNh?XjA%pd^yijCcGdA*I9I@{PL=6Zi$g~%C0 ztB}&|JI05r5N0BsI{S#CP{(wZpBw&MHXKc@&n1RE9d*JwfP2wY!0CH{y@>0Z7JgW~ z%F2m3-Xpx32r6Lzsmi~=7vm_}2N^oDqEZI2oZ)Nl=>IkpKepv0Z)g-_JwTz= zw)ewD&!S_>b@getn#G^R8=wpOx;nmgV1`)L9qUwVB6k)@ z@cvGgRicnsZF~@WyaS=F)%2^S!W^=Py@&McT#?nfy=0{_oKiWBVK;5vizWux6x#?F z`26sSS~#e!gMjEnjiJJ~?S=j~#4n9g37xjqbayAa@I_$^=+1GBBLuZ}*p)q9$W z3yS?1wy%q5MXah%M9wfW74t)u8}%7X>B332sL!dxJu}m_*BfN){E8%~uMW{&BaTMo zC`}H_Db8<=)fcS|<|_e6b-ziX09|{rZNr#W9Tf`XWt%+|B zU>0q88ecd=>kN13EPVxv7Z_G4Odk6ulca5u1jJrq2+>B(siv%9d@At*mV}wpjrdho2Am6y6)!TDI*~s zziu*&uGr|$RVQ@buLiqoVnAdOzPP95Qu+``oov&cLDm-V`TcjNuCOS6cztuem8SQH#t-j9*H~6QBnB-%7(f~ay#Q1x6|{TQ zoas&sVTe-$;|X@^q3y&4>4W2&KGgDq)H!zeY{oSW=Qy`W2a4U%Np{!q+@bLHspnN2 zS+x_q!$2-pd&_MtyTZc1Z2bKJWgVBW2FslsM}}MdBos6nti)x#U5vueWlUt~a$Wmf z{bXE&O|iaUpL`Y4yvEQs1Q~n+ayoJ5f<|K**5@b3@8)WH17|eYCXDH%^fgVM`!^&; z3jSG4$Z@kRwq4rz!wMKaCzg5g{`Oz>@7}qYTXs>_2&M8k+-?7G@2(1=b<`4;gX9N9 z8tr!lO&4`z2#dj}RaMSGr^ArkKbKZW+(gyqd-tPnpYOvbSucLjz&ej#Vri2=6AQFT z3;asURk0VxGka!Mqex1WLO_dF6Egczt(KVK8~$HI?q(bUp?qtQj!gG3H9jZe5)(1ugCLNeEWju1m}TA!yRa5m(p_cLUA1k_KVR&x2tW zU^#HxOjdk2+<+phLB4MNr3GA&!-i#on?qf7i7wg*|PZ7)m%&t1sxMZc>jxguKoTpmH7;F zx9^-7tnE=rK8n2MJr8bW&_ys+DT{LRJtymyUG|F3BB|WAU~7DHGf@J~>$ZkMdfXmx zNrQd0gfMRdjiTiSSIagymEz_m6JT#SoDyaVUaGmI#k9(8Dt(K(cNM8c_@K5Rgdo~+C#jZiw+vAG=6u0R*l5mnOJ4&wn<1ojo_W*1+ zDLO)Wu6XQtY~KhaL_h)qJ#mvZhJGlgVIE>L_*{1RI}w>- z(d%#wicD)6tjnKjvTpP-qWx7gxAM7!ZAMl1*k5mBhg02Dx`jYhaMGr6aIpfW?>Vk# zWHc2dL}3nrkqh@3lO@g(xWSDjI`G?f8`n4waL5*|f)xF$*5NQ~;?#VJa_uJU*Xr!qTRE?o^L161Pe&= zOei5z1zQBy%9{*h5F1H^fRe^G+Icd+NcVwXehQbcb+PXl~0V~d<3f#^k zr}9dZKbdE1I(rA|2~{Wf=gA$?@!YxQ%5Vb?46%jPQ!r2-v}K-(0Utg>-JZHovCaP7 zO{+#Wq+)slJ8W`uP?3~EY`W%6cYc+cfPQQ~!wrU4R93VUY77zY&M$oLnn}bn za~4>tk;?A@%%4UEXVR~9#JhXmcZmnrFnA}GJIs01g5*g2Wj_J>rvPLSfK0$(5qeNJ zo2xmmUx;0Blo6w2IjovvJWmSZ3}YxyqM;M0xi@Ovh)a|-pewB4yylSLxe*-)oB{2V z)Oy}VIIv;T4MHlrogHbvn85Hxi+A8cz?-+lq+5Q8HbA1I3&w$hln`U^Im|ty$iZY(vzPMPJVuvGVP{9+|a321&5Np!TwdP zRDBd_`lU{*=X-U{tCF4Ko#y~K*(SU=vuyFcpKMgQ-uMYmH31rZ$>VYWj1i|F>{F4v z>qxq0?4&}`6Y`UdL9gh1Y>F#UURGy9M#e6i%G_82g~1!E#1^_M-u+Hmi8$nq-@5Vf zh`ag$fnRZ&xIbk2N)$?yM`3q8-i2VRkQ8QxLp0+c;9fL@3J#?=fR&RlV$tu?`5Nz-YTOFWdbGxum4om{2=>zpFk#|rHk$%8 ziUX(*gQ)SJY9oO&C@?_5>u^SBX;xa-2ISsv*PzRmPWdis*geM3_}u{T0|`(XAEV{X zD&a!}htaB-yrk;yL{-Lv4oaCDCFthww62AoO_Hf()VmbIq6 zeO_MHXyH*QUS)Pzij$oZccgC9PP~%!ltK5IT;iC zRmqqayNnLa8D5jlo;rtA2!L;q)b~<;Nz`02^2oh?A6(f7r~QLMo0uf-tm5(9(r;{1 z{b3Yh!rwX{*^sl)E7E#8a8km7?#1nchQvQ;jbzRrmhO+#-#6IZ1M!W6UI4*-L!m_J z#xG!9thm4EvNeh1NpFOQ%8JS62i??n886`@Y3HAs6DGPTxq-PfpYR?GK-Pr2p4Pvr zx@G-P@%a#|g6QY8J^aq_b6s8;GQOxe5g2#u-qzkc!W>n_cJnp8J+@`_2jt*W&=qQQJWH*nm_<&jvVgoDC7nApyuV z8X-EN|4^te^@7LQVA%N$&ZwEBd})3LnvwAJEMr@6>=t9A@S2-WXlM0tK8fWFllJB4 zg{#pzKNzMW&IdvW;c+Jxv?yK0UAy^+`ig!Vf3|gel;uO1;yth$0k#A|&kZp7+9pR> zMvH!@(5~qJtYWLL9bUwcSCme&CKkp#kod_PLSuw3p>H^}&Re{m``&iCyqe;)nFDW= zL4heDu9X1SJdh?u;YJuLa-)0IuN%;2r%Im(Musbod~GiOzb`mywrwZsDw2$f@#%Ja zL|sJn6)8 zaqR^WRN8m@^VU!p`hh+(`Bvr8VEa`{yZ0y{k~n@k8+X|)yCepbRGO)TM2sb9e+y-M z6TrGdI=mmHH5Dko!(*tN{>I9A{mT79U*2A1!y#HHSV>Ay8ebiLZ?)X-6s@r8*2RnA z>4+b%JPwXLvZUT+8R{|bZTujD#|Ay1563GM*;U3Kg}I_|y?f5O3w%C)Lyo9dk~$5> z-D86s8oZyC_OHMUQe&Rh5i;kc{3u)m!||Oq_0oZ5f-`I zS^E?&^DRDqJ-f^CQHs-)eOzX(`R1GN45uLFQH1$ee-VDw7DY;W0~W`_;Xo4=;^ zS!*^5li{qM_I->?b`#Pcx!N%X>568uuh%@`6PUjGrix0f4SKXZ4lqqR5FG(@ELShh z$L8j`@+L_IiN>{5D zF}hh9)svC1a|2&_%kpI{Mcu}o9NFQ|t>x#JE>O>neo|^a(v1f5wVIdlwJukK8o^a^ zGgOS&jkT!ftkR1U+GCDwJ z#LlPvPRt0Yp!>=j#_Zfts#pR)}i}cqfnX(`*!DJrEwbDlF5W&HZV1 zmW(7ith8Bfe8>UL+(odHcX^bh;~Q3UFhf678~G>zwb8U^XrjdkzhT_p8U|4si6|8Z zedZH1swoudhBucrpt(F7-%BY*#7|;|=3HyPFK@s95xTcz@Z$`>;a#2S;fT6U_$vwA zdgN!-0a2!hW5kQRjy(2hc557-8PN?nagB4IMwv02^3MI;ILV50ux0H+s*_0Kpx$Y?yAJ8{bj(j2)$sygn#urH3}0-O7aif2tKOTov@c@Y2bfC zuHZY8>F8ET{bQqWD86x@|3@M<Gj`lWxs6uPb6?C0z!+NVbjv*$M^Qwp&`&0c| z#cS`CLdHzUs{`VPfU)60AH~$;Qp95@x4%NTT>=f&IZYepcK601;0`=Z|FA?T>k%JcST5GDoYOy*It zX`Y8JOqEm@SfW*;;5zQrfJ#_SCbz~Bfd**>jR3uxj(L`E?Ct-H@EN0J7qyrkH7L4L}9VK4$B{vD{U4wnUjI7EN3PQ|=JYFB# z2=1DyX0sa^H@vw-aspOooSVRTwuZj7^z(R9;kSE1~*W9Yg3f~_n9R5x#C!RkW zb9rxFLms2ykkQ4~J?a-0_THZKS~q8fN}&XQCbEPFUNInY4jUHaVnorMox{MJdNFUG zz66qAX^(d6mg&R{S=v|wli}X=Q8W33t!SUInJ=~Wg`Vjktm!Z=V@6{)G^}WwK760Q zb7o!p1rQ%Y>Li1%bwa?-Jn|=*Dq-qVT*qP*adC>Xl%`9=3Qch9PnW`@N9!-xBU--G z3u+c_Siij|R!);E? zm*n4xi`JwZgzkf;=6ec)yo3EZXF!+9qu9j}DbMfU#rB!5w7?=U-MW!&SMjbuDrZjdtTyRM*UAa;2GYSd4e zvVWe_HnAZz@%U)Cah_$1{`Fjy@DcU|LL$Q*HdAZS|D<@ZbYLA+WJK4-s)jhD^EeM! zi+*272s=u_^)r!_HJ%ONy;fXaHSWl%V5w?t^;-x;~aE-5w+HkSfXR z-7)l9>YHDpq-SL-S%23gamp>DCA#Rw(t9cyoOqfZThowZhsV*MGrXI7xWaKjwD{;n z@Tv*e`RcryC5TbEpO?4BGcPInxGucw)yIPFQxR0oPLQ_mmnw;Q-Sy>@{)N~k{aV7S z$tezQ3=%0HKSl7Ll>to6%!B-^L(TrGW@;u9Za?VFF-aNH_oi z`MBJd))EJzq8K92tr%b4N^~b+x{iITLGLMS`U9VdP%QA7NZ}W?GXaW6{>Cq+@ z+=h_PRcJKaxrdNN$By|j?=-LG9ZQIg_hFI1HqoEqa9pWjIb+df++^m z4MFV%taQ`wl~3uAww%Fhf8s~SCM$pXA+w`^-vZbx&3YOL)(4E5ODOv5@39gRK&zQMuA`@Bi-BD4F`?bw~93QsWt&3IC;ND z6y~H$zpsLNWoCSfgdvJyV*c6|u}k`r(boOX2Wej=H03gVyhbL?R5zF}cUii2$Ix@X z_Vr}UGQ3V}w$1!MCDfn!LyHe7^g1%e z_Gnw_mw1R=n;ZBKf($n`A$xl$7};#MoGF<};x#tY)TUW>kciGt7h}nhKdBn_6%fjI za}5}A+nOe}Dl}usC(?P?p{;)>bLSNfYY92)qL?q0$k-zGqrNH}p9=>rKzp&jJmt