Skip to content

Commit 12fdbe2

Browse files
ig-sinicynadamsitnik
authored andcommitted
+ InProcessNoEmitToolchain (#1123)
1 parent 40dcab2 commit 12fdbe2

24 files changed

+990
-32
lines changed

src/BenchmarkDotNet/Analysers/EnvironmentAnalyser.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
using System.Linq;
33
using System.Reflection;
44
using System.Text;
5+
56
using BenchmarkDotNet.Extensions;
67
using BenchmarkDotNet.Portability;
78
using BenchmarkDotNet.Reports;
8-
using BenchmarkDotNet.Toolchains.InProcess;
9+
using BenchmarkDotNet.Toolchains.InProcess.Emit;
10+
using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
911

1012
namespace BenchmarkDotNet.Analysers
1113
{
@@ -54,7 +56,7 @@ private static string CreateWarningAboutAntivirus(ICollection<Antivirus> avProdu
5456
foreach (var av in avProducts)
5557
sb.AppendLine($" - {av}");
5658

57-
sb.AppendLine($"Use {nameof(InProcessToolchain)} to avoid new process creation.");
59+
sb.AppendLine($"Use {nameof(InProcessEmitToolchain)} or {nameof(InProcessNoEmitToolchain)} to avoid new process creation.");
5860

5961
return sb.ToString();
6062
}

src/BenchmarkDotNet/Configs/DebugConfig.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Text;
5+
56
using BenchmarkDotNet.Analysers;
67
using BenchmarkDotNet.Columns;
78
using BenchmarkDotNet.Diagnosers;
@@ -16,8 +17,9 @@
1617
using BenchmarkDotNet.Toolchains;
1718
using BenchmarkDotNet.Toolchains.CoreRt;
1819
using BenchmarkDotNet.Toolchains.CsProj;
19-
using BenchmarkDotNet.Toolchains.InProcess;
20+
using BenchmarkDotNet.Toolchains.InProcess.Emit;
2021
using BenchmarkDotNet.Validators;
22+
2123
using JetBrains.Annotations;
2224

2325
namespace BenchmarkDotNet.Configs
@@ -28,32 +30,31 @@ namespace BenchmarkDotNet.Configs
2830
[PublicAPI]
2931
public class DebugInProcessConfig : DebugConfig
3032
{
31-
public override IEnumerable<Job> GetJobs()
33+
public override IEnumerable<Job> GetJobs()
3234
=> new[]
3335
{
3436
Job.Default
3537
.With(
36-
new InProcessToolchain(
38+
new InProcessEmitToolchain(
3739
TimeSpan.FromHours(1), // 1h should be enough to debug the benchmark
38-
BenchmarkActionCodegen.ReflectionEmit,
3940
true))
4041
};
4142
}
42-
43+
4344
/// <summary>
4445
/// config which allows to build benchmarks in Debug
4546
/// </summary>
4647
[PublicAPI]
4748
public class DebugBuildConfig : DebugConfig
4849
{
49-
public override IEnumerable<Job> GetJobs()
50-
=> new[]
51-
{
50+
public override IEnumerable<Job> GetJobs()
51+
=> new[]
52+
{
5253
Job.Default
5354
.With(GetToolchainThatGeneratesProjectFile())
5455
.WithCustomBuildConfiguration("Debug") // will do `-c Debug everywhere`
5556
};
56-
57+
5758
private IToolchain GetToolchainThatGeneratesProjectFile()
5859
{
5960
switch (RuntimeInformation.GetCurrentRuntime())
@@ -78,7 +79,7 @@ public abstract class DebugConfig : IConfig
7879
public IEnumerable<IValidator> GetValidators() => Array.Empty<IValidator>();
7980
public IEnumerable<IColumnProvider> GetColumnProviders() => DefaultColumnProviders.Instance;
8081
public IEnumerable<IExporter> GetExporters() => Array.Empty<IExporter>();
81-
public IEnumerable<ILogger> GetLoggers() => new []{ ConsoleLogger.Default };
82+
public IEnumerable<ILogger> GetLoggers() => new[] { ConsoleLogger.Default };
8283
public IEnumerable<IDiagnoser> GetDiagnosers() => Array.Empty<IDiagnoser>();
8384
public IEnumerable<IAnalyser> GetAnalysers() => Array.Empty<IAnalyser>();
8485
public IEnumerable<HardwareCounter> GetHardwareCounters() => Array.Empty<HardwareCounter>();

src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoser.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
45
using BenchmarkDotNet.Analysers;
56
using BenchmarkDotNet.Engines;
67
using BenchmarkDotNet.Environments;
@@ -11,6 +12,7 @@
1112
using BenchmarkDotNet.Reports;
1213
using BenchmarkDotNet.Running;
1314
using BenchmarkDotNet.Toolchains.InProcess;
15+
using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
1416
using BenchmarkDotNet.Validators;
1517

1618
namespace BenchmarkDotNet.Diagnosers
@@ -62,7 +64,8 @@ public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
6264
{
6365
var benchmark = parameters.BenchmarkCase;
6466

65-
switch (signal) {
67+
switch (signal)
68+
{
6669
case HostSignal.AfterAll when ShouldUseWindowsDisassembler(benchmark):
6770
results.Add(benchmark, windowsDisassembler.Disassemble(parameters));
6871
break;
@@ -86,7 +89,10 @@ public IEnumerable<ValidationError> Validate(ValidationParameters validationPara
8689
yield return new ValidationError(false, "No Disassembler support, only Mono is supported for non-Windows OS", benchmark);
8790

8891
if (benchmark.Job.Infrastructure.HasValue(InfrastructureMode.ToolchainCharacteristic)
89-
&& benchmark.Job.Infrastructure.Toolchain is InProcessToolchain)
92+
#pragma warning disable 618
93+
&& (benchmark.Job.Infrastructure.Toolchain is InProcessToolchain
94+
#pragma warning restore 618
95+
|| benchmark.Job.Infrastructure.Toolchain is InProcessNoEmitToolchain))
9096
{
9197
yield return new ValidationError(true, "InProcessToolchain has no DisassemblyDiagnoser support", benchmark);
9298
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
3+
using JetBrains.Annotations;
4+
5+
namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit
6+
{
7+
/// <summary>Common API to run the Setup/Clean/Idle/Run methods</summary>
8+
[PublicAPI]
9+
public abstract class BenchmarkAction
10+
{
11+
/// <summary>Gets or sets invoke single callback.</summary>
12+
/// <value>Invoke single callback.</value>
13+
public Action InvokeSingle { get; protected set; }
14+
15+
/// <summary>Gets or sets invoke multiple times callback.</summary>
16+
/// <value>Invoke multiple times callback.</value>
17+
public Action<long> InvokeMultiple { get; protected set; }
18+
19+
/// <summary>Gets the last run result.</summary>
20+
/// <value>The last run result.</value>
21+
public virtual object LastRunResult => null;
22+
}
23+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Runtime.CompilerServices;
4+
using System.Threading.Tasks;
5+
6+
using BenchmarkDotNet.Extensions;
7+
using BenchmarkDotNet.Running;
8+
9+
using JetBrains.Annotations;
10+
11+
namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit
12+
{
13+
/// <summary>Helper class that creates <see cref="BenchmarkAction"/> instances. </summary>
14+
public static partial class BenchmarkActionFactory
15+
{
16+
/// <summary>
17+
/// Dispatch method that creates <see cref="BenchmarkAction"/> using
18+
/// <paramref name="targetMethod"/> or <paramref name="fallbackIdleSignature"/> to find correct implementation.
19+
/// Either <paramref name="targetMethod"/> or <paramref name="fallbackIdleSignature"/> should be not <c>null</c>.
20+
/// </summary>
21+
private static BenchmarkAction CreateCore(
22+
[NotNull] object instance,
23+
[CanBeNull] MethodInfo targetMethod,
24+
[CanBeNull] MethodInfo fallbackIdleSignature,
25+
int unrollFactor)
26+
{
27+
PrepareInstanceAndResultType(instance, targetMethod, fallbackIdleSignature, out var resultInstance, out var resultType);
28+
29+
if (resultType == typeof(void))
30+
return new BenchmarkActionVoid(resultInstance, targetMethod, unrollFactor);
31+
32+
if (resultType == typeof(Task))
33+
return new BenchmarkActionTask(resultInstance, targetMethod, unrollFactor);
34+
35+
if (resultType.GetTypeInfo().IsGenericType)
36+
{
37+
var genericType = resultType.GetGenericTypeDefinition();
38+
var argType = resultType.GenericTypeArguments[0];
39+
if (typeof(Task<>) == genericType)
40+
return Create(
41+
typeof(BenchmarkActionTask<>).MakeGenericType(argType),
42+
resultInstance,
43+
targetMethod,
44+
unrollFactor);
45+
46+
if (typeof(ValueTask<>).IsAssignableFrom(genericType))
47+
return Create(
48+
typeof(BenchmarkActionValueTask<>).MakeGenericType(argType),
49+
resultInstance,
50+
targetMethod,
51+
unrollFactor);
52+
}
53+
54+
if (targetMethod == null && resultType.GetTypeInfo().IsValueType)
55+
// for Idle: we return int because creating bigger ValueType could take longer than benchmarked method itself.
56+
resultType = typeof(int);
57+
58+
return Create(
59+
typeof(BenchmarkAction<>).MakeGenericType(resultType),
60+
resultInstance,
61+
targetMethod,
62+
unrollFactor);
63+
}
64+
65+
private static void PrepareInstanceAndResultType(
66+
object instance, MethodInfo targetMethod, MethodInfo fallbackIdleSignature,
67+
out object resultInstance, out Type resultType)
68+
{
69+
var signature = targetMethod ?? fallbackIdleSignature;
70+
if (signature == null)
71+
throw new ArgumentNullException(
72+
nameof(fallbackIdleSignature),
73+
$"Either {nameof(targetMethod)} or {nameof(fallbackIdleSignature)} should be not null.");
74+
75+
if (!signature.IsStatic && instance == null)
76+
throw new ArgumentNullException(
77+
nameof(instance),
78+
$"The {nameof(instance)} parameter should be not null as invocation method is instance method.");
79+
80+
resultInstance = signature.IsStatic ? null : instance;
81+
resultType = signature.ReturnType;
82+
83+
if (resultType == typeof(void))
84+
{
85+
// DONTTOUCH: async should be checked for target method
86+
// as fallbackIdleSignature used for result type detection only.
87+
bool isUsingAsyncKeyword = targetMethod?.HasAttribute<AsyncStateMachineAttribute>() ?? false;
88+
if (isUsingAsyncKeyword)
89+
throw new NotSupportedException("Async void is not supported by design.");
90+
}
91+
}
92+
93+
/// <summary>Helper to enforce .ctor signature.</summary>
94+
private static BenchmarkActionBase Create(Type actionType, object instance, MethodInfo method, int unrollFactor) =>
95+
(BenchmarkActionBase)Activator.CreateInstance(actionType, instance, method, unrollFactor);
96+
97+
private static void FallbackMethod() { }
98+
private static readonly MethodInfo FallbackSignature = new Action(FallbackMethod).GetMethodInfo();
99+
private static readonly MethodInfo DummyMethod = typeof(DummyInstance).GetMethod(nameof(DummyInstance.Dummy));
100+
101+
/// <summary>Creates run benchmark action.</summary>
102+
/// <param name="descriptor">Descriptor info.</param>
103+
/// <param name="instance">Instance of target.</param>
104+
/// <param name="unrollFactor">Unroll factor.</param>
105+
/// <returns>Run benchmark action.</returns>
106+
public static BenchmarkAction CreateWorkload(Descriptor descriptor, object instance, int unrollFactor) =>
107+
CreateCore(instance, descriptor.WorkloadMethod, null, unrollFactor);
108+
109+
/// <summary>Creates idle benchmark action.</summary>
110+
/// <param name="descriptor">Descriptor info.</param>
111+
/// <param name="instance">Instance of target.</param>
112+
/// <param name="unrollFactor">Unroll factor.</param>
113+
/// <returns>Idle benchmark action.</returns>
114+
public static BenchmarkAction CreateOverhead(Descriptor descriptor, object instance, int unrollFactor) =>
115+
CreateCore(instance, null, descriptor.WorkloadMethod, unrollFactor);
116+
117+
/// <summary>Creates global setup benchmark action.</summary>
118+
/// <param name="descriptor">Descriptor info.</param>
119+
/// <param name="instance">Instance of target.</param>
120+
/// <returns>Setup benchmark action.</returns>
121+
public static BenchmarkAction CreateGlobalSetup(Descriptor descriptor, object instance) =>
122+
CreateCore(instance, descriptor.GlobalSetupMethod, FallbackSignature, 1);
123+
124+
/// <summary>Creates global cleanup benchmark action.</summary>
125+
/// <param name="descriptor">Descriptor info.</param>
126+
/// <param name="instance">Instance of target.</param>
127+
/// <returns>Cleanup benchmark action.</returns>
128+
public static BenchmarkAction CreateGlobalCleanup(Descriptor descriptor, object instance) =>
129+
CreateCore(instance, descriptor.GlobalCleanupMethod, FallbackSignature, 1);
130+
131+
/// <summary>Creates global setup benchmark action.</summary>
132+
/// <param name="descriptor">Descriptor info.</param>
133+
/// <param name="instance">Instance of target.</param>
134+
/// <returns>Setup benchmark action.</returns>
135+
public static BenchmarkAction CreateIterationSetup(Descriptor descriptor, object instance) =>
136+
CreateCore(instance, descriptor.IterationSetupMethod, FallbackSignature, 1);
137+
138+
/// <summary>Creates global cleanup benchmark action.</summary>
139+
/// <param name="descriptor">Descriptor info.</param>
140+
/// <param name="instance">Instance of target.</param>
141+
/// <returns>Cleanup benchmark action.</returns>
142+
public static BenchmarkAction CreateIterationCleanup(Descriptor descriptor, object instance) =>
143+
CreateCore(instance, descriptor.IterationCleanupMethod, FallbackSignature, 1);
144+
145+
/// <summary>Creates a dummy benchmark action.</summary>
146+
/// <returns>Dummy benchmark action.</returns>
147+
public static BenchmarkAction CreateDummy() =>
148+
CreateCore(new DummyInstance(), DummyMethod, null, 1);
149+
}
150+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
5+
using JetBrains.Annotations;
6+
7+
namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit
8+
{
9+
/*
10+
Design goals of the whole stuff:
11+
0. Reusable API to call Setup/Clean/Overhead/Workload actions with arbitrary return value and store the result.
12+
Supported ones are: void, T, Task, Task<T>, ValueTask<T>. No input args.
13+
1. Overhead signature should match to the benchmark method signature (including static/instance modifier).
14+
2. Should work under .Net native. Uses Delegate.Combine instead of emitting the code.
15+
3. High data locality and no additional allocations / JIT where possible.
16+
This means NO closures allowed, no allocations but in .ctor and for LastCallResult boxing,
17+
all state should be stored explicitly as BenchmarkAction's fields.
18+
4. There can be multiple benchmark actions per single target instance (workload, globalSetup, globalCleanup methods),
19+
so target instantiation is not a responsibility of the benchmark action.
20+
5. Implementation should match to the code in BenchmarkProgram.txt.
21+
*/
22+
23+
// DONTTOUCH: Be VERY CAREFUL when changing the code.
24+
// Please, ensure that the implementation is in sync with content of BenchmarkProgram.txt
25+
26+
/// <summary>Helper class that creates <see cref="BenchmarkAction"/> instances. </summary>
27+
public static partial class BenchmarkActionFactory
28+
{
29+
/// <summary>Base class that provides reusable API for final implementations.</summary>
30+
internal abstract class BenchmarkActionBase : BenchmarkAction
31+
{
32+
protected static TDelegate CreateWorkload<TDelegate>([CanBeNull] object targetInstance, MethodInfo workloadMethod)
33+
{
34+
if (workloadMethod.IsStatic)
35+
return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate));
36+
37+
return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate), targetInstance);
38+
}
39+
40+
protected static TDelegate CreateWorkloadOrOverhead<TDelegate>(
41+
[CanBeNull] object targetInstance,
42+
[CanBeNull] MethodInfo workloadMethod,
43+
[NotNull] TDelegate overheadStaticCallback,
44+
[NotNull] TDelegate overheadInstanceCallback)
45+
{
46+
if (workloadMethod == null)
47+
return targetInstance == null ? overheadStaticCallback : overheadInstanceCallback;
48+
49+
if (workloadMethod.IsStatic)
50+
return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate));
51+
52+
return (TDelegate)(object)workloadMethod.CreateDelegate(typeof(TDelegate), targetInstance);
53+
}
54+
55+
protected static TDelegate Unroll<TDelegate>(TDelegate callback, int unrollFactor)
56+
{
57+
if (callback == null)
58+
throw new ArgumentNullException(nameof(callback));
59+
60+
if (unrollFactor <= 1)
61+
return callback;
62+
63+
return (TDelegate)(object)Delegate.Combine(
64+
Enumerable.Repeat((Delegate)(object)callback, unrollFactor).ToArray());
65+
}
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)