From eef9aa63bb1e15092f3283f00d01c96a8962e01d Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 16:49:17 +0200 Subject: [PATCH 01/11] README.md updated --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ca2a1b..eacb743 100644 --- a/README.md +++ b/README.md @@ -126,9 +126,11 @@ 1. 🅿️ Optimize `OneSizeArrayPool` 1. 🅿️ Review locks 1. 🅿️ Measure multi-threading performance + 1. 🅿️ Implement memory bucket disposal in high RAM pressure scenario 1. 🅿️ Review `RecyclableArrayPool` 1. 🅿️ Review locks 1. 🅿️ Measure multi-threading performance + 1. 🅿️ Implement array disposal in high RAM pressure scenario 1. 🅿️ Optimize `MemoryBucket` 1. 🅿️ Convert to `struct`, if possible 1. 🅿️ Find out if there are better replacements @@ -137,8 +139,11 @@ 1. 🅿️ `IndexOfSynchronizationContext` 1. 🅿️ `IndexOfSynchronizationContextPool` 1. 🅿️ `ManualResetEventSlimmer` + 1. 🅿️ Multi-threading benchmarks 1. 🅿️ `ManualResetEventSlimmerPool` + 1. 🅿️ Multi-threading benchmarks 1. 🅿️ `SpinLockSlimmer` + 1. 🅿️ Multi-threading benchmarks 1. 🅿️ Release 0.0.9-beta 1. 🅿️ Cleanup 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field @@ -174,7 +179,7 @@ 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` 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. +* ⚠️ `IndexOf` will automatically switch to parallel search mode. Collections bigger than (currently) 850_000 items will be scanned using parallelization. In 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. From 95aed6803d14e8b1ab311521079cc5fe94ebd8e6 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 21:41:10 +0200 Subject: [PATCH 02/11] README.md updated. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index eacb743..7a999c1 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,17 @@ 1. 🅿️ `ListExtensions` 1. 🅿️ `MathUtils` 1. 🅿️ `SystemRandomNumberGenerator` +1. 🅿️ Review and remove warnings & hints + 1. 🅿️ Warnings + 1. 🅿️ Hints +1. Documentation + 1. 🅿️ Document differences in behavior + 1. 🅿️ Document other specifics 1. 🅿️ Release 1.0.0 +1. 🅿️ Optimize + 1. 🅿️ `RecyclableLongList.Resize` + 1. 🅿️ Check if we can benefit from Sse2 in `.IndexOf`/`.Contains` methods as given in [MS blog](https://devblogs.microsoft.com/dotnet/hardware-intrinsics-in-net-core/). +1. 🅿️ Release 2.0.0 # Characteristics of the classes From b0a61e5aae2843ede8fc5e2010358f263dc2cbf1 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 21:53:19 +0200 Subject: [PATCH 03/11] README.md updated. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7a999c1..ebdab47 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,9 @@ 1. 🅿️ Make type castings `checked` 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableList` 1. 🅿️ Release 0.0.3 +1. 🅿️ Implement `ILongList` interface + 1. 🅿️ `RecyclableList` + 1. 🅿️ `RecyclableLongList` 1. 🅿️ Implement `RecyclableQeueue` 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableQueue` 1. 🅿️ Release 0.0.4 From 1f00a431fba50bbd3e25ef57404fbc70148b3fdd Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 21:55:40 +0200 Subject: [PATCH 04/11] Added support for ReadOnlySpan --- Recyclable.Collections/RecyclableList.cs | 36 +++ Recyclable.Collections/RecyclableLongList.cs | 227 +++++++++++++++++++ 2 files changed, 263 insertions(+) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 19937d3..bbc85bf 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -12,6 +12,8 @@ namespace Recyclable.Collections protected T[] _memoryBlock; + public static explicit operator ReadOnlySpan(RecyclableList source) => new(source._memoryBlock, 0, source.Count); + private static void ThrowArgumentOutOfRangeException(in string message) { throw new ArgumentOutOfRangeException(message, (Exception?)null); @@ -92,6 +94,20 @@ namespace Recyclable.Collections AddRange(source); } +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableList(RecyclableLongList source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableList(ReadOnlySpan source) +#pragma warning restore CS8618 + { + AddRange(source); + } + #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added public RecyclableList(in T[] source) #pragma warning restore CS8618 @@ -188,6 +204,26 @@ namespace Recyclable.Collections _count = targetCapacity; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(ReadOnlySpan itemsSpan) + { + if (itemsSpan.Length == 0) + { + return; + } + + int sourceItemsCount = itemsSpan.Length; + int targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + var targetSpan = new Span(_memoryBlock, _count, sourceItemsCount); + itemsSpan.CopyTo(targetSpan); + _count = targetCapacity; + } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public void AddRange(List items) { diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index e1ac32c..9394aa9 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -417,6 +417,186 @@ namespace Recyclable.Collections } } +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongList(in T[] 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); + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongList(ReadOnlySpan 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); + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongList(RecyclableList 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); + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongList(RecyclableLongList 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); + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongList(List 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); + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongList(IList 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); + } + #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling public RecyclableLongList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) #pragma warning restore CS8618 @@ -576,6 +756,53 @@ namespace Recyclable.Collections _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } + public void AddRange(ReadOnlySpan itemsSpan) + { + if (itemsSpan.Length == 0) + { + return; + } + + long targetCapacity = _longCount + itemsSpan.Length; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (itemsSpan.Length >= targetBlockArraySpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + 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; + _lastBlockWithData = targetBlockIndex; + } + public void AddRange(RecyclableLongList items) { if (items.LongCount == 0) From 15614de8a43b78722e0d0f2cd3ffb0171ad31aa2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 22:02:55 +0200 Subject: [PATCH 05/11] BaselineBenchmarkType / BenchmarkType renamed to BaseDataType / DataType --- .../RecyclableCollections.Benchmarks.cs | 8 ++++---- .../POC/BoolOrComparePoc.Benchmarks.cs | 2 +- .../POC/DelegateVsComparerPoc.Benchmarks.cs | 4 ++-- .../POC/PocBenchmarkBase.cs | 20 +++++++++---------- .../POC/RecyclableLongListPoc.Benchmarks.cs | 6 +++--- .../POC/RoundBenchmarks.cs | 4 ++-- .../POC/SpanVsArrayBenchmarks.cs | 4 ++-- .../POC/WhenParallelSearchBenchmarks.cs | 6 +++--- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index b741a94..c8560c2 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -17,8 +17,8 @@ namespace Recyclable.Collections.Benchmarks public RecyclableCollectionsBenchmarks() : base() { TestObjectCount = 1; - BaselineBenchmarkType = RecyclableCollectionsBenchmarkSource.List; - BenchmarkType = RecyclableCollectionsBenchmarkSource.RecyclableLongList; + BaseDataType = RecyclableCollectionsBenchmarkSource.List; + DataType = RecyclableCollectionsBenchmarkSource.RecyclableLongList; } [Params @@ -54,7 +54,7 @@ namespace Recyclable.Collections.Benchmarks ( RecyclableCollectionsBenchmarkSource.List )] - public override RecyclableCollectionsBenchmarkSource BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + public override RecyclableCollectionsBenchmarkSource BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; } [Params ( @@ -64,7 +64,7 @@ namespace Recyclable.Collections.Benchmarks RecyclableCollectionsBenchmarkSource.RecyclableList, RecyclableCollectionsBenchmarkSource.RecyclableLongList )] - public override RecyclableCollectionsBenchmarkSource BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + public override RecyclableCollectionsBenchmarkSource DataType { get => base.DataType; set => base.DataType = value; } protected override Action? GetTestMethod(RecyclableCollectionsBenchmarkSource benchmarkType) => (Action?) GetType().GetMethod($"{benchmarkType}_{TestCase}", BindingFlags.Instance | BindingFlags.Public)?.CreateDelegate(typeof(Action), this) diff --git a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs index ea60943..755b2a0 100644 --- a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs @@ -12,7 +12,7 @@ namespace Recyclable.Collections.Benchmarks.POC public class BoolOrComparePocBenchmarks : PocBenchmarkBase { [Params(BoolOrComparePocBenchmarkType.Bool, BoolOrComparePocBenchmarkType.Compare)] - public override BoolOrComparePocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + public override BoolOrComparePocBenchmarkType DataType { get => base.DataType; set => base.DataType = value; } [Params(8)] public override int TestObjectCount { get => base.TestObjectCount; set => base.TestObjectCount = value; } diff --git a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs index c926998..11a2a5f 100644 --- a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs @@ -38,10 +38,10 @@ namespace Recyclable.Collections.Benchmarks.POC DelegateVsComparerPocBenchmarkType.ListOfUnboxedStructs, DelegateVsComparerPocBenchmarkType.StaticFunc )] - public override DelegateVsComparerPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + public override DelegateVsComparerPocBenchmarkType DataType { get => base.DataType; set => base.DataType = value; } [Params(DelegateVsComparerPocBenchmarkType.Comparer)] - public override DelegateVsComparerPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + public override DelegateVsComparerPocBenchmarkType BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; } #region Dummy classes diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 03805e5..5e8f2f3 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -6,8 +6,8 @@ namespace Recyclable.Collections.Benchmarks.POC { public abstract class PocBenchmarkBase { - public virtual TBenchmarkType? BenchmarkType { get; set; } - public virtual TBenchmarkType? BaselineBenchmarkType { get; set; } + public virtual TBenchmarkType? DataType { get; set; } + public virtual TBenchmarkType? BaseDataType { get; set; } private Action? _testMethod; protected Action TestMethod @@ -96,23 +96,23 @@ namespace Recyclable.Collections.Benchmarks.POC [GlobalSetup] public virtual void Setup() { - Console.WriteLine($"******* SETTING UP TEST CASE FOR BENCHMARK {{{BenchmarkType}}} *******"); + Console.WriteLine($"******* SETTING UP TEST CASE FOR BENCHMARK {{{DataType}}} *******"); - if (!EqualityComparer.Default.Equals(BaselineBenchmarkType, default)) + if (!EqualityComparer.Default.Equals(BaseDataType, default)) { Console.WriteLine("******* SETTING UP BASELINE DATA *******"); - PrepareData(BaselineBenchmarkType); - _baselineMethod = GetTestMethod(BaselineBenchmarkType); + PrepareData(BaseDataType); + _baselineMethod = GetTestMethod(BaseDataType); } //~ 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)) + if (!EqualityComparer.Default.Equals(BaseDataType, DataType)) { Console.WriteLine("******* SETTING UP TEST CASE DATA *******"); - //~ If BaselineBenchmarkType == null, then we'll come here only if BaselineBenchmarkType != null. - PrepareData(BenchmarkType!); - _testMethod = GetTestMethod(BenchmarkType); + //~ If BaseDataType == null, then we'll come here only if DataType != null. + PrepareData(DataType!); + _testMethod = GetTestMethod(DataType); } Console.WriteLine("******* DATA PREPARED *******"); diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs index 0a067a8..a7fb2b6 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs @@ -23,11 +23,11 @@ namespace Recyclable.Collections.Benchmarks.POC public partial class RecyclableLongListPocBenchmarks : PocBenchmarkBase { [Params(RecyclableLongListPocBenchmarkType.IndexOf)] - public override RecyclableLongListPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + public override RecyclableLongListPocBenchmarkType DataType { get => base.DataType; set => base.DataType = value; } // [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.RecyclableList, BenchmarkType.RecyclableLongList)] [Params(RecyclableLongListPocBenchmarkType.IndexOf_PooledList)] - public override RecyclableLongListPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + public override RecyclableLongListPocBenchmarkType BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; } private long[]? _testArray; public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); @@ -131,7 +131,7 @@ namespace Recyclable.Collections.Benchmarks.POC { base.PrepareData(benchmarkType); - switch (BenchmarkType) + switch (DataType) { case RecyclableLongListPocBenchmarkType.IndexOfV1: _testRecyclableLongListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); diff --git a/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs index d3b8223..1aa35b4 100644 --- a/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs @@ -12,10 +12,10 @@ namespace Recyclable.Collections.Benchmarks.POC public class RoundBenchmarks : PocBenchmarkBase { [Params(RoundBenchmarkType.MathFloor)] - public override RoundBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + public override RoundBenchmarkType BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; } [Params(RoundBenchmarkType.MathRound, RoundBenchmarkType.WithTypecasting)] - public override RoundBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + public override RoundBenchmarkType DataType { get => base.DataType; set => base.DataType = value; } private const double OptimalParallelSearchStep = 0.329; diff --git a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs index b270c14..3a45b2c 100644 --- a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs @@ -21,11 +21,11 @@ namespace Recyclable.Collections.Benchmarks.POC SpanVsArrayBenchmarkType.ArrayGetWithVar, SpanVsArrayBenchmarkType.SpanGet, SpanVsArrayBenchmarkType.ArraySetWithField, SpanVsArrayBenchmarkType.ArraySetWithVar, SpanVsArrayBenchmarkType.SpanSet )] - public override SpanVsArrayBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + public override SpanVsArrayBenchmarkType DataType { get => base.DataType; set => base.DataType = value; } [Params(SpanVsArrayBenchmarkType.ArrayGetWithField)] // [Params(SpanVsArrayBenchmarkType.ArraySetWithField)] - public override SpanVsArrayBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + public override SpanVsArrayBenchmarkType BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; } protected override Action GetTestMethod(SpanVsArrayBenchmarkType benchmarkType) => benchmarkType switch { diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs index b2b3e4a..397b59c 100644 --- a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -83,10 +83,10 @@ namespace Recyclable.Collections.Benchmarks.POC public object? ItemToFind { get; set; } [Params(WhenParallelSearchBenchmarkType.IndexOfParallel)] - public override WhenParallelSearchBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + public override WhenParallelSearchBenchmarkType DataType { get => base.DataType; set => base.DataType = value; } [Params(WhenParallelSearchBenchmarkType.IndexOfSequentially)] - public override WhenParallelSearchBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + public override WhenParallelSearchBenchmarkType BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; } private long DoLongIndexOfSequentially() { @@ -304,7 +304,7 @@ namespace Recyclable.Collections.Benchmarks.POC ItemToFind ??= _testData.Last(); } - protected override Action? GetTestMethod(WhenParallelSearchBenchmarkType benchmarkType) => BenchmarkType switch + protected override Action? GetTestMethod(WhenParallelSearchBenchmarkType benchmarkType) => DataType switch { WhenParallelSearchBenchmarkType.IndexOfSequentially => IndexOfSequentially, WhenParallelSearchBenchmarkType.IndexOfParallel => IndexOfParallel, From e977cea2e1e4f2aeae88db29f1028336e364ccba Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 22:06:50 +0200 Subject: [PATCH 06/11] Added support for xxxSameType benchmarks. --- .../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 c8560c2..901ac8b 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -68,6 +68,6 @@ namespace Recyclable.Collections.Benchmarks 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); + ?? (Action?)GetType().GetMethod($"{benchmarkType}_AddRangeWhenSourceIs{benchmarkType}", BindingFlags.Instance | BindingFlags.Public)?.CreateDelegate(typeof(Action), this); } } From ae673d3f86f698bd214b47b87109c8b67b0603f3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 22:08:05 +0200 Subject: [PATCH 07/11] Exception message reworded for better clarity. --- 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 5e8f2f3..37127ad 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -12,7 +12,7 @@ namespace Recyclable.Collections.Benchmarks.POC private Action? _testMethod; protected Action TestMethod { - get => _testMethod ?? throw new OperationCanceledException("Skip - Custom benchmark run in progress"); + get => _testMethod ?? throw new OperationCanceledException("Skip - No actual defined"); set => _testMethod = value; } From 339e317d9679da35e1e4cdcd727e6557ae665ac3 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 22:12:14 +0200 Subject: [PATCH 08/11] New params defined --- .../POC/PocBenchmarkBase.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 37127ad..a033a39 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -41,20 +41,21 @@ namespace Recyclable.Collections.Benchmarks.POC // [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(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)] //[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(1, 10, 1024, 10240, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinItemsCountForParallelization)] // [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)] // [Params(1_048_576)] 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)] - //[Params(1, 10)] + // [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 From b06193ead3968617fc8c3362789f8d3f4f969955 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 22:12:14 +0200 Subject: [PATCH 09/11] New params defined --- .../POC/PocBenchmarkBase.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index 37127ad..b3ba2ad 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -41,20 +41,21 @@ namespace Recyclable.Collections.Benchmarks.POC // [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(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)] - //[Params(RecyclableDefaults.MinItemsCountForParallelization, 1_048_576, 2_097_152, 4_194_304, 8_388_608, 16_777_216, 33_554_432, RecyclableDefaults.MaxPooledBlockSize)] + // [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(1, 10, 1024, 10240, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinItemsCountForParallelization)] // [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)] // [Params(1_048_576)] 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)] - //[Params(1, 10)] + // [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 From 711947f2c32ed9f943195d8f12cab8b5f359e34b Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 22:14:31 +0200 Subject: [PATCH 10/11] Keeping files disabled because it's causing dead-locks on Windows --- Recyclable.Collections.Benchmarks/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 26a8f3e..2608901 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -6,7 +6,7 @@ using Recyclable.Collections.Benchmarks.POC; public static class Program { static IConfig BenchmarkConfig { get; } = ManualConfig.Create(DefaultConfig.Instance) - .WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary | ConfigOptions.KeepBenchmarkFiles); + .WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary ); static void RunAssemblyBenchmarks() { From 2d86b2ce5d69c510dda65fc95726c237f8a3d283 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 11 Jun 2023 22:21:17 +0200 Subject: [PATCH 11/11] README.md updated --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ebdab47..81c658a 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,9 @@ 1. ✅ `RecyclableSortedList` 1. ✅ `RecyclableStack` 1. ✅ `RecyclableUnorderedList` - 1. 👉 Add support for `ReadOnlySpan` - 1. 🅿️ Implement list versioning to allow data change identification + 1. ✅ Add support for `ReadOnlySpan` 1. ✅ Release 0.0.3-alpha - 1. 🅿️ Implement `List` interfaces + 1. 👉 Implement `List` interfaces 1. 🅿️ `ICollection` 1. 🅿️ `IEnumerable` 1. 🅿️ `IEnumerable` @@ -87,6 +86,7 @@ 1. 🅿️ `IReadOnlyList` 1. 🅿️ `ICollection` 1. 🅿️ `IList` + 1. 🅿️ Implement list versioning to allow data change identification 1. 🅿️ Make sure that `NeedsClearing` is used & items are cleared in 1. 🅿️ `Clear` 1. 🅿️ `Dispose` @@ -167,6 +167,7 @@ 1. 🅿️ Optimize 1. 🅿️ `RecyclableLongList.Resize` 1. 🅿️ Check if we can benefit from Sse2 in `.IndexOf`/`.Contains` methods as given in [MS blog](https://devblogs.microsoft.com/dotnet/hardware-intrinsics-in-net-core/). +1. 🅿️ Add support for `ICollection` interface in `.AddRange` & `constructor` 1. 🅿️ Release 2.0.0 # Characteristics of the classes