dotnet/BenchmarkDotNet

Solve MinIterationTime warning

swtrse opened this issue · 4 comments

Hello,

I have a Benchmark that looks like

namespace Cobra.Exoda.Matching.Benchmarks.Base;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using JetBrains.Annotations;

[CategoriesColumn]
[MinColumn]
[MaxColumn]
[MemoryDiagnoser(false)]
[HideColumns("Job", "Type")]
[ShortRunJob(RuntimeMoniker.Net80)]
[ShortRunJob(RuntimeMoniker.Net90)]
//[ShortRunJob(RuntimeMoniker.NativeAot80)] // Bug
//[ShortRunJob(RuntimeMoniker.NativeAot90)] // Bug
public class BenchmarkBase
{
	[Params(0, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000)]
	public int Prefill { get; [UsedImplicitly] set; }

	[Params(10)]
	public int Inserts { get; [UsedImplicitly] set; }

	[Params(1_00, 2_000, 5_000, 10_000)] public int PriceRange { get; [UsedImplicitly] set; }
}
namespace Cobra.Exoda.Matching.Benchmarks;

using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Numerics;
using Base;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNetVisualizer;
using JetBrains.Annotations;
using Nethermind.Int256;
using SortedDictionary.BigInteger;
using SortedDictionary.UInt256;
using Utils;

[PublicAPI]
[RichHtmlExporter("Benchmark of Collection Performance",
                  ["Prefill", "Inserts", "PriceRange"],
                  ["Mean", "Allocated"],
                  ["Mean", "Min", "Max", "Allocated"],
                  dividerMode: RenderTableDividerMode.SeparateTables,
                  htmlWrapMode: HtmlDocumentWrapMode.RichDataTables)]
[GenericTypeArguments(typeof(OrderDataBigIntegerKey), typeof(OrderDataBigInteger))]
[GenericTypeArguments(typeof(OrderDataUInt256Key), typeof(OrderDataUInt256))]
[Display(Name = "Benchmark of Flat Dictionary Item Insert for IDictionary<{0}, {1}>", GroupName = "Benchmark of Dictionaries Item Insert")]

