RecyclableList<T> and RecyclableLongList<T> implemented. Merge from recyclable-block-list-added branch.

This commit is contained in:
mlemanczyk committed 2023-06-10 11:27:48 +02:00
1 parent aba28028b1
commit 12083c4682
12 files changed
+414 -8

No files matched your search

@@ -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<RecyclableCollectionsBenchmarkSource>
{
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);
}
}
+52 -2
View File
@@ -4,5 +4,55 @@ using Recyclable.Collections.Benchmarks;
BenchmarkRunner.Run<ListVsLinkedListBenchmark>();
//BenchmarkRunner.Run<DelegateVsComparerBenchmark>();
//var tests = new ListVsLinkedListBenchmark();
//tests.RecyclableList();
static void RunAssemblyBenchmarks()
{
_ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" });
}
private static void RunRecyclableCollectionsBenchmarks()
{
_ = BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>(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<BoolOrComparePocBenchmarks>(BenchmarkConfig);
//_ = BenchmarkRunner.Run<DelegateVsComparerPocBenchmarks>(BenchmarkConfig);
//_ = BenchmarkRunner.Run<ModuloPocBenchmarks>(BenchmarkConfig);
// _ = BenchmarkRunner.Run<RecyclableLongListPocBenchmarks>(BenchmarkConfig);
_ = BenchmarkRunner.Run<TaskRunVsTaskFactoryStartNewBenchmarks>(BenchmarkConfig);
//_ = BenchmarkRunner.Run<RefVsInstanceMemberPocBenchmarks>(BenchmarkConfig);
//_ = BenchmarkRunner.Run<WhenParallelSearchBenchmarks>(BenchmarkConfig);
//_ = BenchmarkRunner.Run<RoundBenchmarks>(BenchmarkConfig);
// _ = BenchmarkRunner.Run<SpanVsArrayBenchmarks>(BenchmarkConfig);
// ArraySizeLimitPocBenchmarks>(BenchmarkConfig);
}
private static void Main(string[] args)
{
RunRecyclableCollectionsBenchmarks();
// RunPocBenchmarks();
// RunSelectedBenchmarks();
// RunAssemblyBenchmarks();
}
}
+2
View File
@@ -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
@@ -1,6 +1,6 @@
namespace Recyclable.Collections
{
public interface IRandomNumberGenerator
internal interface IRandomNumberGenerator
{
int NextInt32(int startIndex, int endIndex);
}
+1 -1
View File
@@ -1,6 +1,6 @@
namespace Recyclable.Collections
{
public interface IRecyclableOwner<T> : IList<T>
internal interface IRecyclableOwner<T> : IList<T>
{
T this[long index] { get; set; }
int BlockSize { get; }
+1 -1
View File
@@ -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<T>(this IList<T> values, int startIndex, int endIndex, IComparer<T> comparer)
{
+43
View File
@@ -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;
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
namespace Recyclable.Collections
{
internal class MemoryBucket<T>
{
public MemoryBucket(T[] memory, MemoryBucket<T> nextBucket)
{
_memory = memory;
_nextBucket = nextBucket;
}
public MemoryBucket(T[] memory)
{
_memory = memory;
}
private T[] _memory;
public T[] Memory { get => _memory; set => _memory = value; }
private MemoryBucket<T>? _nextBucket;
public MemoryBucket<T>? NextBucket { get => _nextBucket; set => _nextBucket = value; }
}
}
@@ -7,7 +7,7 @@
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>Recyclable.Collections</PackageId>
<PackageVersion>0.0.2</PackageVersion>
<PackageVersion>0.0.3.2-alpha</PackageVersion>
<Authors>Marek Lemańczyk</Authors>
<Company>N/A</Company>
<Product>Recyclable.Collections</Product>
@@ -19,7 +19,8 @@
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>RecyclableSortedList added. Minor improvements &amp; cleanups.</PackageReleaseNotes>
<PackageReleaseNotes>Several classes made internal to avoid conflicts with other packages. Not ready classes made internal for now.</PackageReleaseNotes>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
@@ -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<T>(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<T>(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex)
{
if (itemsCount <= 0)
{
return;
}
Span<T> sourceBlockMemory;
Span<T> destinationArrayMemory;
int startingBlockIndex = (int)(startingIndex / blockSize);
int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize > 0 ? 1 : 0)) - 1;
Span<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1);
destinationArrayMemory = new Span<T>(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<T>(this RecyclableLongList<T[]> arrays, T item) => arrays.Any(x => x.Contains(item));
public static IEnumerable<T> Enumerate<T>(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<T>(this T value, T maxValue, IComparer<T> comparer)
// => comparer.Compare(value, maxValue) > 0 ? maxValue : value;
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long LongIndexOf<T>(this RecyclableLongList<T> arrays, long itemsCount, T itemToFind, IEqualityComparer<T> 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<T>(this T[][] arrays, long itemsCount, T itemToFind, IEqualityComparer<T> comparer)
{
var arraysSpan = new Span<T[]>(arrays);
var arraysCount = arraysSpan.Length;
for (int arrayIdx = 0, scannedCount = 0; arrayIdx < arraysCount; arrayIdx++)
{
var itemsSpan = new Span<T>(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<T> ToRecyclableLongList<T>(this IList<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableLongList<T> ToRecyclableLongList<T>(this List<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableList<T> ToRecyclableList<T>(this IEnumerable<T> values) => new(values);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableList<T> ToRecyclableList<T>(this T[] values) => new(values);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableList<T> ToRecyclableList<T>(this List<T> values) => new(values);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableList<T> ToRecyclableList<T>(this RecyclableList<T> values) => new(values);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableLongList<T> ToRecyclableLongList<T>(this IEnumerable<T> values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] RentArrayFromPool<T>(this int minSize, ArrayPool<T> arrayPool) => (minSize >= _minPooledArraySize)
? arrayPool.Rent(minSize)
: new T[minSize];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] RentArrayFromPool<T>(this long minSize, ArrayPool<T> arrayPool) => (minSize is >= _minPooledArraySize and <= int.MaxValue)
? arrayPool.Rent((int)minSize)
: new T[minSize];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnToPool<T>(this T[] array, ArrayPool<T> 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<T>(this T[] memory, int itemCount, T? itemToFind, IEqualityComparer<T> equalityComparer)
{
for (var itemIdx = 0; itemIdx < itemCount; itemIdx++)
{
var item = memory[itemIdx];
if (equalityComparer.Equals(item, itemToFind))
{
return itemIdx;
}
}
return -1;
}
}
}
@@ -0,0 +1,29 @@
namespace Recyclable.Collections
{
internal readonly struct SearchConfiguration<TList, TItem>
{
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<RecyclableLongList<TItem>, TItem> CreateFrom(RecyclableLongList<TItem> list, in TItem itemToFind) => new()
{
List = list,
ItemToFind = itemToFind,
BlockSizeMinus1 = list._blockSizeMinus1,
BlockSizePow2BitShift = list._blockSizePow2BitShift,
ItemsCount = list._longCount,
BlockSize = list._blockSize
};
}
}
@@ -1,6 +1,6 @@
namespace Recyclable.Collections
{
public class SystemRandomNumberGenerator : IRandomNumberGenerator
internal class SystemRandomNumberGenerator : IRandomNumberGenerator
{
private readonly Random _random = new();