diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs new file mode 100644 index 0000000..630e3b9 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -0,0 +1,73 @@ +using System.Reflection; +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public enum RecyclableCollectionsBenchmarkType + { + Add_WithCapacity, Add, AddRange_WithCapacity, AddRangeWhenSourceIsArray, AddRangeWhenSourceIsIEnumerable, + AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, Contains_FirstItems, + Contains_LastItems, Count, Create_WithCapacity, Create, GetItem, IndexOf_BestAndWorstCases, IndexOf_FirstItems, + IndexOf_LastItems, LongCount, Remove_FirstItems, Remove_LastItems, RemoveAt_FirstItems, RemoveAt_LastItems, + SetItem, + } + + public partial class RecyclableCollectionsBenchmarks : RecyclableBenchmarkBase + { + public RecyclableCollectionsBenchmarks() : base() + { + TestObjectCount = 1; + // BaselineBenchmarkType = RecyclableCollectionsBenchmarkSource.PooledList; + BenchmarkType = RecyclableCollectionsBenchmarkSource.RecyclableLongList; + } + + [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_BestAndWorstCases + //RecyclableCollectionsBenchmarkType.IndexOf_FirstItems, + //RecyclableCollectionsBenchmarkType.IndexOf_LastItems + //RecyclableCollectionsBenchmarkType.LongCount, + //RecyclableCollectionsBenchmarkType.Remove_FirstItems, + //RecyclableCollectionsBenchmarkType.Remove_LastItems, + //RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems, + //RecyclableCollectionsBenchmarkType.RemoveAt_LastItems, + //RecyclableCollectionsBenchmarkType.SetItem + )] + + public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases; + + [Params + ( + RecyclableCollectionsBenchmarkSource.List + )] + public override RecyclableCollectionsBenchmarkSource BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + [Params + ( + //RecyclableCollectionsBenchmarkSource.Array, + //RecyclableCollectionsBenchmarkSource.List, + //RecyclableCollectionsBenchmarkSource.PooledList + //RecyclableCollectionsBenchmarkSource.RecyclableList, + RecyclableCollectionsBenchmarkSource.RecyclableLongList + )] + public override RecyclableCollectionsBenchmarkSource BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = 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/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 68c401c..e6ba27f 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -4,5 +4,55 @@ using Recyclable.Collections.Benchmarks; BenchmarkRunner.Run(); //BenchmarkRunner.Run(); -//var tests = new ListVsLinkedListBenchmark(); -//tests.RecyclableList(); \ No newline at end of file + static void RunAssemblyBenchmarks() + { + _ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); + } + + private static void RunRecyclableCollectionsBenchmarks() + { + _ = BenchmarkRunner.Run(BenchmarkConfig); + } + + static void RunSelectedBenchmarks() + { + // **************** + // *** Template *** + // **************** + var benchmark = new RecyclableCollectionsBenchmarks(); + benchmark.Setup(); + benchmark.RecyclableLongList_IndexOf_BestAndWorstCases(); + benchmark.Cleanup(); + + //foreach (var _ in Enumerable.Range(1, 1000)) + //{ + // benchmark.RecyclableLongList_Remove_FirstItems(); + // benchmark.RecyclableLongList_RemoveAt_LastItems(); + // benchmark.List_RemoveAt_LastItems(); + //} + + //benchmark.Cleanup(); + } + + 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); + // ArraySizeLimitPocBenchmarks>(BenchmarkConfig); + } + + private static void Main(string[] args) + { + RunRecyclableCollectionsBenchmarks(); + // RunPocBenchmarks(); + // RunSelectedBenchmarks(); + // RunAssemblyBenchmarks(); + } +} diff --git a/Recyclable.Collections.sln b/Recyclable.Collections.sln index d746276..9e0491f 100644 --- a/Recyclable.Collections.sln +++ b/Recyclable.Collections.sln @@ -27,6 +27,8 @@ Global {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|Any CPU.Build.0 = Debug|Any CPU {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/IRandomNumberGenerator.cs b/Recyclable.Collections/IRandomNumberGenerator.cs index 9199fe4..a4df34f 100644 --- a/Recyclable.Collections/IRandomNumberGenerator.cs +++ b/Recyclable.Collections/IRandomNumberGenerator.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections { - public interface IRandomNumberGenerator + internal interface IRandomNumberGenerator { int NextInt32(int startIndex, int endIndex); } diff --git a/Recyclable.Collections/IRecyclableOwner.cs b/Recyclable.Collections/IRecyclableOwner.cs index 254c04f..b6eaca2 100644 --- a/Recyclable.Collections/IRecyclableOwner.cs +++ b/Recyclable.Collections/IRecyclableOwner.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections { - public interface IRecyclableOwner : IList + internal interface IRecyclableOwner : IList { T this[long index] { get; set; } int BlockSize { get; } diff --git a/Recyclable.Collections/ListExtensions.cs b/Recyclable.Collections/ListExtensions.cs index ab8dafa..b209a0b 100644 --- a/Recyclable.Collections/ListExtensions.cs +++ b/Recyclable.Collections/ListExtensions.cs @@ -4,7 +4,7 @@ namespace Recyclable.Collections { // The source code was taken from https://codereview.stackexchange.com/questions/205407/quicksort-without-recursion. // It was only beautified for .Net 6.0+. - public static class ListExtensions + internal static class ListExtensions { public static void InsertionSort(this IList values, int startIndex, int endIndex, IComparer comparer) { diff --git a/Recyclable.Collections/MathUtils.cs b/Recyclable.Collections/MathUtils.cs new file mode 100644 index 0000000..0fe8e7a --- /dev/null +++ b/Recyclable.Collections/MathUtils.cs @@ -0,0 +1,43 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections +{ + internal 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 + // }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPow2(long value) => (value != 0) && ((value & (value - 1)) == 0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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) + { + 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.Collections/MemoryBucket.cs b/Recyclable.Collections/MemoryBucket.cs new file mode 100644 index 0000000..720a33f --- /dev/null +++ b/Recyclable.Collections/MemoryBucket.cs @@ -0,0 +1,22 @@ +namespace Recyclable.Collections +{ + internal 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/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj index b2a40c8..99a142c 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.2-alpha Marek Lemańczyk N/A Recyclable.Collections @@ -19,7 +19,8 @@ LICENSE.md README.md git - RecyclableSortedList added. Minor improvements & cleanups. + Several classes made internal to avoid conflicts with other packages. Not ready classes made internal for now. + AnyCPU;x64 diff --git a/Recyclable.Collections/RecyclableLongListExtensions.cs b/Recyclable.Collections/RecyclableLongListExtensions.cs new file mode 100644 index 0000000..e4e05b0 --- /dev/null +++ b/Recyclable.Collections/RecyclableLongListExtensions.cs @@ -0,0 +1,186 @@ +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections +{ + internal static class RecyclableLongListExtensions + { + 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) + { + for (long destItemIndex = startingIndex, sourceItemIndex = offset; destItemIndex < startingIndex + count; destItemIndex++, sourceItemIndex++) + { + destArray[destItemIndex] = memory[sourceItemIndex]; + } + } + + 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 % 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++) + { + sourceBlockMemory = new(sourceMemoryBlocksSpan[memoryBlockIndex], 0, blockSize); + sourceBlockMemory.CopyTo(destinationArrayMemory); + destinationArrayMemory = destinationArrayMemory[blockSize..]; + } + + if (itemsCount - (lastBlockIndex * blockSize) > 0) + { + sourceBlockMemory = new(sourceMemoryBlocksSpan[lastBlockIndex], 0, (int)(itemsCount - (lastBlockIndex * blockSize))); + sourceBlockMemory.CopyTo(destinationArrayMemory); + } + } + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //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) + { + 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; + + 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 RecyclableLongList arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) + { + for (var itemIdx = 0L; itemIdx < itemsCount; itemIdx++) + { + var item = arrays[itemIdx]; + if (comparer.Equals(itemToFind, item)) + { + return itemIdx; + } + + itemIdx++; + } + + return -1; + } + + public static long LongIndexOf(this T[][] arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) + { + var arraysSpan = new Span(arrays); + var arraysCount = arraysSpan.Length; + for (int arrayIdx = 0, scannedCount = 0; arrayIdx < arraysCount; arrayIdx++) + { + var itemsSpan = new Span(arraysSpan[arrayIdx]); + var arrayItemsCount = itemsSpan.Length; + for (var itemIdx = 0; itemIdx < arrayItemsCount; itemIdx++) + { + if (comparer.Equals(itemToFind, itemsSpan[itemIdx])) + { + return scannedCount; + } + + scannedCount++; + if (scannedCount == itemsCount) + { + return -1; + } + } + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this IEnumerable values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this T[] values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this List values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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, 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, ArrayPool arrayPool, bool needsClearing) + { + if (array.LongLength is > _minPooledArraySize and <= int.MaxValue) + { + arrayPool.Return(array, needsClearing); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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/SearchConfiguration.cs b/Recyclable.Collections/SearchConfiguration.cs new file mode 100644 index 0000000..40cb7ca --- /dev/null +++ b/Recyclable.Collections/SearchConfiguration.cs @@ -0,0 +1,29 @@ +namespace Recyclable.Collections +{ + internal 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 BlockSize { get; init; } + + // public SearchConfiguration(TList list, in TItem itemToFind) + // { + // ItemToFind = itemToFind; + // List = list; + // } + + public static SearchConfiguration, TItem> CreateFrom(RecyclableLongList list, in TItem itemToFind) => new() + { + List = list, + ItemToFind = itemToFind, + BlockSizeMinus1 = list._blockSizeMinus1, + BlockSizePow2BitShift = list._blockSizePow2BitShift, + ItemsCount = list._longCount, + BlockSize = list._blockSize + }; + } +} \ No newline at end of file diff --git a/Recyclable.Collections/SystemRandomNumberGenerator.cs b/Recyclable.Collections/SystemRandomNumberGenerator.cs index baf7758..ffd8dab 100644 --- a/Recyclable.Collections/SystemRandomNumberGenerator.cs +++ b/Recyclable.Collections/SystemRandomNumberGenerator.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections { - public class SystemRandomNumberGenerator : IRandomNumberGenerator + internal class SystemRandomNumberGenerator : IRandomNumberGenerator { private readonly Random _random = new();