// ReSharper disable once ClassCanBeSealed.Global
public class AddItemBenchmarkFlat<TKey, TData> : BenchmarkBase
	where TKey : notnull
	where TData : notnull
{
	[GlobalSetup]
	public void A_Global_Setup()
	{
		_orderDataKeys = new TKey[Inserts];
		_orderDataArray = new TData[Inserts];
		_prefillArray = new TData[Prefill];
		var rnd = new Random();
		if (typeof(TData) == typeof(OrderDataBigInteger))
		{
			Parallel.For(0,
			             Inserts,
			             i =>
			             {
				             var price = new BigInteger(rnd.NextInt64(0, PriceRange));
				             var orderId = new Guid(i + Prefill, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]);
				             _orderDataKeys[i] = (dynamic)new OrderDataBigIntegerKey(price, orderId);
				             _orderDataArray[i] = (dynamic)new OrderDataBigInteger(price, orderId, 1);
			             });
			Parallel.For(0,
			             Prefill,
			             i =>
			             {
				             var price = new BigInteger(rnd.NextInt64(0, PriceRange));
				             var orderId = new Guid(i, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]);
				             _prefillArray[i] = (dynamic)new OrderDataBigInteger(price, orderId, 1);
			             });
		}
		else if (typeof(TData) == typeof(OrderDataUInt256))
		{
			Parallel.For(0,
			             Inserts,
			             i =>
			             {
				             var price = new UInt256((ulong)rnd.NextInt64(0, PriceRange));
				             var orderId = new Guid(i + Prefill, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]);
				             _orderDataKeys[i] = (dynamic)new OrderDataUInt256Key(price, orderId);
				             _orderDataArray[i] = (dynamic)new OrderDataUInt256(price, orderId, 1);
			             });
			Parallel.For(0,
			             Prefill,
			             i =>
			             {
				             var price = new UInt256((ulong)rnd.NextInt64(0, PriceRange));
				             var orderId = new Guid(i, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]);
				             _prefillArray[i] = (dynamic)new OrderDataUInt256(price, orderId, 1);
			             });
		}
		else { throw new Exception("Unsupported Type"); }
	}

	[IterationSetup(Target = "SortedDictionary_AddItem")]
	public void A_Iteration_Setup_SortedDictionary()
	{
		if (typeof(TData) == typeof(OrderDataBigInteger))
		{
			_collection =
				(dynamic)new SortedDictionary<OrderDataBigIntegerKey, OrderDataBigInteger>(((OrderDataBigInteger[])(dynamic)_prefillArray).ToDictionary(static pair =>
						                                                                           new OrderDataBigIntegerKey(pair.Price, pair.OrderId),
					                                                                           static pair => pair));
		}
		else
		{
			_collection =
				(dynamic)new SortedDictionary<OrderDataUInt256Key, OrderDataUInt256>(((OrderDataUInt256[])(dynamic)_prefillArray).ToDictionary(static pair =>
						                                                                     new OrderDataUInt256Key(pair.Price, pair.OrderId),
					                                                                     static pair => pair));
		}
	}

	[IterationSetup(Target = "SortedImmutableDictionary_AddItem")]
	public void A_Iteration_Setup_SortedImmutableDictionary()
	{
		if (typeof(TData) == typeof(OrderDataBigInteger))
		{
			_collection =
				(dynamic)((OrderDataBigInteger[])(dynamic)_prefillArray).ToImmutableSortedDictionary(static p => new OrderDataBigIntegerKey(p.Price, p.OrderId), static p => p);
		}
		else
		{
			_collection = (dynamic)((OrderDataUInt256[])(dynamic)_prefillArray).ToImmutableSortedDictionary(static p => new OrderDataUInt256Key(p.Price, p.OrderId), static p => p);
		}
	}

	[IterationSetup(Target = "SortedList_AddItem")]
	public void A_Iteration_Setup_SortedList()
	{
		if (typeof(TData) == typeof(OrderDataBigInteger))
		{
			_collection =
				(dynamic)new SortedList<OrderDataBigIntegerKey, OrderDataBigInteger>(((OrderDataBigInteger[])(dynamic)_prefillArray).ToDictionary(static pair =>
						                                                                     new OrderDataBigIntegerKey(pair.Price, pair.OrderId),
					                                                                     static pair => pair));
		}
		else
		{
			_collection =
				(dynamic)new SortedList<OrderDataUInt256Key, OrderDataUInt256>(((OrderDataUInt256[])(dynamic)_prefillArray).ToDictionary(static pair =>
						                                                               new OrderDataUInt256Key(pair.Price, pair.OrderId),
					                                                               static pair => pair));
		}
	}

	[Benchmark(Baseline = true)]
	[BenchmarkCategory("AddItem")]
	public void SortedDictionary_AddItem()
	{
		BenchmarkHelper.FlatCollection(_orderDataKeys, _orderDataArray, ref _collection);
	}

	[Benchmark]
	[BenchmarkCategory("AddItem")]
	public void SortedImmutableDictionary_AddItem()
	{
		var immutable = (IImmutableDictionary<TKey, TData>)_collection;
		BenchmarkHelper.FlatImmutableCollection(_orderDataKeys, _orderDataArray, ref immutable);
	}

	[Benchmark]
	[BenchmarkCategory("AddItem")]
	public void SortedList_AddItem()
	{
		BenchmarkHelper.FlatCollection(_orderDataKeys, _orderDataArray, ref _collection);
	}

	// ReSharper disable NullableWarningSuppressionIsUsed
	private IDictionary<TKey, TData> _collection = default!;

	private TData[] _prefillArray = default!;

	public string[] GetDataTypeName { get; } = [typeof(TData) == typeof(OrderDataBigInteger) ? "BigInteger" : "UInt256"];

	[ParamsSource(nameof(GetDataTypeName), Priority = -1)]
	public string DataType { get; set; } = default!;
	// ReSharper restore NullableWarningSuppressionIsUsed

	// ReSharper disable NullableWarningSuppressionIsUsed
	private TData[] _orderDataArray = default!;

	private TKey[] _orderDataKeys = default!;
	// ReSharper restore NullableWarningSuppressionIsUsed
}

What I want to compare is the performance between the different variants.
However I get the MinIterationTime warning. Since my time is far away from the needed minimum iteration time I think I need to rewrite the tests but I have absolutely no idea how to do that and achieve the same tests. What I do not want is to add the time to prepare the collection to the performance benchmark time.

Add a const int OpCount = 100_000;
Run the work in a loop for (int i = 0; i < OpCount; ++i).
Attribute your benchmark like this [Benchmark(OperationsPerInvoke = OpCount)].
Adjust OpCount value until the warning goes away.
If necessary, group your setup data into an array the size of OpCount, and loop to fill it in the [IterationSetup].

thx that's what I was looking for.
@timcassell I guess OperationsPerInvoke will calculate it down to time of one operation. Am I right?

I guess OperationsPerInvoke will calculate it down to time of one operation. Am I right?

Yes, the results will be scaled.