This commit is contained in:
mlemanczyk committed 2023-12-28 19:53:17 +01:00
commit e1f6e485fb
10 files changed
+359

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Collections.Benchmarks.Core", "Collections.Benchmarks.Core\Collections.Benchmarks.Core.csproj", "{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}"
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
{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}.Debug|x64.ActiveCfg = Debug|x64
{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}.Debug|x64.Build.0 = Debug|x64
{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}.Release|Any CPU.Build.0 = Release|Any CPU
{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}.Release|x64.ActiveCfg = Release|x64
{CDB4A29F-D53D-4779-8DE2-EF6CA050DB11}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3626D07F-F4BD-4292-8087-4DE458A2ADB6}
EndGlobalSection
EndGlobal
@@ -0,0 +1,96 @@
using BenchmarkDotNet.Attributes;
using System.Numerics;
using System.Reflection;
namespace Collections.Benchmarks.Core
{
public abstract class BaselineVsActualBenchmarkBase<TBenchmarkType>
{
public virtual TBenchmarkType? DataType { get; set; }
public virtual TBenchmarkType? BaseDataType { get; set; }
private Action? _testMethod;
protected Action TestMethod
{
get => _testMethod ?? throw new OperationCanceledException("Skip - No actual defined");
set => _testMethod = value;
}
private Action? _baselineMethod;
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();
public virtual int BlockSize { get; protected set; }
public byte BlockSizePow2BitShift => checked((byte)(31 - BitOperations.LeadingZeroCount((uint)BlockSize)));
public virtual int Divider { get; protected set; } = 10;
public virtual int TestObjectCount { get; set; } = 0;
public virtual int TestObjectCountForSlowMethods { get; private set; }
protected static Exception CreateUnknownBenchmarkTypeException<T>(T benchmarkType) => new InvalidOperationException($"******* UNKNOWN BENCHMARK TYPE {{{benchmarkType}}} *******");
protected static Exception CreateMethodNotFoundException(in string methodName, in string? className) => new MethodAccessException($"******* METHOD {{{methodName}}} NOT FOUND IN CLASS {{{className}}} *******");
protected virtual void PrepareData<T>(T benchmarkType) { }
protected virtual Action? GetTestMethod(TBenchmarkType? benchmarkType)
=> (Action?)GetType().GetMethod(GetTestMethodName(benchmarkType), BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)?.CreateDelegate(typeof(Action), this) ?? throw CreateMethodNotFoundException(GetTestMethodName(benchmarkType), GetType().FullName);
protected virtual string GetTestMethodName(TBenchmarkType? benchmarkType) => $"{benchmarkType}";
[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 {{{DataType}}} *******");
TestObjectCountForSlowMethods = (TestObjectCount / 2) + 1;
BlockSize = TestObjectCount switch
{
0 => 1,
> 0 and <= 10_240 => TestObjectCount,
_ => TestObjectCount / Divider > 0 ? TestObjectCount / Divider : TestObjectCount
};
if (BlockSize > 0)
{
BlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)BlockSize));
}
Console.WriteLine($"******* BaseDataType = {BaseDataType}; default = {default(TBenchmarkType)} *******");
if (!EqualityComparer<TBenchmarkType>.Default.Equals(BaseDataType, default))
{
Console.WriteLine("******* SETTING UP BASELINE DATA *******");
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<TBenchmarkType>.Default.Equals(BaseDataType, DataType))
{
Console.WriteLine("******* SETTING UP TEST CASE DATA *******");
//~ If BaseDataType == null, then we'll come here only if DataType != null.
PrepareData(DataType!);
_testMethod = GetTestMethod(DataType);
}
Console.WriteLine("******* DATA PREPARED *******");
}
}
}
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>Collections.Benchmarks.Core</PackageId>
<PackageVersion>0.0.4</PackageVersion>
<Authors>Marek Lemańczyk</Authors>
<Company>N/A</Company>
<Product>Collections.Benchmarks.Core</Product>
<PackageTags>Recyclable Array Pool List Sortable Sort Benchmark Benchmarks Collection Collections</PackageTags>
<Description>`Collections.Benchmarks.Core` project is a basic open source framework for performance measurements based on various collection types like `RecyclableList&lt;T&gt;`, `RecyclableLongList&lt;T&gt;`, `List&lt;T&gt;`, `SortableList&lt;T&gt;`, `PriorityQueue&lt;T&gt;` &amp; similar.</Description>
<PackageProjectUrl>https://github.com/mlemanczyk/Collections.Benchmarks.Core</PackageProjectUrl>
<RepositoryUrl>https://github.com/mlemanczyk/Collections.Benchmarks.Core</RepositoryUrl>
<Copyright>Marek Lemańczyk 2023</Copyright>
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>Recyclable.Collections updated. MethodNameSuffix added to DataDrivenBenchmarksBase to simplify usage.</PackageReleaseNotes>
<Platforms>AnyCPU;x64</Platforms>
<PackageIcon>logo.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<None Include="..\logo.png" Pack="true" PackagePath="" Link="logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\LICENSE.md" Pack="true" PackagePath="" Link="LICENSE.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\README.md" Pack="true" PackagePath="" Link="README.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
<PackageReference Include="Collections.Pooled" Version="1.0.82" />
<PackageReference Include="Recyclable.Collections" Version="0.0.6.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
namespace Collections.Benchmarks.Core
{
public enum CollectionsBenchmarkType
{
Unknown, Add_WithCapacity, Add, AddRange_WithCapacity, AddRangeWhenSourceIsArray, AddRangeWhenSourceIsIEnumerable,
AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, BinarySearch_BestAndWorstCases,
Contains_FirstItems, Contains_LastItems, ConvertAll, Count, Create_WithCapacity, Create, Exists_BestAndWorstCases,
Find_BestAndWorstCases, FindAll_BestAndWorstCases, FindLast_BestAndWorstCases, FindLastIndex_BestAndWorstCases,
ForEach, GetItem, IndexOf_BestAndWorstCases,IndexOf_FirstItems, IndexOf_LastItems, LongCount,
Remove_FirstItems, Remove_LastItems, RemoveAt_FirstItems, RemoveAt_LastItems,
SetItem, VersionedForEach
}
}
@@ -0,0 +1,12 @@
namespace Collections.Benchmarks.Core
{
public enum CollectionsBenchmarksSource
{
Unknown,
Array,
List,
PooledList,
RecyclableList,
RecyclableLongList,
}
}
@@ -0,0 +1,99 @@
using BenchmarkDotNet.Attributes;
using Collections.Pooled;
using Recyclable.Collections;
namespace Collections.Benchmarks.Core
{
[MemoryDiagnoser]
public class DataDrivenBenchmarksBase<TTestCase> : BaselineVsActualBenchmarkBase<TTestCase>
{
protected int[]? _testObjects;
protected int[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized");
protected List<int>? _testObjectsAsList;
protected List<int> TestObjectsAsList => _testObjectsAsList ?? throw new NullReferenceException("Something went wrong and the field is not initialized");
protected PooledList<int>? _testObjectsAsPooledList;
public PooledList<int> TestObjectsAsPooledList => _testObjectsAsPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized");
protected RecyclableList<int>? _testObjectsAsRecyclableList;
protected RecyclableList<int> TestObjectsAsRecyclableList => _testObjectsAsRecyclableList ?? throw new NullReferenceException("Something is wrong and the field is not initialized");
protected RecyclableLongList<int>? _testObjectsAsRecyclableLongList;
protected RecyclableLongList<int> TestObjectsAsRecyclableLongList => _testObjectsAsRecyclableLongList ?? throw new NullReferenceException("Something is wrong and the field is not initialized");
protected IEnumerable<int> TestObjectsAsIEnumerable
{
get
{
for (int i = 1; i <= TestObjectCount; i++)
{
yield return i;
}
}
}
protected override string GetTestMethodName(TTestCase? benchmarkType)
{
return $"{base.GetTestMethodName(benchmarkType)}{MethodNameSuffix}";
}
protected override void PrepareData<T>(T benchmarkType)
{
Console.WriteLine($"******* PREPARING DATA FOR {benchmarkType} *******");
switch (benchmarkType)
{
case CollectionsBenchmarksSource.Unknown:
break;
case CollectionsBenchmarksSource.Array:
_testObjects ??= DataGenerator.EnumerateTestObjects(TestObjectCount);
break;
case CollectionsBenchmarksSource.List:
_testObjectsAsList ??= TestObjects.ToList();
break;
case CollectionsBenchmarksSource.PooledList:
_testObjectsAsPooledList ??= new(TestObjects, ClearMode.Auto);
break;
case CollectionsBenchmarksSource.RecyclableList:
_testObjectsAsRecyclableList ??= new(TestObjects, initialCapacity: TestObjectCount);
break;
case CollectionsBenchmarksSource.RecyclableLongList:
_testObjectsAsRecyclableLongList ??= new(TestObjects, BlockSize, initialCapacity: base.TestObjectCount);
break;
default:
throw CreateUnknownBenchmarkTypeException(benchmarkType);
}
base.PrepareData(benchmarkType);
}
public override void Cleanup()
{
Console.WriteLine("******* GLOBAL CLEANUP *******");
if (_testObjects?.Length > 0 && !_testObjects[0].GetType().IsValueType)
{
Array.Fill(_testObjects, 0);
}
_testObjects = default;
_testObjectsAsList = default;
_testObjectsAsPooledList?.Dispose();
_testObjectsAsPooledList = default;
_testObjectsAsRecyclableList?.Dispose();
_testObjectsAsRecyclableList = default;
_testObjectsAsRecyclableLongList?.Dispose();
_testObjectsAsRecyclableLongList = default;
base.Cleanup();
}
public override void Setup()
{
PrepareData(CollectionsBenchmarksSource.Array);
base.Setup();
}
public virtual string MethodNameSuffix { get; } = string.Empty;
}
}
@@ -0,0 +1,29 @@
namespace Collections.Benchmarks.Core
{
public static class DataGenerator
{
public static int[] EnumerateTestObjects(int testObjectCount)
{
var result = new int[testObjectCount];
Console.WriteLine("******* TEST DATA ARRAY CREATED *******");
Span<int> resultSpan = result;
for (int i = 0; i < testObjectCount; i++)
{
resultSpan[i] = i;
}
return result;
}
public static IEnumerable<long> EnumerateTestObjects(long testObjectCount)
{
Console.WriteLine("******* TEST DATA ARRAY CREATED *******");
for (int i = 1; i <= testObjectCount; i++)
{
yield return i;
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.Runtime.CompilerServices;
#pragma warning disable IDE0060 // Remove unused parameter
namespace Collections.Benchmarks.Core
{
public static class DoNothing
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static void With<T>(in T item)
{
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static void With<T>(in T item1, in T item2)
{
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static void With<T>(ReadOnlySpan<T> span)
{
}
}
}
+10
View File
@@ -0,0 +1,10 @@
# License
The project &amp; source code is free for educational &amp; 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)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB