From 7a2eaee0a6a18ca74323e5ffe22e528495f90acb Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 21 Oct 2023 13:38:15 +0200 Subject: [PATCH] Initial version extracted from `Recyclable.Collections` --- LICENSE.md | 10 + README.md | 10 +- Recyclable.Collections.TestData.sln | 25 ++ .../CustomIList.cs | 33 +++ .../EnumerableWithoutCount.cs | 17 ++ .../Recyclable.Collections.TestData.csproj | 42 +++ .../RecyclableLongListTestData.cs | 280 ++++++++++++++++++ logo.png | Bin 0 -> 14559 bytes 8 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 LICENSE.md create mode 100644 Recyclable.Collections.TestData.sln create mode 100644 Recyclable.Collections.TestData/CustomIList.cs create mode 100644 Recyclable.Collections.TestData/EnumerableWithoutCount.cs create mode 100644 Recyclable.Collections.TestData/Recyclable.Collections.TestData.csproj create mode 100644 Recyclable.Collections.TestData/RecyclableLongListTestData.cs create mode 100644 logo.png diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0da7cad --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,10 @@ +# License +The project & source code is free for educational & personal use. **Any kind** of commercial use requires a donation, as prescribed below. + +# Donation requirements for a life-time, commercial use +* Commercial license is paid one time. +* It can be obtained by donating $10 for each developer seat, using the link provided below. If you hire 2 developers, you need to donate $20 for a life-time, commercial use of this package. +* Your license stays with you, if your developer moves somewhere else. + +# Link for donations +[![Donate](buy-me-a-coffee.svg)](https://www.buymeacoffee.com/mlemanczyk) \ No newline at end of file diff --git a/README.md b/README.md index 6e10073..971c5ae 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ -# Recyclable.Collections.TestData -TBD +# Collections.Benchmarks.Core +`Collections.Benchmarks.Core` project is a basic open source framework for performance measurements based on various collection types like `RecyclableList`, `RecyclableLongList`, `List`, `SortableList`, `PriorityQueue` & similar. + +## Included +* `BaselineVsActualBenchmarkBase` +* `CollectionsBenchmarksBase` +* `DataGenerator` +* `DoNothing` diff --git a/Recyclable.Collections.TestData.sln b/Recyclable.Collections.TestData.sln new file mode 100644 index 0000000..e6ca371 --- /dev/null +++ b/Recyclable.Collections.TestData.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34031.279 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Recyclable.Collections.TestData", "Recyclable.Collections.TestData\Recyclable.Collections.TestData.csproj", "{C78BEDC4-71C7-4893-B4A0-5C632E4D376E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C78BEDC4-71C7-4893-B4A0-5C632E4D376E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C78BEDC4-71C7-4893-B4A0-5C632E4D376E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C78BEDC4-71C7-4893-B4A0-5C632E4D376E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C78BEDC4-71C7-4893-B4A0-5C632E4D376E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E83A8F25-1E42-4766-BAC6-E38822D64F8A} + EndGlobalSection +EndGlobal diff --git a/Recyclable.Collections.TestData/CustomIList.cs b/Recyclable.Collections.TestData/CustomIList.cs new file mode 100644 index 0000000..42f367f --- /dev/null +++ b/Recyclable.Collections.TestData/CustomIList.cs @@ -0,0 +1,33 @@ +using System.Collections; + +namespace Recyclable.Collections.TestData +{ + 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(0, array, arrayIndex, _list.Count); + 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.Collections.TestData/EnumerableWithoutCount.cs b/Recyclable.Collections.TestData/EnumerableWithoutCount.cs new file mode 100644 index 0000000..fda438e --- /dev/null +++ b/Recyclable.Collections.TestData/EnumerableWithoutCount.cs @@ -0,0 +1,17 @@ +using System.Collections; + +namespace Recyclable.Collections.TestData +{ + public class EnumerableWithoutCount : IEnumerable + { + private readonly 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.Collections.TestData/Recyclable.Collections.TestData.csproj b/Recyclable.Collections.TestData/Recyclable.Collections.TestData.csproj new file mode 100644 index 0000000..07f389a --- /dev/null +++ b/Recyclable.Collections.TestData/Recyclable.Collections.TestData.csproj @@ -0,0 +1,42 @@ + + + + net6.0;net7.0 + enable + enable + Library + true + Recyclable.Collections.TestData + 0.0.4 + Marek Lemańczyk + N/A + Recyclable.Collections.TestData + Recyclable Array Pool List Sortable Sort Benchmark Benchmarks Collection Collections Test Data + `Recyclable.Collections.TestData` project is a basic open source package for containing test data useful for testing collections like `RecyclableList<T>`, `RecyclableLongList<T>`, `List<T>`, `SortableList<T>`, `PriorityQueue<T>` & similar. + https://github.com/mlemanczyk/Recyclable.Collections.TestData + https://github.com/mlemanczyk/Recyclable.Collections.TestData + Marek Lemańczyk 2023 + LICENSE.md + README.md + git + Initial version extracted from `Recyclable.Collections` + AnyCPU;x64 + logo.png + + + + + + + + + + + + + + + + + + diff --git a/Recyclable.Collections.TestData/RecyclableLongListTestData.cs b/Recyclable.Collections.TestData/RecyclableLongListTestData.cs new file mode 100644 index 0000000..e164fcb --- /dev/null +++ b/Recyclable.Collections.TestData/RecyclableLongListTestData.cs @@ -0,0 +1,280 @@ +using MoreLinq; +using Open.Numeric.Primes; +using Recyclable.Collections; +using System.Numerics; + +namespace Recyclable.Collections.TestData +{ + public static class RecyclableLongListTestData + { + public static object[] SourceRefData { get; } = Enumerable.Range(1, 100).Cast().ToArray(); + + public static IEnumerable CreateTestData(long itemsCount) + { + for (long index = 1; index <= itemsCount; index++) + { + yield return index; + } + } + + public static IEnumerable CreateRefTestData(long itemsCount) + { + for (long index = 1; index <= itemsCount; index++) + { + yield return index; + } + } + + public static readonly IEnumerable ItemsCountVariants = new long[] + { + //RecyclableDefaults.MinPooledArrayLength - 5, RecyclableDefaults.MinPooledArrayLength - 1, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinPooledArrayLength + 1, RecyclableDefaults.MinPooledArrayLength + 5, 127, 128, 129, RecyclableDefaults.MinItemsCountForParallelization + 0, 1, 2, 3, 7, 10, 16, RecyclableDefaults.MinPooledArrayLength - 5, RecyclableDefaults.MinPooledArrayLength - 1, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinPooledArrayLength + 1, RecyclableDefaults.MinPooledArrayLength + 5, RecyclableDefaults.MinPooledArrayLength * 100 + }; + + public static readonly IEnumerable ItemsCountTestCases = ItemsCountVariants.Select(x => new object[] { (int)x }); + + public static readonly IEnumerable BlockSizeVariants = new int[] + { + 1, 2, 4, 16, RecyclableDefaults.MinPooledArrayLength - 5, (int)BitOperations.RoundUpToPowerOf2(RecyclableDefaults.MinPooledArrayLength) + }; + + private static bool IsValidBlockSize(long itemsCount, long blockSize) => itemsCount is < 1024 || blockSize is > 8; + + private static IEnumerable GetTargetDataVariants() + { + foreach (var itemsCount in ItemsCountVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + if (!IsValidBlockSize(itemsCount, targetBlockSize)) + { + continue; + } + + yield return new object[] { itemsCount, targetBlockSize }; + } + } + } + + public static IEnumerable TargetDataVariants => GetTargetDataVariants(); + + private static IEnumerable CombineSourceDataWithBlockSize(IEnumerable sourceData) + { + foreach (var testData in sourceData) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + if (!IsValidBlockSize((long)testData[2], targetBlockSize)) + { + continue; + } + + yield return new object[] { testData[0], testData[1], testData[2], targetBlockSize }; + } + } + } + + private static IEnumerable CreateSourceDataVariants(long itemsCount, bool refType) + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + + switch (refType) + { + case false: + long[] testData = CreateTestData(itemsCount).ToArray(); + + yield return new object[] { $"Array[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"ICollection[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"ICollection[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"IEnumerable[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"IEnumerable[{itemsCount}] without non-enumerated count", new EnumerableWithoutCount(testData), itemsCount }; + yield return new object[] { $"IList[{itemsCount}]", new CustomIList(testData), itemsCount }; + yield return new object[] { $"IReadOnlyList[{itemsCount}])", testData, itemsCount }; + yield return new object[] { $"List[{itemsCount}]", testData.ToList(), itemsCount }; + yield return new object[] { $"ReadOnlySpan[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"RecyclableList[{itemsCount}]", testData.ToRecyclableList(), itemsCount }; + + foreach (var sourceBlockSize in BlockSizeVariants) + { + if (!IsValidBlockSize(itemsCount, sourceBlockSize)) + { + continue; + } + + yield return new object[] + { + $"RecyclableLongList[itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize}]", + testData.ToRecyclableLongList(sourceBlockSize), + itemsCount + }; + } + + yield return new object[] { $"Span[{itemsCount}]", testData, itemsCount }; + yield return new object[] { $"T[{itemsCount}]", testData, itemsCount }; + break; + + case true: + IEnumerable testRefData = CreateRefTestData(itemsCount).ToArray(); + + yield return new object[] { $"Array[{itemsCount}]", testRefData, itemsCount }; + yield return new object[] { $"ICollection(itemsCount: {itemsCount}", testRefData, itemsCount }; + yield return new object[] { $"ICollection(itemsCount: {itemsCount}", testRefData, itemsCount }; + yield return new object[] { $"IEnumerable(itemsCount: {itemsCount})", testRefData, itemsCount }; + yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) without non-enumerated count", new EnumerableWithoutCount(testRefData), itemsCount }; + yield return new object[] { $"IList(itemsCount: {itemsCount})", new CustomIList(testRefData), itemsCount }; + yield return new object[] { $"IReadOnlyList(itemsCount: {itemsCount})", testRefData, itemsCount }; + yield return new object[] { $"List(itemsCount: {itemsCount}", testRefData.ToList(), itemsCount }; + yield return new object[] { $"ReadOnlySpan[{itemsCount}]", testRefData, itemsCount }; + yield return new object[] { $"RecyclableList(itemsCount: {itemsCount})", testRefData.ToRecyclableList(), itemsCount }; + + foreach (var sourceBlockSize in BlockSizeVariants) + { + if (!IsValidBlockSize(itemsCount, sourceBlockSize)) + { + continue; + } + + yield return new object[] + { + $"RecyclableLongList(itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize})", + testRefData.ToRecyclableLongList(sourceBlockSize), + itemsCount + }; + } + + yield return new object[] { $"Span[{itemsCount}]", testRefData, itemsCount }; + yield return new object[] { $"T[{itemsCount}]", testRefData, itemsCount }; + break; + } + } + + private static IEnumerable CreateSourceDataVariants(bool refType) + { + foreach (var itemsCount in ItemsCountVariants) + { + foreach (var variant in CreateSourceDataVariants(itemsCount, refType)) + { + yield return variant; + } + } + } + + public static IEnumerable EmptySourceDataVariants => CreateSourceDataVariants(0, false); + public static IEnumerable SourceDataVariants => CreateSourceDataVariants(false); + public static IEnumerable SourceDataWithItemIndexVariants + { + get + { + foreach (var testCase in CreateSourceDataVariants(false).Where(testCase => (long)testCase[2] > 0)) + { + var itemIndexes = CreateItemIndexVariants((long)testCase[2], RecyclableDefaults.BlockSize).ToArray(); + yield return new object[] { testCase[0], testCase[1], testCase[2], itemIndexes }; + } + } + } + + public static IEnumerable SourceRefDataWithItemIndexVariants + { + get + { + IEnumerable testCases = CreateSourceDataVariants(true).Where(testCase => (long)testCase[2] > 0); + foreach (var testCase in testCases) + { + var itemIndexes = CreateItemIndexVariants((long)testCase[2], RecyclableDefaults.BlockSize).ToArray(); + + yield return new object[] { testCase[0], testCase[1], testCase[2], itemIndexes }; + } + } + } + + public static IEnumerable SourceDataWithBlockSizeVariants => CombineSourceDataWithBlockSize(SourceDataVariants); + public static IEnumerable SourceRefDataVariants => CreateSourceDataVariants(true); + public static IEnumerable SourceRefDataWithBlockSizeVariants { get; } = CombineSourceDataWithBlockSize(SourceRefDataVariants); + + private static IEnumerable _initialItemIndexVariants => new long[] { 0, 1, 2, 3, 4, 5 }; + private static IEnumerable CreateItemIndexVariants(long itemsCount, int blockSize) + { + IEnumerable primes = _initialItemIndexVariants.Concat( + _initialItemIndexVariants.SelectMany(index => new long[] { blockSize - index, blockSize + index }) + //new long[] { blockSize - 3, blockSize - 2, blockSize - 1, blockSize, blockSize + 1, blockSize + 2, blockSize + 3 } + ).ToArray(); + + IEnumerable second = Prime.Numbers.StartingAt(7) + .TakeUntil(prime => + prime > ((blockSize * 2) - 1) || + prime >= itemsCount + ); + + primes = primes.Concat(second).ToArray(); + + primes = primes.Concat( + primes.Select(index => itemsCount - index - 1) + ) + .Where(index => itemsCount > 0 && index >= 0 && index < itemsCount).ToArray() + .Distinct().ToArray(); + + return primes; + + //IEnumerable CreateItemIndexVariants() + //{ + //if (itemsCount > 0) + //{ + // yield return 0; + //} + + //foreach (long prime in primes) + //{ + // yield return prime; + + // long index = itemsCount - prime; + // if (index >= 0) + // { + // yield return index; + // } + //} + + //for (long index = itemsCount - (blockSize * 2) - 1; index < itemsCount; index++) + //{ + // if (index >= 0) + // { + // yield return index; + // } + //} + + //if ((blockSize / 16 * 16) + 1 < itemsCount) + //{ + // yield return (blockSize / 16 * 16) + 1; + //} + //} + + //return CreateItemIndexVariants(); + } + + public static IEnumerable CombineSourceRefDataWithBlockSizeWithItemIndex(bool refType) + { + IEnumerable sourceData = refType switch + { + false => SourceDataWithBlockSizeVariants, + true => SourceRefDataWithBlockSizeVariants + }; + + foreach (var testCase in sourceData) + { + long itemsCount = (long)testCase[2]; + if (itemsCount == 0) + { + continue; + } + + long[] itemIndexes = CreateItemIndexVariants(itemsCount, (int)testCase[3]) + .Where(_ => IsValidBlockSize((long)testCase[2], (int)testCase[3])) + .ToArray(); + + yield return new object[] { testCase[0], testCase[1], (long)testCase[2], (int)testCase[3], itemIndexes }; + } + } + + public static IEnumerable SourceDataWithBlockSizeWithItemIndexVariants { get; } = CombineSourceRefDataWithBlockSizeWithItemIndex(false); + public static IEnumerable SourceRefDataWithBlockSizeWithItemIndexVariants { get; } = CombineSourceRefDataWithBlockSizeWithItemIndex(true); + } +} \ No newline at end of file diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..da8146096e79f5b9cff6fffaf17b827d57f37afd GIT binary patch literal 14559 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