Skip to content

Commit 7aa9759

Browse files
authored
IBenchmarkActionFactory for InProcessNoEmitToolchain (#3063)
* `IBenchmarkActionFactory` for `InProcessNoEmitToolchain` * Account for unrollfactor. * Make benchmark actions public.
1 parent 836b5c3 commit 7aa9759

13 files changed

+812
-731
lines changed

src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkAction.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using BenchmarkDotNet.Extensions;
2+
using Perfolizer.Horology;
3+
using System.Reflection;
4+
5+
namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit;
6+
7+
/*
8+
Design goals of the whole stuff:
9+
0. Reusable API to call Setup/Clean/Overhead/Workload actions with arbitrary return value and store the result.
10+
Supported ones are: void, T, Task, Task<T>, ValueTask<T>. No input args.
11+
1. Overhead signature should match to the benchmark method signature (including static/instance modifier).
12+
2. Should work under .Net native. Uses Delegate.Combine instead of emitting the code.
13+
3. High data locality and no additional allocations / JIT where possible.
14+
This means NO closures allowed, no allocations but in .ctor,
15+
all state should be stored explicitly as BenchmarkFunc's fields.
16+
4. There can be multiple benchmark actions per single target instance (workload, globalSetup, globalCleanup methods),
17+
so target instantiation is not a responsibility of the benchmark action.
18+
5. Implementation should match to the code in BenchmarkProgram.txt.
19+
*/
20+
21+
// DONTTOUCH: Be VERY CAREFUL when changing the code.
22+
// Please, ensure that the implementation is in sync with content of BenchmarkType.txt
23+
/// <summary>Base class that provides reusable API for final implementations.</summary>
24+
public abstract class BenchmarkActionBase : IBenchmarkAction
25+
{
26+
public required Func<ValueTask> InvokeSingle { get; init; }
27+
public required Func<long, IClock, ValueTask<ClockSpan>> InvokeUnroll { get; init; }
28+
public required Func<long, IClock, ValueTask<ClockSpan>> InvokeNoUnroll { get; init; }
29+
public abstract void Complete();
30+
31+
protected static TDelegate CreateWorkload<TDelegate>(object? targetInstance, MethodInfo workloadMethod) where TDelegate : Delegate
32+
=> workloadMethod.IsStatic
33+
? workloadMethod.CreateDelegate<TDelegate>()
34+
: workloadMethod.CreateDelegate<TDelegate>(targetInstance);
35+
36+
private protected Action CreateWorkloadOrOverhead(object? instance, MethodInfo? method)
37+
{
38+
if (method == null)
39+
{
40+
return instance == null ? OverheadStatic : OverheadInstance;
41+
}
42+
return method.IsStatic
43+
? method.CreateDelegate<Action>()
44+
: method.CreateDelegate<Action>(instance);
45+
}
46+
47+
protected static TDelegate Unroll<TDelegate>(TDelegate callback, int unrollFactor)
48+
where TDelegate : notnull, Delegate
49+
{
50+
if (unrollFactor <= 1)
51+
{
52+
return callback;
53+
}
54+
55+
var delegates = new Delegate[unrollFactor];
56+
for (int i = 0; i < unrollFactor; i++)
57+
{
58+
delegates[i] = callback;
59+
}
60+
61+
return (TDelegate) Delegate.Combine(delegates)!;
62+
}
63+
64+
// must be kept in sync with Runnable_X.__Overhead
65+
internal static void OverheadStatic() { }
66+
private void OverheadInstance() { }
67+
}

src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory.cs

Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
1-
using BenchmarkDotNet.Extensions;
1+
using BenchmarkDotNet.Extensions;
22
using BenchmarkDotNet.Running;
33
using System.Reflection;
44
using System.Runtime.CompilerServices;
55

66
namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit;
77

8-
internal static partial class BenchmarkActionFactory
8+
internal static class BenchmarkActionFactory
99
{
10-
/// <summary>
11-
/// Dispatch method that creates <see cref="BenchmarkAction"/> using
12-
/// <paramref name="targetMethod"/> or <paramref name="fallbackIdleSignature"/> to find correct implementation.
13-
/// Either <paramref name="targetMethod"/> or <paramref name="fallbackIdleSignature"/> should be not <c>null</c>.
14-
/// </summary>
15-
private static BenchmarkAction CreateCore(object instance, MethodInfo? targetMethod, MethodInfo? fallbackIdleSignature, int unrollFactor)
10+
private static IBenchmarkAction CreateCore(IBenchmarkActionFactory? factory, object instance, MethodInfo targetMethod, int unrollFactor)
1611
{
17-
PrepareInstanceAndResultType(instance, targetMethod, fallbackIdleSignature, out var resultInstance, out var resultType);
12+
if (factory?.TryCreate(instance, targetMethod, unrollFactor, out var benchmarkAction) == true)
13+
{
14+
return benchmarkAction;
15+
}
16+
17+
PrepareInstanceAndResultType(instance, targetMethod, out var resultInstance, out var resultType);
1818

1919
if (resultType == typeof(void))
2020
return new BenchmarkActionVoid(resultInstance, targetMethod, unrollFactor);
2121

22-
// targetMethod must not be null here. Because it's checked by PrepareInstanceAndResultType.
23-
// Following null check is added to suppress nullable annotation errors.
24-
ArgumentNullException.ThrowIfNull(targetMethod);
25-
2622
if (resultType == typeof(void*))
2723
return new BenchmarkActionVoidPointer(resultInstance, targetMethod, unrollFactor);
2824

@@ -71,7 +67,7 @@ private static BenchmarkAction CreateCore(object instance, MethodInfo? targetMet
7167

7268
if (resultType.IsAwaitable())
7369
{
74-
throw new NotSupportedException($"{nameof(InProcessNoEmitToolchain)} does not support returning awaitable types except (Value)Task(<T>).");
70+
throw new NotSupportedException($"Default {nameof(BenchmarkActionFactory)} does not support returning awaitable types except (Value)Task(<T>).");
7571
}
7672

7773
return Create(
@@ -81,21 +77,10 @@ private static BenchmarkAction CreateCore(object instance, MethodInfo? targetMet
8177
unrollFactor);
8278
}
8379

84-
private static void PrepareInstanceAndResultType(object instance, MethodInfo? targetMethod, MethodInfo? fallbackIdleSignature, out object? resultInstance, out Type resultType)
80+
private static void PrepareInstanceAndResultType(object instance, MethodInfo targetMethod, out object? resultInstance, out Type resultType)
8581
{
86-
var signature = targetMethod ?? fallbackIdleSignature;
87-
if (signature == null)
88-
throw new ArgumentNullException(
89-
nameof(fallbackIdleSignature),
90-
$"Either {nameof(targetMethod)} or {nameof(fallbackIdleSignature)} should be not null.");
91-
92-
if (!signature.IsStatic && instance == null)
93-
throw new ArgumentNullException(
94-
nameof(instance),
95-
$"The {nameof(instance)} parameter should be not null as invocation method is instance method.");
96-
97-
resultInstance = signature.IsStatic ? null : instance;
98-
resultType = signature.ReturnType;
82+
resultInstance = targetMethod.IsStatic ? null : instance;
83+
resultType = targetMethod.ReturnType;
9984

10085
if (resultType == typeof(void))
10186
{
@@ -107,32 +92,31 @@ private static void PrepareInstanceAndResultType(object instance, MethodInfo? ta
10792
}
10893
else if (resultType.IsPointer && resultType != typeof(void*))
10994
{
110-
throw new NotSupportedException("InProcessNoEmitToolchain only supports void* return, not T*");
95+
throw new NotSupportedException($"Default {nameof(BenchmarkActionFactory)} only supports void* return, not T*");
11196
}
11297
}
11398

11499
/// <summary>Helper to enforce .ctor signature.</summary>
115100
private static BenchmarkActionBase Create(Type actionType, object? instance, MethodInfo method, int unrollFactor) =>
116101
(BenchmarkActionBase)Activator.CreateInstance(actionType, instance, method, unrollFactor)!;
117102

118-
private static void FallbackMethod() { }
119-
private static readonly MethodInfo FallbackSignature = new Action(FallbackMethod).GetMethodInfo();
103+
private static readonly MethodInfo FallbackSignature = new Action(BenchmarkActionBase.OverheadStatic).GetMethodInfo();
120104

121-
public static BenchmarkAction CreateWorkload(Descriptor descriptor, object instance, int unrollFactor) =>
122-
CreateCore(instance, descriptor.WorkloadMethod, null, unrollFactor);
105+
public static IBenchmarkAction CreateWorkload(IBenchmarkActionFactory? factory, Descriptor descriptor, object instance, int unrollFactor) =>
106+
CreateCore(factory, instance, descriptor.WorkloadMethod, unrollFactor);
123107

124-
public static BenchmarkAction CreateOverhead(Descriptor descriptor, object instance, int unrollFactor) =>
125-
CreateCore(instance, null, FallbackSignature, unrollFactor);
108+
public static IBenchmarkAction CreateOverhead(IBenchmarkActionFactory? factory, Descriptor descriptor, object instance, int unrollFactor) =>
109+
CreateCore(factory, instance, FallbackSignature, unrollFactor);
126110

127-
public static BenchmarkAction CreateGlobalSetup(Descriptor descriptor, object instance) =>
128-
CreateCore(instance, descriptor.GlobalSetupMethod, FallbackSignature, 1);
111+
public static IBenchmarkAction CreateGlobalSetup(IBenchmarkActionFactory? factory, Descriptor descriptor, object instance) =>
112+
CreateCore(factory, instance, descriptor.GlobalSetupMethod ?? FallbackSignature, 1);
129113

130-
public static BenchmarkAction CreateGlobalCleanup(Descriptor descriptor, object instance) =>
131-
CreateCore(instance, descriptor.GlobalCleanupMethod, FallbackSignature, 1);
114+
public static IBenchmarkAction CreateGlobalCleanup(IBenchmarkActionFactory? factory, Descriptor descriptor, object instance) =>
115+
CreateCore(factory, instance, descriptor.GlobalCleanupMethod ?? FallbackSignature, 1);
132116

133-
public static BenchmarkAction CreateIterationSetup(Descriptor descriptor, object instance) =>
134-
CreateCore(instance, descriptor.IterationSetupMethod, FallbackSignature, 1);
117+
public static IBenchmarkAction CreateIterationSetup(IBenchmarkActionFactory? factory, Descriptor descriptor, object instance) =>
118+
CreateCore(factory, instance, descriptor.IterationSetupMethod ?? FallbackSignature, 1);
135119

136-
public static BenchmarkAction CreateIterationCleanup(Descriptor descriptor, object instance) =>
137-
CreateCore(instance, descriptor.IterationCleanupMethod, FallbackSignature, 1);
120+
public static IBenchmarkAction CreateIterationCleanup(IBenchmarkActionFactory? factory, Descriptor descriptor, object instance) =>
121+
CreateCore(factory, instance, descriptor.IterationCleanupMethod ?? FallbackSignature, 1);
138122
}

src/BenchmarkDotNet/Toolchains/InProcess/NoEmit/BenchmarkActionFactory_Base.cs

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)