From e087c6fbded836f9207578cd1813cb2b0afee188 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Mar 2022 06:53:22 -0500 Subject: [PATCH 01/22] Added AwaitHelper to properly wait for ValueTasks. Updated BenchmarkType and InProcess toolchains to use AwaitHelper. --- .../Code/DeclarationsProvider.cs | 14 ++-- src/BenchmarkDotNet/Helpers/AwaitHelper.cs | 78 +++++++++++++++++++ .../IlGeneratorStatementExtensions.cs | 31 ++++++++ .../Templates/BenchmarkType.txt | 4 + .../ConsumableTypeInfo.cs | 32 ++++---- .../Emitters/RunnableEmitter.cs | 68 +++++++++------- .../Runnable/RunnableConstants.cs | 1 + .../BenchmarkActionFactory_Implementations.cs | 9 ++- .../BenchmarkActionFactory_Implementations.cs | 9 ++- .../Validators/ExecutionValidatorBase.cs | 2 +- 10 files changed, 183 insertions(+), 65 deletions(-) create mode 100644 src/BenchmarkDotNet/Helpers/AwaitHelper.cs diff --git a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs index ddf78eb572..21e3ce54c1 100644 --- a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs +++ b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs @@ -63,7 +63,7 @@ private string GetMethodName(MethodInfo method) (method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) || method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>)))) { - return $"() => {method.Name}().GetAwaiter().GetResult()"; + return $"() => awaitHelper.GetResult({method.Name}())"; } return method.Name; @@ -149,12 +149,10 @@ internal class TaskDeclarationsProvider : VoidDeclarationsProvider { public TaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - // we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way, - // and will eventually throw actual exception, not aggregated one public override string WorkloadMethodDelegate(string passArguments) - => $"({passArguments}) => {{ {Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult(); }}"; + => $"({passArguments}) => {{ awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}"; - public override string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult()"; + public override string GetWorkloadMethodCall(string passArguments) => $"awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))"; protected override Type WorkloadMethodReturnType => typeof(void); } @@ -168,11 +166,9 @@ public GenericTaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) protected override Type WorkloadMethodReturnType => Descriptor.WorkloadMethod.ReturnType.GetTypeInfo().GetGenericArguments().Single(); - // we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way, - // and will eventually throw actual exception, not aggregated one public override string WorkloadMethodDelegate(string passArguments) - => $"({passArguments}) => {{ return {Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult(); }}"; + => $"({passArguments}) => {{ return awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}"; - public override string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments}).GetAwaiter().GetResult()"; + public override string GetWorkloadMethodCall(string passArguments) => $"awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))"; } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs new file mode 100644 index 0000000000..13fe4881f4 --- /dev/null +++ b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading.Tasks; + +namespace BenchmarkDotNet.Helpers +{ + internal class AwaitHelper + { + + private readonly object awaiterLock = new object(); + private readonly Action awaiterCallback; + private bool awaiterCompleted; + + public AwaitHelper() + { + awaiterCallback = AwaiterCallback; + } + + private void AwaiterCallback() + { + lock (awaiterLock) + { + awaiterCompleted = true; + System.Threading.Monitor.Pulse(awaiterLock); + } + } + + // we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way, + // and will eventually throw actual exception, not aggregated one + public void GetResult(Task task) + { + task.GetAwaiter().GetResult(); + } + + public T GetResult(Task task) + { + return task.GetAwaiter().GetResult(); + } + + // It is illegal to call GetResult from an uncomplete ValueTask, so we must hook up a callback. + public void GetResult(ValueTask task) + { + var awaiter = task.GetAwaiter(); + if (!awaiter.IsCompleted) + { + lock (awaiterLock) + { + awaiterCompleted = false; + awaiter.UnsafeOnCompleted(awaiterCallback); + // Check if the callback executed synchronously before blocking. + if (awaiterCompleted) + { + System.Threading.Monitor.Wait(awaiterLock); + } + } + } + awaiter.GetResult(); + } + + public T GetResult(ValueTask task) + { + var awaiter = task.GetAwaiter(); + if (!awaiter.IsCompleted) + { + lock (awaiterLock) + { + awaiterCompleted = false; + awaiter.UnsafeOnCompleted(awaiterCallback); + // Check if the callback executed synchronously before blocking. + if (awaiterCompleted) + { + System.Threading.Monitor.Wait(awaiterLock); + } + } + } + return awaiter.GetResult(); + } + } +} diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs index 6d601e6495..84b773522a 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs @@ -42,6 +42,37 @@ public static void EmitVoidReturn(this ILGenerator ilBuilder, MethodBuilder meth ilBuilder.Emit(OpCodes.Ret); } + public static void EmitSetFieldToNewInstance( + this ILGenerator ilBuilder, + FieldBuilder delegateField, + Type instanceType) + { + if (delegateField.IsStatic) + throw new ArgumentException("The field should be instance field", nameof(delegateField)); + + if (instanceType != null) + { + /* + IL_0006: ldarg.0 + IL_0007: newobj instance void BenchmarkDotNet.Helpers.AwaitHelper::.ctor() + IL_000c: stfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Autogenerated.Runnable_0::awaitHelper + */ + var ctor = instanceType.GetConstructor(Array.Empty()); + if (ctor == null) + throw new InvalidOperationException($"Bug: instanceType {instanceType.Name} does not have a 0-parameter accessible constructor."); + + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Newobj, ctor); + ilBuilder.Emit(OpCodes.Stfld, delegateField); + } + else + { + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldnull); + ilBuilder.Emit(OpCodes.Stfld, delegateField); + } + } + public static void EmitSetDelegateToThisField( this ILGenerator ilBuilder, FieldBuilder delegateField, diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index d8f15f9138..7a215a4c93 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -57,6 +57,8 @@ public Runnable_$ID$() { + awaitHelper = new BenchmarkDotNet.Helpers.AwaitHelper(); + globalSetupAction = $GlobalSetupMethodName$; globalCleanupAction = $GlobalCleanupMethodName$; iterationSetupAction = $IterationSetupMethodName$; @@ -66,6 +68,8 @@ $InitializeArgumentFields$ } + private readonly BenchmarkDotNet.Helpers.AwaitHelper awaitHelper; + private System.Action globalSetupAction; private System.Action globalCleanupAction; private System.Action iterationSetupAction; diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs index 060c977014..13823ad33a 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs @@ -17,28 +17,24 @@ public ConsumableTypeInfo(Type methodReturnType) OriginMethodReturnType = methodReturnType; - // Please note this code does not support await over extension methods. + // Only support (Value)Task for parity with other toolchains (and so we can use AwaitHelper). + IsAwaitable = methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask) + || (methodReturnType.GetTypeInfo().IsGenericType + && (methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) + || methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>))); + var getAwaiterMethod = methodReturnType.GetMethod(nameof(Task.GetAwaiter), BindingFlagsPublicInstance); - if (getAwaiterMethod == null) + if (!IsAwaitable || getAwaiterMethod == null) { WorkloadMethodReturnType = methodReturnType; } else { - var getResultMethod = getAwaiterMethod + GetResultMethod = typeof(Helpers.AwaitHelper).GetMethod(nameof(Helpers.AwaitHelper.GetResult), BindingFlagsPublicInstance, null, new Type[1] { methodReturnType }, null); + WorkloadMethodReturnType = getAwaiterMethod .ReturnType - .GetMethod(nameof(TaskAwaiter.GetResult), BindingFlagsPublicInstance); - - if (getResultMethod == null) - { - WorkloadMethodReturnType = methodReturnType; - } - else - { - WorkloadMethodReturnType = getResultMethod.ReturnType; - GetAwaiterMethod = getAwaiterMethod; - GetResultMethod = getResultMethod; - } + .GetMethod(nameof(TaskAwaiter.GetResult), BindingFlagsPublicInstance) + .ReturnType; } if (WorkloadMethodReturnType == null) @@ -78,9 +74,7 @@ public ConsumableTypeInfo(Type methodReturnType) [NotNull] public Type OverheadMethodReturnType { get; } - [CanBeNull] - public MethodInfo GetAwaiterMethod { get; } - [CanBeNull] + [NotNull] public MethodInfo GetResultMethod { get; } public bool IsVoid { get; } @@ -89,6 +83,6 @@ public ConsumableTypeInfo(Type methodReturnType) [CanBeNull] public FieldInfo WorkloadConsumableField { get; } - public bool IsAwaitable => GetAwaiterMethod != null && GetResultMethod != null; + public bool IsAwaitable { get; } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index 91e97cc71e..29e2135216 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -246,6 +246,7 @@ private static void EmitNoArgsMethodCallPopReturn( private ConsumableTypeInfo consumableInfo; private ConsumeEmitter consumeEmitter; + private FieldBuilder awaitHelperField; private FieldBuilder globalSetupActionField; private FieldBuilder globalCleanupActionField; private FieldBuilder iterationSetupActionField; @@ -412,6 +413,8 @@ private Type EmitWorkloadDelegateType() private void DefineFields() { + awaitHelperField = + runnableBuilder.DefineField(AwaitHelperFieldName, typeof(Helpers.AwaitHelper), FieldAttributes.Private); globalSetupActionField = runnableBuilder.DefineField(GlobalSetupActionFieldName, typeof(Action), FieldAttributes.Private); globalCleanupActionField = @@ -582,20 +585,15 @@ private MethodInfo EmitWorkloadImplementation(string methodName) args); args = methodBuilder.GetEmitParameters(args); var callResultType = consumableInfo.OriginMethodReturnType; - var awaiterType = consumableInfo.GetAwaiterMethod?.ReturnType - ?? throw new InvalidOperationException($"Bug: {nameof(consumableInfo.GetAwaiterMethod)} is null"); var ilBuilder = methodBuilder.GetILGenerator(); /* .locals init ( - [0] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1 + [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task ) */ - var callResultLocal = - ilBuilder.DeclareOptionalLocalForInstanceCall(callResultType, consumableInfo.GetAwaiterMethod); - var awaiterLocal = - ilBuilder.DeclareOptionalLocalForInstanceCall(awaiterType, consumableInfo.GetResultMethod); + var callResultLocal = ilBuilder.DeclareLocal(callResultType); /* // return TaskSample(arg0). ... ; @@ -609,14 +607,19 @@ [0] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1 ilBuilder.Emit(OpCodes.Call, Descriptor.WorkloadMethod); /* - // ... .GetAwaiter().GetResult(); - IL_0007: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1 class [mscorlib]System.Threading.Tasks.Task`1::GetAwaiter() - IL_000c: stloc.0 - IL_000d: ldloca.s 0 - IL_000f: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() - */ - ilBuilder.EmitInstanceCallThisValueOnStack(callResultLocal, consumableInfo.GetAwaiterMethod); - ilBuilder.EmitInstanceCallThisValueOnStack(awaiterLocal, consumableInfo.GetResultMethod); + // awaitHelper.GetResult(...); + IL_0006: stloc.0 + IL_0007: ldarg.0 + IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper + IL_000d: ldloc.0 + IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) + */ + + ilBuilder.EmitStloc(callResultLocal); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); + ilBuilder.Emit(OpCodes.Ldloc_0); + ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod); /* IL_0014: ret @@ -832,16 +835,15 @@ .locals init ( var argLocals = EmitDeclareArgLocals(ilBuilder, skipFirstArg); LocalBuilder callResultLocal = null; - LocalBuilder awaiterLocal = null; if (consumableInfo.IsAwaitable) { var callResultType = consumableInfo.OriginMethodReturnType; - var awaiterType = consumableInfo.GetAwaiterMethod?.ReturnType - ?? throw new InvalidOperationException($"Bug: {nameof(consumableInfo.GetAwaiterMethod)} is null"); - callResultLocal = - ilBuilder.DeclareOptionalLocalForInstanceCall(callResultType, consumableInfo.GetAwaiterMethod); - awaiterLocal = - ilBuilder.DeclareOptionalLocalForInstanceCall(awaiterType, consumableInfo.GetResultMethod); + /* + .locals init ( + [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task + ) + */ + callResultLocal = ilBuilder.DeclareLocal(callResultType); } consumeEmitter.DeclareDisassemblyDiagnoserLocals(ilBuilder); @@ -883,14 +885,19 @@ .locals init ( if (consumableInfo.IsAwaitable) { /* - // ... .GetAwaiter().GetResult(); - IL_0007: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1 class [mscorlib]System.Threading.Tasks.Task`1::GetAwaiter() - IL_000c: stloc.0 - IL_000d: ldloca.s 0 - IL_000f: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1::GetResult() - */ - ilBuilder.EmitInstanceCallThisValueOnStack(callResultLocal, consumableInfo.GetAwaiterMethod); - ilBuilder.EmitInstanceCallThisValueOnStack(awaiterLocal, consumableInfo.GetResultMethod); + // awaitHelper.GetResult(...); + IL_0006: stloc.0 + IL_0007: ldarg.0 + IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper + IL_000d: ldloc.0 + IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) + */ + + ilBuilder.EmitStloc(callResultLocal); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); + ilBuilder.Emit(OpCodes.Ldloc_0); + ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod); } /* @@ -953,6 +960,7 @@ private void EmitCtorBody() consumeEmitter.OnEmitCtorBody(ctorMethod, ilBuilder); + ilBuilder.EmitSetFieldToNewInstance(awaitHelperField, typeof(Helpers.AwaitHelper)); ilBuilder.EmitSetDelegateToThisField(globalSetupActionField, globalSetupMethod); ilBuilder.EmitSetDelegateToThisField(globalCleanupActionField, globalCleanupMethod); ilBuilder.EmitSetDelegateToThisField(iterationSetupActionField, iterationSetupMethod); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs index c6e8cd8ae1..920fabb03e 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs @@ -16,6 +16,7 @@ public class RunnableConstants public const string ArgFieldPrefix = "__argField"; public const string ArgParamPrefix = "arg"; + public const string AwaitHelperFieldName = "awaitHelper"; public const string GlobalSetupActionFieldName = "globalSetupAction"; public const string GlobalCleanupActionFieldName = "globalCleanupAction"; public const string IterationSetupActionFieldName = "iterationSetupAction"; diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs index eef3ce8997..a139adbafc 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs @@ -69,6 +69,7 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionTask : BenchmarkActionBase { + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); private readonly Func startTaskCallback; private readonly Action callback; private readonly Action unrolledCallback; @@ -97,7 +98,7 @@ public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) private void Overhead() { } // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate - private void ExecuteBlocking() => startTaskCallback.Invoke().GetAwaiter().GetResult(); + private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); private void InvokeMultipleHardcoded(long repeatCount) { @@ -108,6 +109,7 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionTask : BenchmarkActionBase { + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); private readonly Func> startTaskCallback; private readonly Func callback; private readonly Func unrolledCallback; @@ -135,7 +137,7 @@ public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) private T Overhead() => default; // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => startTaskCallback().GetAwaiter().GetResult(); + private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); private void InvokeSingleHardcoded() => result = callback(); @@ -150,6 +152,7 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionValueTask : BenchmarkActionBase { + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); private readonly Func> startTaskCallback; private readonly Func callback; private readonly Func unrolledCallback; @@ -178,7 +181,7 @@ public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFa private T Overhead() => default; // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => startTaskCallback().GetAwaiter().GetResult(); + private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); private void InvokeSingleHardcoded() => result = callback(); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs index 5ca5592e1e..9cb9d4c2a2 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs @@ -83,6 +83,7 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionTask : BenchmarkActionBase { + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); private readonly Func startTaskCallback; private readonly Action callback; private readonly Action unrolledCallback; @@ -117,7 +118,7 @@ public BenchmarkActionTask(object instance, MethodInfo method, BenchmarkActionCo private void Overhead() { } // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate - private void ExecuteBlocking() => startTaskCallback.Invoke().GetAwaiter().GetResult(); + private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); private void InvokeMultipleHardcoded(long repeatCount) { @@ -128,6 +129,7 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionTask : BenchmarkActionBase { + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); private readonly Func> startTaskCallback; private readonly Func callback; private readonly Func unrolledCallback; @@ -162,7 +164,7 @@ public BenchmarkActionTask(object instance, MethodInfo method, BenchmarkActionCo private T Overhead() => default; // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => startTaskCallback().GetAwaiter().GetResult(); + private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback()); private void InvokeSingleHardcoded() => result = callback(); @@ -177,6 +179,7 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionValueTask : BenchmarkActionBase { + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); private readonly Func> startTaskCallback; private readonly Func callback; private readonly Func unrolledCallback; @@ -211,7 +214,7 @@ public BenchmarkActionValueTask(object instance, MethodInfo method, BenchmarkAct private T Overhead() => default; // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => startTaskCallback().GetAwaiter().GetResult(); + private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback()); private void InvokeSingleHardcoded() => result = callback(); diff --git a/src/BenchmarkDotNet/Validators/ExecutionValidatorBase.cs b/src/BenchmarkDotNet/Validators/ExecutionValidatorBase.cs index 92d7422ae2..371bdb5ece 100644 --- a/src/BenchmarkDotNet/Validators/ExecutionValidatorBase.cs +++ b/src/BenchmarkDotNet/Validators/ExecutionValidatorBase.cs @@ -143,7 +143,7 @@ private void TryToGetTaskResult(object result) } else if (result is ValueTask valueTask) { - valueTask.GetAwaiter().GetResult(); + valueTask.AsTask().GetAwaiter().GetResult(); } } From 8d4343159c88aa78c5a58281a6f50457f3929fdb Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Mar 2022 08:46:36 -0500 Subject: [PATCH 02/22] Fixed (Value)Task for InProcessEmitToolchain. --- src/BenchmarkDotNet/Helpers/AwaitHelper.cs | 2 +- .../ConsumableTypeInfo.cs | 28 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs index 13fe4881f4..1ad6d67747 100644 --- a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs +++ b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs @@ -3,7 +3,7 @@ namespace BenchmarkDotNet.Helpers { - internal class AwaitHelper + public class AwaitHelper { private readonly object awaiterLock = new object(); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs index 13823ad33a..644a7397a1 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs @@ -1,6 +1,8 @@ using BenchmarkDotNet.Engines; using JetBrains.Annotations; using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -23,18 +25,36 @@ public ConsumableTypeInfo(Type methodReturnType) && (methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) || methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>))); - var getAwaiterMethod = methodReturnType.GetMethod(nameof(Task.GetAwaiter), BindingFlagsPublicInstance); - if (!IsAwaitable || getAwaiterMethod == null) + if (!IsAwaitable) { WorkloadMethodReturnType = methodReturnType; } else { - GetResultMethod = typeof(Helpers.AwaitHelper).GetMethod(nameof(Helpers.AwaitHelper.GetResult), BindingFlagsPublicInstance, null, new Type[1] { methodReturnType }, null); - WorkloadMethodReturnType = getAwaiterMethod + WorkloadMethodReturnType = methodReturnType + .GetMethod(nameof(Task.GetAwaiter), BindingFlagsPublicInstance) .ReturnType .GetMethod(nameof(TaskAwaiter.GetResult), BindingFlagsPublicInstance) .ReturnType; + if (methodReturnType.GetTypeInfo().IsGenericType) + { + Type compareType = methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) + ? typeof(Task<>) + : typeof(ValueTask<>); + GetResultMethod = typeof(Helpers.AwaitHelper).GetMethods(BindingFlagsPublicInstance) + .First(m => + { + if (m.Name != nameof(Helpers.AwaitHelper.GetResult)) return false; + Type paramType = m.GetParameters().First().ParameterType; + // We have to compare the types indirectly, == check doesn't work. + return paramType.Assembly == compareType.Assembly && paramType.Namespace == compareType.Namespace && paramType.Name == compareType.Name; + }) + .MakeGenericMethod(new Type[1] { WorkloadMethodReturnType }); + } + else + { + GetResultMethod = typeof(Helpers.AwaitHelper).GetMethod(nameof(Helpers.AwaitHelper.GetResult), BindingFlagsPublicInstance, null, new Type[1] { methodReturnType }, null); + } } if (WorkloadMethodReturnType == null) From e63ca1ec90f558e828aa2575b4331741eb5442a2 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Mar 2022 11:13:32 -0500 Subject: [PATCH 03/22] Added support for `(Value)Task` Setup and Cleanup in InProcessEmitToolchain. Added support for `ValueTask` benchmark in InProcess.NoEmit. Added tests. --- .../Emitters/RunnableEmitter.cs | 119 ++++++++- .../BenchmarkActionFactory.cs | 3 + .../BenchmarkActionFactory_Implementations.cs | 40 +++ .../InProcess/BenchmarkActionFactory.cs | 3 + .../BenchmarkActionFactory_Implementations.cs | 46 ++++ .../InProcessEmitTest.cs | 14 + .../InProcessTest.cs | 240 ++++++++++++++++-- 7 files changed, 426 insertions(+), 39 deletions(-) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index 29e2135216..0f95554272 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -245,6 +245,10 @@ private static void EmitNoArgsMethodCallPopReturn( private TypeBuilder runnableBuilder; private ConsumableTypeInfo consumableInfo; private ConsumeEmitter consumeEmitter; + private ConsumableTypeInfo globalSetupReturnInfo; + private ConsumableTypeInfo globalCleanupReturnInfo; + private ConsumableTypeInfo iterationSetupReturnInfo; + private ConsumableTypeInfo iterationCleanupReturnInfo; private FieldBuilder awaitHelperField; private FieldBuilder globalSetupActionField; @@ -358,6 +362,10 @@ private void InitForEmitRunnable(BenchmarkBuildInfo newBenchmark) consumableInfo = new ConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.WorkloadMethod.ReturnType); consumeEmitter = ConsumeEmitter.GetConsumeEmitter(consumableInfo); + globalSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalSetupMethod?.ReturnType); + globalCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalCleanupMethod?.ReturnType); + iterationSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.IterationSetupMethod?.ReturnType); + iterationCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.IterationCleanupMethod?.ReturnType); // Init types runnableBuilder = DefineRunnableTypeBuilder(benchmark, moduleBuilder); @@ -365,6 +373,11 @@ private void InitForEmitRunnable(BenchmarkBuildInfo newBenchmark) workloadDelegateType = EmitWorkloadDelegateType(); } + private static ConsumableTypeInfo GetConsumableTypeInfo(Type methodReturnType) + { + return methodReturnType == null ? null : new ConsumableTypeInfo(methodReturnType); + } + private Type EmitOverheadDelegateType() { // .class public auto ansi sealed BenchmarkDotNet.Autogenerated.Runnable_0OverheadDelegate @@ -924,34 +937,112 @@ private void EmitSetupCleanupMethods() { // Emit Setup/Cleanup methods // We emit empty method instead of EmptyAction = "() => { }" - globalSetupMethod = EmitWrapperMethod( - GlobalSetupMethodName, - Descriptor.GlobalSetupMethod); - globalCleanupMethod = EmitWrapperMethod( - GlobalCleanupMethodName, - Descriptor.GlobalCleanupMethod); - iterationSetupMethod = EmitWrapperMethod( - IterationSetupMethodName, - Descriptor.IterationSetupMethod); - iterationCleanupMethod = EmitWrapperMethod( - IterationCleanupMethodName, - Descriptor.IterationCleanupMethod); + globalSetupMethod = EmitWrapperMethod(GlobalSetupMethodName, Descriptor.GlobalSetupMethod, globalSetupReturnInfo); + globalCleanupMethod = EmitWrapperMethod(GlobalCleanupMethodName, Descriptor.GlobalCleanupMethod, globalCleanupReturnInfo); + iterationSetupMethod = EmitWrapperMethod(IterationSetupMethodName, Descriptor.IterationSetupMethod, iterationSetupReturnInfo); + iterationCleanupMethod = EmitWrapperMethod(IterationCleanupMethodName, Descriptor.IterationCleanupMethod, iterationCleanupReturnInfo); } - private MethodBuilder EmitWrapperMethod(string methodName, MethodInfo optionalTargetMethod) + private MethodBuilder EmitWrapperMethod(string methodName, MethodInfo optionalTargetMethod, ConsumableTypeInfo returnTypeInfo) { var methodBuilder = runnableBuilder.DefinePrivateVoidInstanceMethod(methodName); var ilBuilder = methodBuilder.GetILGenerator(); if (optionalTargetMethod != null) - EmitNoArgsMethodCallPopReturn(methodBuilder, optionalTargetMethod, ilBuilder, forceDirectCall: true); + { + if (returnTypeInfo?.IsAwaitable == true) + { + EmitAwaitableSetupTeardown(methodBuilder, optionalTargetMethod, ilBuilder, returnTypeInfo); + } + else + { + EmitNoArgsMethodCallPopReturn(methodBuilder, optionalTargetMethod, ilBuilder, forceDirectCall: true); + } + } ilBuilder.EmitVoidReturn(methodBuilder); return methodBuilder; } + private void EmitAwaitableSetupTeardown( + MethodBuilder methodBuilder, + MethodInfo targetMethod, + ILGenerator ilBuilder, + ConsumableTypeInfo returnTypeInfo) + { + if (targetMethod == null) + throw new ArgumentNullException(nameof(targetMethod)); + + /* + // call for instance void + // GlobalSetup(); + IL_0000: ldarg.0 + IL_0001: call instance void [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalSetup() + */ + /* + // call for static with return value + // GlobalSetup(); + IL_0000: call string [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalCleanup() + IL_0005: pop + */ + LocalBuilder callResultLocal; + if (targetMethod.IsStatic) + { + ilBuilder.Emit(OpCodes.Ldarg_0); + var callResultType = returnTypeInfo.OriginMethodReturnType; + + /* + .locals init ( + [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task + ) + */ + callResultLocal = ilBuilder.DeclareLocal(callResultType); + + ilBuilder.Emit(OpCodes.Call, targetMethod); + + } + else if (methodBuilder.IsStatic) + { + throw new InvalidOperationException( + $"[BUG] Static method {methodBuilder.Name} tries to call instance member {targetMethod.Name}"); + } + else + { + ilBuilder.Emit(OpCodes.Ldarg_0); + var callResultType = returnTypeInfo.OriginMethodReturnType; + + /* + .locals init ( + [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task + ) + */ + callResultLocal = ilBuilder.DeclareLocal(callResultType); + + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Call, targetMethod); + } + + /* + // awaitHelper.GetResult(...); + IL_0006: stloc.0 + IL_0007: ldarg.0 + IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper + IL_000d: ldloc.0 + IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) + */ + + ilBuilder.EmitStloc(callResultLocal); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); + ilBuilder.Emit(OpCodes.Ldloc_0); + ilBuilder.Emit(OpCodes.Callvirt, returnTypeInfo.GetResultMethod); + + if (targetMethod.ReturnType != typeof(void)) + ilBuilder.Emit(OpCodes.Pop); + } + private void EmitCtorBody() { var ilBuilder = ctorMethod.GetILGenerator(); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory.cs index ef351975a1..130c67dbf3 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory.cs @@ -32,6 +32,9 @@ private static BenchmarkAction CreateCore( if (resultType == typeof(Task)) return new BenchmarkActionTask(resultInstance, targetMethod, unrollFactor); + if (resultType == typeof(ValueTask)) + return new BenchmarkActionValueTask(resultInstance, targetMethod, unrollFactor); + if (resultType.GetTypeInfo().IsGenericType) { var genericType = resultType.GetGenericTypeDefinition(); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs index a139adbafc..d0f1df8daf 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs @@ -150,6 +150,46 @@ private void InvokeMultipleHardcoded(long repeatCount) public override object LastRunResult => result; } + internal class BenchmarkActionValueTask : BenchmarkActionBase + { + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); + private readonly Func startTaskCallback; + private readonly Action callback; + private readonly Action unrolledCallback; + + public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFactor) + { + bool isIdle = method == null; + if (!isIdle) + { + startTaskCallback = CreateWorkload>(instance, method); + callback = ExecuteBlocking; + } + else + { + callback = Overhead; + } + + InvokeSingle = callback; + + unrolledCallback = Unroll(callback, unrollFactor); + InvokeMultiple = InvokeMultipleHardcoded; + + } + + // must be kept in sync with VoidDeclarationsProvider.IdleImplementation + private void Overhead() { } + + // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate + private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); + + private void InvokeMultipleHardcoded(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + unrolledCallback(); + } + } + internal class BenchmarkActionValueTask : BenchmarkActionBase { private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs index 0774339ad2..987a4c81b6 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs @@ -34,6 +34,9 @@ private static BenchmarkAction CreateCore( if (resultType == typeof(Task)) return new BenchmarkActionTask(resultInstance, targetMethod, codegenMode, unrollFactor); + if (resultType == typeof(ValueTask)) + return new BenchmarkActionValueTask(resultInstance, targetMethod, codegenMode, unrollFactor); + if (resultType.GetTypeInfo().IsGenericType) { var genericType = resultType.GetGenericTypeDefinition(); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs index 9cb9d4c2a2..5b39e445f5 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs @@ -177,6 +177,52 @@ private void InvokeMultipleHardcoded(long repeatCount) public override object LastRunResult => result; } + internal class BenchmarkActionValueTask : BenchmarkActionBase + { + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); + private readonly Func startTaskCallback; + private readonly Action callback; + private readonly Action unrolledCallback; + + public BenchmarkActionValueTask(object instance, MethodInfo method, BenchmarkActionCodegen codegenMode, int unrollFactor) + { + bool isIdle = method == null; + if (!isIdle) + { + startTaskCallback = CreateWorkload>(instance, method); + callback = ExecuteBlocking; + } + else + { + callback = Overhead; + } + + InvokeSingle = callback; + + if (UseFallbackCode(codegenMode, unrollFactor)) + { + unrolledCallback = Unroll(callback, unrollFactor); + InvokeMultiple = InvokeMultipleHardcoded; + } + else + { + InvokeMultiple = EmitInvokeMultiple(this, nameof(callback), null, unrollFactor); + } + } + + // must be kept in sync with VoidDeclarationsProvider.IdleImplementation + private void Overhead() { } + + // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate + private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); + + private void InvokeMultipleHardcoded(long repeatCount) + { + for (long i = 0; i < repeatCount; i++) + unrolledCallback(); + } + } + internal class BenchmarkActionValueTask : BenchmarkActionBase { private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs index 227e6411ed..2a07e942e9 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs @@ -149,6 +149,13 @@ public async Task InvokeOnceTaskAsync() Interlocked.Increment(ref Counter); } + [Benchmark] + public async ValueTask InvokeOnceValueTaskAsync() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + [Benchmark] public string InvokeOnceRefType() { @@ -191,6 +198,13 @@ public static async Task InvokeOnceStaticTaskAsync() Interlocked.Increment(ref Counter); } + [Benchmark] + public static async ValueTask InvokeOnceStaticValueTaskAsync() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + [Benchmark] public static string InvokeOnceStaticRefType() { diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs index 89120e84c4..6415cf181f 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs @@ -44,6 +44,9 @@ public InProcessTest(ITestOutputHelper output) : base(output) [Fact] public void BenchmarkActionTaskSupported() => TestInvoke(x => x.InvokeOnceTaskAsync(), UnrollFactor, null); + [Fact] + public void BenchmarkActionValueTaskSupported() => TestInvoke(x => x.InvokeOnceValueTaskAsync(), UnrollFactor, null); + [Fact] public void BenchmarkActionRefTypeSupported() => TestInvoke(x => x.InvokeOnceRefType(), UnrollFactor, StringResult); @@ -56,6 +59,30 @@ public InProcessTest(ITestOutputHelper output) : base(output) [Fact] public void BenchmarkActionValueTaskOfTSupported() => TestInvoke(x => x.InvokeOnceValueTaskOfT(), UnrollFactor, DecimalResult); + [Fact] + public void BenchmarkActionGlobalSetupTaskSupported() => TestInvokeSetupCleanupTask(x => BenchmarkSetupCleanupTask.GlobalSetup(), UnrollFactor); + + [Fact] + public void BenchmarkActionGlobalCleanupTaskSupported() => TestInvokeSetupCleanupTask(x => x.GlobalCleanup(), UnrollFactor); + + [Fact] + public void BenchmarkActionIterationSetupTaskSupported() => TestInvokeSetupCleanupTask(x => BenchmarkSetupCleanupTask.GlobalSetup(), UnrollFactor); + + [Fact] + public void BenchmarkActionIterationCleanupTaskSupported() => TestInvokeSetupCleanupTask(x => x.GlobalCleanup(), UnrollFactor); + + [Fact] + public void BenchmarkActionGlobalSetupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => BenchmarkSetupCleanupValueTask.GlobalSetup(), UnrollFactor); + + [Fact] + public void BenchmarkActionGlobalCleanupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => x.GlobalCleanup(), UnrollFactor); + + [Fact] + public void BenchmarkActionIterationSetupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => BenchmarkSetupCleanupValueTask.GlobalSetup(), UnrollFactor); + + [Fact] + public void BenchmarkActionIterationCleanupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => x.GlobalCleanup(), UnrollFactor); + [AssertionMethod] private void TestInvoke(Expression> methodCall, int unrollFactor) { @@ -64,34 +91,34 @@ private void TestInvoke(Expression> methodCall, int un // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); - TestInvoke(action, unrollFactor, false, null); + TestInvoke(action, unrollFactor, false, null, ref BenchmarkAllCases.Counter); action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.DelegateCombine, unrollFactor); - TestInvoke(action, unrollFactor, false, null); + TestInvoke(action, unrollFactor, false, null, ref BenchmarkAllCases.Counter); // Idle mode action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkAllCases.Counter); action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.DelegateCombine, unrollFactor); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkAllCases.Counter); // GlobalSetup/GlobalCleanup action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, 1, false, null); + TestInvoke(action, 1, false, null, ref BenchmarkAllCases.Counter); action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, 1, false, null); + TestInvoke(action, 1, false, null, ref BenchmarkAllCases.Counter); // GlobalSetup/GlobalCleanup (empty) descriptor = new Descriptor(typeof(BenchmarkAllCases), targetMethod); action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkAllCases.Counter); action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkAllCases()); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkAllCases.Counter); // Dummy (just in case something may broke) action = BenchmarkActionFactory.CreateDummy(); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkAllCases.Counter); action = BenchmarkActionFactory.CreateDummy(); - TestInvoke(action, unrollFactor, true, null); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkAllCases.Counter); } [AssertionMethod] @@ -102,9 +129,9 @@ private void TestInvoke(Expression> methodCall, in // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); - TestInvoke(action, unrollFactor, false, expectedResult); + TestInvoke(action, unrollFactor, false, expectedResult, ref BenchmarkAllCases.Counter); action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.DelegateCombine, unrollFactor); - TestInvoke(action, unrollFactor, false, expectedResult); + TestInvoke(action, unrollFactor, false, expectedResult, ref BenchmarkAllCases.Counter); // Idle mode @@ -113,17 +140,93 @@ private void TestInvoke(Expression> methodCall, in object idleExpected; if (isValueTask) idleExpected = GetDefault(typeof(T).GetGenericArguments()[0]); + else if (expectedResult == null || typeof(T) == typeof(Task) || typeof(T) == typeof(ValueTask)) + idleExpected = null; else if (typeof(T).GetTypeInfo().IsValueType) idleExpected = 0; - else if (expectedResult == null || typeof(T) == typeof(Task)) - idleExpected = null; else idleExpected = GetDefault(expectedResult.GetType()); action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); - TestInvoke(action, unrollFactor, true, idleExpected); + TestInvoke(action, unrollFactor, true, idleExpected, ref BenchmarkAllCases.Counter); action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.DelegateCombine, unrollFactor); - TestInvoke(action, unrollFactor, true, idleExpected); + TestInvoke(action, unrollFactor, true, idleExpected, ref BenchmarkAllCases.Counter); + } + + [AssertionMethod] + private void TestInvokeSetupCleanupTask(Expression> methodCall, int unrollFactor) + { + var targetMethod = ((MethodCallExpression) methodCall.Body).Method; + var descriptor = new Descriptor(typeof(BenchmarkSetupCleanupTask), targetMethod, targetMethod, targetMethod, targetMethod, targetMethod); + + // Run mode + var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkSetupCleanupTask(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); + TestInvoke(action, unrollFactor, false, null, ref BenchmarkSetupCleanupTask.Counter); + action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkSetupCleanupTask(), BenchmarkActionCodegen.DelegateCombine, unrollFactor); + TestInvoke(action, unrollFactor, false, null, ref BenchmarkSetupCleanupTask.Counter); + + // Idle mode + action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkSetupCleanupTask(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupTask.Counter); + action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkSetupCleanupTask(), BenchmarkActionCodegen.DelegateCombine, unrollFactor); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupTask.Counter); + + // GlobalSetup/GlobalCleanup + action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkSetupCleanupTask()); + TestInvoke(action, 1, false, null, ref BenchmarkSetupCleanupTask.Counter); + action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkSetupCleanupTask()); + TestInvoke(action, 1, false, null, ref BenchmarkSetupCleanupTask.Counter); + + // GlobalSetup/GlobalCleanup (empty) + descriptor = new Descriptor(typeof(BenchmarkSetupCleanupTask), targetMethod); + action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkSetupCleanupTask()); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupTask.Counter); + action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkSetupCleanupTask()); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupTask.Counter); + + // Dummy (just in case something may broke) + action = BenchmarkActionFactory.CreateDummy(); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupTask.Counter); + action = BenchmarkActionFactory.CreateDummy(); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupTask.Counter); + } + + [AssertionMethod] + private void TestInvokeSetupCleanupValueTask(Expression> methodCall, int unrollFactor) + { + var targetMethod = ((MethodCallExpression) methodCall.Body).Method; + var descriptor = new Descriptor(typeof(BenchmarkSetupCleanupValueTask), targetMethod, targetMethod, targetMethod, targetMethod, targetMethod); + + // Run mode + var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkSetupCleanupValueTask(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); + TestInvoke(action, unrollFactor, false, null, ref BenchmarkSetupCleanupValueTask.Counter); + action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkSetupCleanupValueTask(), BenchmarkActionCodegen.DelegateCombine, unrollFactor); + TestInvoke(action, unrollFactor, false, null, ref BenchmarkSetupCleanupValueTask.Counter); + + // Idle mode + action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkSetupCleanupValueTask(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupValueTask.Counter); + action = BenchmarkActionFactory.CreateOverhead(descriptor, new BenchmarkSetupCleanupValueTask(), BenchmarkActionCodegen.DelegateCombine, unrollFactor); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupValueTask.Counter); + + // GlobalSetup/GlobalCleanup + action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkSetupCleanupValueTask()); + TestInvoke(action, 1, false, null, ref BenchmarkSetupCleanupValueTask.Counter); + action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkSetupCleanupValueTask()); + TestInvoke(action, 1, false, null, ref BenchmarkSetupCleanupValueTask.Counter); + + // GlobalSetup/GlobalCleanup (empty) + descriptor = new Descriptor(typeof(BenchmarkSetupCleanupValueTask), targetMethod); + action = BenchmarkActionFactory.CreateGlobalSetup(descriptor, new BenchmarkSetupCleanupValueTask()); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupValueTask.Counter); + action = BenchmarkActionFactory.CreateGlobalCleanup(descriptor, new BenchmarkSetupCleanupValueTask()); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupValueTask.Counter); + + // Dummy (just in case something may broke) + action = BenchmarkActionFactory.CreateDummy(); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupValueTask.Counter); + action = BenchmarkActionFactory.CreateDummy(); + TestInvoke(action, unrollFactor, true, null, ref BenchmarkSetupCleanupValueTask.Counter); } private static object GetDefault(Type type) @@ -136,36 +239,36 @@ private static object GetDefault(Type type) } [AssertionMethod] - private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool isIdle, object expectedResult) + private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool isIdle, object expectedResult, ref int counter) { try { - BenchmarkAllCases.Counter = 0; + counter = 0; if (isIdle) { benchmarkAction.InvokeSingle(); - Assert.Equal(0, BenchmarkAllCases.Counter); + Assert.Equal(0, counter); benchmarkAction.InvokeMultiple(0); - Assert.Equal(0, BenchmarkAllCases.Counter); + Assert.Equal(0, counter); benchmarkAction.InvokeMultiple(11); - Assert.Equal(0, BenchmarkAllCases.Counter); + Assert.Equal(0, counter); } else { benchmarkAction.InvokeSingle(); - Assert.Equal(1, BenchmarkAllCases.Counter); + Assert.Equal(1, counter); benchmarkAction.InvokeMultiple(0); - Assert.Equal(1, BenchmarkAllCases.Counter); + Assert.Equal(1, counter); benchmarkAction.InvokeMultiple(11); - Assert.Equal(BenchmarkAllCases.Counter, 1 + unrollFactor * 11); + Assert.Equal(1 + unrollFactor * 11, counter); } Assert.Equal(benchmarkAction.LastRunResult, expectedResult); } finally { - BenchmarkAllCases.Counter = 0; + counter = 0; } } @@ -256,6 +359,13 @@ public async Task InvokeOnceTaskAsync() Interlocked.Increment(ref Counter); } + [Benchmark] + public async ValueTask InvokeOnceValueTaskAsync() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + [Benchmark] public string InvokeOnceRefType() { @@ -285,5 +395,85 @@ public ValueTask InvokeOnceValueTaskOfT() return new ValueTask(DecimalResult); } } + + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public class BenchmarkSetupCleanupTask + { + public static int Counter; + + [GlobalSetup] + public static async Task GlobalSetup() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + + [GlobalCleanup] + public async Task GlobalCleanup() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + + [IterationSetup] + public static async Task IterationSetup() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + + [IterationCleanup] + public async Task IterationCleanup() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + + [Benchmark] + public void InvokeOnceVoid() + { + Interlocked.Increment(ref Counter); + } + } + + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public class BenchmarkSetupCleanupValueTask + { + public static int Counter; + + [GlobalSetup] + public static async ValueTask GlobalSetup() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + + [GlobalCleanup] + public async ValueTask GlobalCleanup() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + + [IterationSetup] + public static async ValueTask IterationSetup() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + + [IterationCleanup] + public async ValueTask IterationCleanup() + { + await Task.Yield(); + Interlocked.Increment(ref Counter); + } + + [Benchmark] + public void InvokeOnceVoid() + { + Interlocked.Increment(ref Counter); + } + } } } \ No newline at end of file From 08ef9d9a13b4e04b0e80bcd8f8b52153a8162e48 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Mar 2022 15:44:07 -0500 Subject: [PATCH 04/22] Add readonly modifier to awaitHelper emit field. --- .../InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index 0f95554272..4c347a5748 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -427,7 +427,7 @@ private Type EmitWorkloadDelegateType() private void DefineFields() { awaitHelperField = - runnableBuilder.DefineField(AwaitHelperFieldName, typeof(Helpers.AwaitHelper), FieldAttributes.Private); + runnableBuilder.DefineField(AwaitHelperFieldName, typeof(Helpers.AwaitHelper), FieldAttributes.Private | FieldAttributes.InitOnly); globalSetupActionField = runnableBuilder.DefineField(GlobalSetupActionFieldName, typeof(Action), FieldAttributes.Private); globalCleanupActionField = From f207ba0a71f5773b93a533862eebad2d5167dd18 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Mar 2022 17:02:58 -0500 Subject: [PATCH 05/22] Fix ldloc index --- .../InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index 4c347a5748..dd82b05d7d 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -909,7 +909,7 @@ [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task ilBuilder.EmitStloc(callResultLocal); ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); - ilBuilder.Emit(OpCodes.Ldloc_0); + ilBuilder.EmitLdloc(callResultLocal); ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod); } From c7d0676cd5c4287fba7087bd880eff1110e02a04 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Mar 2022 17:38:53 -0500 Subject: [PATCH 06/22] Fixed InProcessBenchmarkEmitsSameIL tests. --- .../Emitters/RunnableEmitter.cs | 78 ++++++++----------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index dd82b05d7d..7f06021c40 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -597,23 +597,24 @@ private MethodInfo EmitWorkloadImplementation(string methodName) workloadInvokeMethod.ReturnParameter, args); args = methodBuilder.GetEmitParameters(args); - var callResultType = consumableInfo.OriginMethodReturnType; var ilBuilder = methodBuilder.GetILGenerator(); /* - .locals init ( - [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task - ) - */ - var callResultLocal = ilBuilder.DeclareLocal(callResultType); + IL_0007: ldarg.0 + IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); /* - // return TaskSample(arg0). ... ; - IL_0000: ldarg.0 - IL_0001: ldarg.1 - IL_0002: call instance class [mscorlib]System.Threading.Tasks.Task`1 [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::TaskSample(int64) - */ + IL_0026: ldarg.0 + IL_0027: ldloc.0 + IL_0028: ldloc.1 + IL_0029: ldloc.2 + IL_002a: ldloc.3 + IL_002b: call instance class [System.Private.CoreLib]System.Threading.Tasks.Task`1 BenchmarkDotNet.Helpers.Runnable_0::WorkloadMethod(string, string, string, string) + */ if (!Descriptor.WorkloadMethod.IsStatic) ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.EmitLdargs(args); @@ -621,17 +622,9 @@ [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task /* // awaitHelper.GetResult(...); - IL_0006: stloc.0 - IL_0007: ldarg.0 - IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper - IL_000d: ldloc.0 IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) */ - ilBuilder.EmitStloc(callResultLocal); - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); - ilBuilder.Emit(OpCodes.Ldloc_0); ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod); /* @@ -847,18 +840,6 @@ .locals init ( var skipFirstArg = workloadMethod.IsStatic; var argLocals = EmitDeclareArgLocals(ilBuilder, skipFirstArg); - LocalBuilder callResultLocal = null; - if (consumableInfo.IsAwaitable) - { - var callResultType = consumableInfo.OriginMethodReturnType; - /* - .locals init ( - [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task - ) - */ - callResultLocal = ilBuilder.DeclareLocal(callResultType); - } - consumeEmitter.DeclareDisassemblyDiagnoserLocals(ilBuilder); var notElevenLabel = ilBuilder.DefineLabel(); @@ -882,16 +863,28 @@ [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task */ EmitLoadArgFieldsToLocals(ilBuilder, argLocals, skipFirstArg); + if (consumableInfo.IsAwaitable) + { + /* + IL_0026: ldarg.0 + IL_0027: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); + } + /* - // return TaskSample(_argField) ... ; - IL_0011: ldarg.0 - IL_0012: ldloc.0 - IL_0013: call instance class [mscorlib]System.Threading.Tasks.Task`1 [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::TaskSample(int64) - IL_0018: ret + IL_0026: ldarg.0 + IL_0027: ldloc.0 + IL_0028: ldloc.1 + IL_0029: ldloc.2 + IL_002a: ldloc.3 + IL_002b: call instance class [System.Private.CoreLib]System.Threading.Tasks.Task`1 BenchmarkDotNet.Helpers.Runnable_0::WorkloadMethod(string, string, string, string) */ - if (!workloadMethod.IsStatic) + { ilBuilder.Emit(OpCodes.Ldarg_0); + } ilBuilder.EmitLdLocals(argLocals); ilBuilder.Emit(OpCodes.Call, workloadMethod); @@ -899,17 +892,8 @@ [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task { /* // awaitHelper.GetResult(...); - IL_0006: stloc.0 - IL_0007: ldarg.0 - IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper - IL_000d: ldloc.0 - IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) + IL_0036: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) */ - - ilBuilder.EmitStloc(callResultLocal); - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); - ilBuilder.EmitLdloc(callResultLocal); ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod); } From 42ceaeed4138b856e545987d886e001ed76e73a8 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Mar 2022 17:48:09 -0500 Subject: [PATCH 07/22] Update Setup/Cleanup IL to match workload. --- .../Emitters/RunnableEmitter.cs | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index 7f06021c40..0444d1c85a 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -971,19 +971,15 @@ private void EmitAwaitableSetupTeardown( IL_0000: call string [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalCleanup() IL_0005: pop */ - LocalBuilder callResultLocal; + ilBuilder.Emit(OpCodes.Ldarg_0); + /* + IL_0026: ldarg.0 + IL_0027: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); if (targetMethod.IsStatic) { - ilBuilder.Emit(OpCodes.Ldarg_0); - var callResultType = returnTypeInfo.OriginMethodReturnType; - - /* - .locals init ( - [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task - ) - */ - callResultLocal = ilBuilder.DeclareLocal(callResultType); - ilBuilder.Emit(OpCodes.Call, targetMethod); } @@ -994,33 +990,14 @@ [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task } else { - ilBuilder.Emit(OpCodes.Ldarg_0); - var callResultType = returnTypeInfo.OriginMethodReturnType; - - /* - .locals init ( - [0] class [System.Private.CoreLib]System.Threading.Tasks.Task task - ) - */ - callResultLocal = ilBuilder.DeclareLocal(callResultType); - ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Call, targetMethod); } /* // awaitHelper.GetResult(...); - IL_0006: stloc.0 - IL_0007: ldarg.0 - IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper - IL_000d: ldloc.0 IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) */ - - ilBuilder.EmitStloc(callResultLocal); - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); - ilBuilder.Emit(OpCodes.Ldloc_0); ilBuilder.Emit(OpCodes.Callvirt, returnTypeInfo.GetResultMethod); if (targetMethod.ReturnType != typeof(void)) From 85fa2ef0734b6be54c6e920e7705df62066197ed Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 11 Mar 2022 19:37:10 -0500 Subject: [PATCH 08/22] Fixed `(Value)Task`-returning Setup/Cleanup methods. --- .../Emitters/RunnableEmitter.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index 0444d1c85a..4551dfaf64 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -959,25 +959,27 @@ private void EmitAwaitableSetupTeardown( if (targetMethod == null) throw new ArgumentNullException(nameof(targetMethod)); + if (returnTypeInfo.WorkloadMethodReturnType == typeof(void)) + { + ilBuilder.Emit(OpCodes.Ldarg_0); + } /* - // call for instance void - // GlobalSetup(); IL_0000: ldarg.0 - IL_0001: call instance void [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalSetup() + IL_0001: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); /* - // call for static with return value + // call for instance // GlobalSetup(); - IL_0000: call string [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalCleanup() - IL_0005: pop + IL_0006: ldarg.0 + IL_0007: call instance void [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalSetup() */ - ilBuilder.Emit(OpCodes.Ldarg_0); /* - IL_0026: ldarg.0 - IL_0027: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper + // call for static + // GlobalSetup(); + IL_0006: call string [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalCleanup() */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); if (targetMethod.IsStatic) { ilBuilder.Emit(OpCodes.Call, targetMethod); @@ -999,9 +1001,7 @@ private void EmitAwaitableSetupTeardown( IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) */ ilBuilder.Emit(OpCodes.Callvirt, returnTypeInfo.GetResultMethod); - - if (targetMethod.ReturnType != typeof(void)) - ilBuilder.Emit(OpCodes.Pop); + ilBuilder.Emit(OpCodes.Pop); } private void EmitCtorBody() From 7be781998aa5d9811e6521553fec4d615e778161 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 15 Mar 2022 14:43:33 -0400 Subject: [PATCH 09/22] Fixed naming and CanBeNullAttribute. --- .../Reflection.Emit/IlGeneratorStatementExtensions.cs | 10 +++++----- .../ConsumableTypeInfo.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs index 84b773522a..65b016a0dd 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs @@ -44,11 +44,11 @@ public static void EmitVoidReturn(this ILGenerator ilBuilder, MethodBuilder meth public static void EmitSetFieldToNewInstance( this ILGenerator ilBuilder, - FieldBuilder delegateField, + FieldBuilder field, Type instanceType) { - if (delegateField.IsStatic) - throw new ArgumentException("The field should be instance field", nameof(delegateField)); + if (field.IsStatic) + throw new ArgumentException("The field should be instance field", nameof(field)); if (instanceType != null) { @@ -63,13 +63,13 @@ public static void EmitSetFieldToNewInstance( ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Newobj, ctor); - ilBuilder.Emit(OpCodes.Stfld, delegateField); + ilBuilder.Emit(OpCodes.Stfld, field); } else { ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldnull); - ilBuilder.Emit(OpCodes.Stfld, delegateField); + ilBuilder.Emit(OpCodes.Stfld, field); } } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs index 644a7397a1..b0a7189e3f 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs @@ -94,7 +94,7 @@ public ConsumableTypeInfo(Type methodReturnType) [NotNull] public Type OverheadMethodReturnType { get; } - [NotNull] + [CanBeNull] public MethodInfo GetResultMethod { get; } public bool IsVoid { get; } From bdf8e1c4ba76b52ee8ffcbc5807712e58bfbc24e Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 21 Mar 2022 14:16:31 -0400 Subject: [PATCH 10/22] Fixed awaiterCompleted check. --- src/BenchmarkDotNet/Helpers/AwaitHelper.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs index 1ad6d67747..de64eb9e61 100644 --- a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs +++ b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs @@ -5,7 +5,6 @@ namespace BenchmarkDotNet.Helpers { public class AwaitHelper { - private readonly object awaiterLock = new object(); private readonly Action awaiterCallback; private bool awaiterCompleted; @@ -47,7 +46,7 @@ public void GetResult(ValueTask task) awaiterCompleted = false; awaiter.UnsafeOnCompleted(awaiterCallback); // Check if the callback executed synchronously before blocking. - if (awaiterCompleted) + if (!awaiterCompleted) { System.Threading.Monitor.Wait(awaiterLock); } @@ -66,7 +65,7 @@ public T GetResult(ValueTask task) awaiterCompleted = false; awaiter.UnsafeOnCompleted(awaiterCallback); // Check if the callback executed synchronously before blocking. - if (awaiterCompleted) + if (!awaiterCompleted) { System.Threading.Monitor.Wait(awaiterLock); } From 39bdbea8bf69e25fbb190494b2220dea368bca13 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 14 Aug 2022 20:32:04 -0400 Subject: [PATCH 11/22] Use `ConfigureAwait(false)` on `ValueTask`s in `AwaitHelper` to prevent in-process deadlocks. --- src/BenchmarkDotNet/Helpers/AwaitHelper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs index de64eb9e61..602da95ccb 100644 --- a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs +++ b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs @@ -38,7 +38,8 @@ public T GetResult(Task task) // It is illegal to call GetResult from an uncomplete ValueTask, so we must hook up a callback. public void GetResult(ValueTask task) { - var awaiter = task.GetAwaiter(); + // Don't continue on the captured context, as that may result in a deadlock if the user runs this in-process. + var awaiter = task.ConfigureAwait(false).GetAwaiter(); if (!awaiter.IsCompleted) { lock (awaiterLock) @@ -57,7 +58,8 @@ public void GetResult(ValueTask task) public T GetResult(ValueTask task) { - var awaiter = task.GetAwaiter(); + // Don't continue on the captured context, as that may result in a deadlock if the user runs this in-process. + var awaiter = task.ConfigureAwait(false).GetAwaiter(); if (!awaiter.IsCompleted) { lock (awaiterLock) From d505562b9068fe9c09c4bd2b48370760ee29b11b Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 21 Mar 2022 14:34:34 -0400 Subject: [PATCH 12/22] WIP Refactored delegates to pass in IClock and return ValueTask. TODO: Fix compile errors from BenchmarkType.txt in NetFramework and Mono. Update InProcessEmitBuilder for the new behavior. --- src/BenchmarkDotNet/Code/CodeGenerator.cs | 12 +- .../Code/DeclarationsProvider.cs | 43 +- src/BenchmarkDotNet/Engines/Engine.cs | 31 +- src/BenchmarkDotNet/Engines/EngineFactory.cs | 3 +- .../Engines/EngineParameters.cs | 17 +- src/BenchmarkDotNet/Engines/IEngine.cs | 11 +- src/BenchmarkDotNet/Helpers/AwaitHelper.cs | 23 + .../Helpers/ManualResetValueTaskSource.cs | 179 ++++++ src/BenchmarkDotNet/Running/BenchmarkCase.cs | 7 +- .../Templates/BenchmarkType.txt | 301 +++++++---- .../Emitters/RunnableEmitter.cs | 10 +- .../Runnable/RunnableReflectionHelpers.cs | 10 +- .../InProcess.NoEmit/BenchmarkAction.cs | 16 +- .../BenchmarkActionFactory.cs | 15 + .../BenchmarkActionFactory_Implementations.cs | 489 ++++++++++++++--- .../InProcess.NoEmit/InProcessNoEmitRunner.cs | 26 +- .../Toolchains/InProcess/BenchmarkAction.cs | 17 +- .../InProcess/BenchmarkActionFactory.cs | 15 + .../BenchmarkActionFactory_Implementations.cs | 510 +++++++++++++++--- .../Toolchains/InProcess/InProcessRunner.cs | 24 +- .../CustomEngineTests.cs | 10 +- .../InProcessTest.cs | 56 +- .../Engine/EngineFactoryTests.cs | 73 ++- 23 files changed, 1478 insertions(+), 420 deletions(-) create mode 100644 src/BenchmarkDotNet/Helpers/ManualResetValueTaskSource.cs diff --git a/src/BenchmarkDotNet/Code/CodeGenerator.cs b/src/BenchmarkDotNet/Code/CodeGenerator.cs index e60dfe2699..478ff8866e 100644 --- a/src/BenchmarkDotNet/Code/CodeGenerator.cs +++ b/src/BenchmarkDotNet/Code/CodeGenerator.cs @@ -49,6 +49,7 @@ internal static string Generate(BuildPartition buildPartition) .Replace("$WorkloadMethodReturnType$", provider.WorkloadMethodReturnTypeName) .Replace("$WorkloadMethodReturnTypeModifiers$", provider.WorkloadMethodReturnTypeModifiers) .Replace("$OverheadMethodReturnTypeName$", provider.OverheadMethodReturnTypeName) + .Replace("$AwaiterTypeName$", provider.AwaiterTypeName) .Replace("$GlobalSetupMethodName$", provider.GlobalSetupMethodName) .Replace("$GlobalCleanupMethodName$", provider.GlobalCleanupMethodName) .Replace("$IterationSetupMethodName$", provider.IterationSetupMethodName) @@ -155,15 +156,12 @@ private static DeclarationsProvider GetDeclarationsProvider(Descriptor descripto { var method = descriptor.WorkloadMethod; - if (method.ReturnType == typeof(Task) || method.ReturnType == typeof(ValueTask)) - { - return new TaskDeclarationsProvider(descriptor); - } - if (method.ReturnType.GetTypeInfo().IsGenericType - && (method.ReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) + if (method.ReturnType == typeof(Task) || method.ReturnType == typeof(ValueTask) + || method.ReturnType.GetTypeInfo().IsGenericType + && (method.ReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) || method.ReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>))) { - return new GenericTaskDeclarationsProvider(descriptor); + return new TaskDeclarationsProvider(descriptor); } if (method.ReturnType == typeof(void)) diff --git a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs index 21e3ce54c1..058f912e53 100644 --- a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs +++ b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Reflection; using System.Threading.Tasks; using BenchmarkDotNet.Engines; @@ -11,9 +10,6 @@ namespace BenchmarkDotNet.Code { internal abstract class DeclarationsProvider { - // "GlobalSetup" or "GlobalCleanup" methods are optional, so default to an empty delegate, so there is always something that can be invoked - private const string EmptyAction = "() => { }"; - protected readonly Descriptor Descriptor; internal DeclarationsProvider(Descriptor descriptor) => Descriptor = descriptor; @@ -26,9 +22,9 @@ internal abstract class DeclarationsProvider public string GlobalCleanupMethodName => GetMethodName(Descriptor.GlobalCleanupMethod); - public string IterationSetupMethodName => Descriptor.IterationSetupMethod?.Name ?? EmptyAction; + public string IterationSetupMethodName => GetMethodName(Descriptor.IterationSetupMethod); - public string IterationCleanupMethodName => Descriptor.IterationCleanupMethod?.Name ?? EmptyAction; + public string IterationCleanupMethodName => GetMethodName(Descriptor.IterationCleanupMethod); public abstract string ReturnsDefinition { get; } @@ -48,13 +44,18 @@ internal abstract class DeclarationsProvider public string OverheadMethodReturnTypeName => OverheadMethodReturnType.GetCorrectCSharpTypeName(); + public virtual string AwaiterTypeName => string.Empty; + + public virtual void OverrideUnrollFactor(BenchmarkCase benchmarkCase) { } + public abstract string OverheadImplementation { get; } private string GetMethodName(MethodInfo method) { + // "Setup" or "Cleanup" methods are optional, so default to a simple delegate, so there is always something that can be invoked if (method == null) { - return EmptyAction; + return "() => new System.Threading.Tasks.ValueTask()"; } if (method.ReturnType == typeof(Task) || @@ -63,10 +64,10 @@ private string GetMethodName(MethodInfo method) (method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) || method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>)))) { - return $"() => awaitHelper.GetResult({method.Name}())"; + return $"() => BenchmarkDotNet.Helpers.AwaitHelper.ToValueTaskVoid({method.Name}())"; } - return method.Name; + return $"() => {{ {method.Name}(); return new System.Threading.Tasks.ValueTask(); }}"; } } @@ -145,30 +146,18 @@ public ByReadOnlyRefDeclarationsProvider(Descriptor descriptor) : base(descripto public override string WorkloadMethodReturnTypeModifiers => "ref readonly"; } - internal class TaskDeclarationsProvider : VoidDeclarationsProvider + internal class TaskDeclarationsProvider : DeclarationsProvider { public TaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - public override string WorkloadMethodDelegate(string passArguments) - => $"({passArguments}) => {{ awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}"; - - public override string GetWorkloadMethodCall(string passArguments) => $"awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))"; + public override string ExtraDefines => "#define RETURNS_AWAITABLE"; - protected override Type WorkloadMethodReturnType => typeof(void); - } - - /// - /// declarations provider for and - /// - internal class GenericTaskDeclarationsProvider : NonVoidDeclarationsProvider - { - public GenericTaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } + public override string AwaiterTypeName => WorkloadMethodReturnType.GetMethod(nameof(Task.GetAwaiter), BindingFlags.Public | BindingFlags.Instance).ReturnType.GetCorrectCSharpTypeName(); - protected override Type WorkloadMethodReturnType => Descriptor.WorkloadMethod.ReturnType.GetTypeInfo().GetGenericArguments().Single(); + public override string OverheadImplementation => $"return default({OverheadMethodReturnType.GetCorrectCSharpTypeName()});"; - public override string WorkloadMethodDelegate(string passArguments) - => $"({passArguments}) => {{ return awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}"; + protected override Type OverheadMethodReturnType => WorkloadMethodReturnType; - public override string GetWorkloadMethodCall(string passArguments) => $"awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))"; + public override void OverrideUnrollFactor(BenchmarkCase benchmarkCase) => benchmarkCase.ForceUnrollFactorForAsync(); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Engines/Engine.cs b/src/BenchmarkDotNet/Engines/Engine.cs index f626eddac4..ee5e254265 100644 --- a/src/BenchmarkDotNet/Engines/Engine.cs +++ b/src/BenchmarkDotNet/Engines/Engine.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using BenchmarkDotNet.Characteristics; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Portability; @@ -19,17 +20,17 @@ public class Engine : IEngine public const int MinInvokeCount = 4; [PublicAPI] public IHost Host { get; } - [PublicAPI] public Action WorkloadAction { get; } + [PublicAPI] public Func> WorkloadAction { get; } [PublicAPI] public Action Dummy1Action { get; } [PublicAPI] public Action Dummy2Action { get; } [PublicAPI] public Action Dummy3Action { get; } - [PublicAPI] public Action OverheadAction { get; } + [PublicAPI] public Func> OverheadAction { get; } [PublicAPI] public Job TargetJob { get; } [PublicAPI] public long OperationsPerInvoke { get; } - [PublicAPI] public Action GlobalSetupAction { get; } - [PublicAPI] public Action GlobalCleanupAction { get; } - [PublicAPI] public Action IterationSetupAction { get; } - [PublicAPI] public Action IterationCleanupAction { get; } + [PublicAPI] public Func GlobalSetupAction { get; } + [PublicAPI] public Func GlobalCleanupAction { get; } + [PublicAPI] public Func IterationSetupAction { get; } + [PublicAPI] public Func IterationCleanupAction { get; } [PublicAPI] public IResolver Resolver { get; } [PublicAPI] public CultureInfo CultureInfo { get; } [PublicAPI] public string BenchmarkName { get; } @@ -46,13 +47,14 @@ public class Engine : IEngine private readonly EngineActualStage actualStage; private readonly bool includeExtraStats; private readonly Random random; + private readonly Helpers.AwaitHelper awaitHelper; internal Engine( IHost host, IResolver resolver, - Action dummy1Action, Action dummy2Action, Action dummy3Action, Action overheadAction, Action workloadAction, Job targetJob, - Action globalSetupAction, Action globalCleanupAction, Action iterationSetupAction, Action iterationCleanupAction, long operationsPerInvoke, - bool includeExtraStats, string benchmarkName) + Action dummy1Action, Action dummy2Action, Action dummy3Action, Func> overheadAction, Func> workloadAction, + Job targetJob, Func globalSetupAction, Func globalCleanupAction, Func iterationSetupAction, Func iterationCleanupAction, + long operationsPerInvoke, bool includeExtraStats, string benchmarkName) { Host = host; @@ -84,13 +86,14 @@ internal Engine( actualStage = new EngineActualStage(this); random = new Random(12345); // we are using constant seed to try to get repeatable results + awaitHelper = new Helpers.AwaitHelper(); } public void Dispose() { try { - GlobalCleanupAction?.Invoke(); + awaitHelper.GetResult(GlobalCleanupAction.Invoke()); } catch (Exception e) { @@ -165,9 +168,8 @@ public Measurement RunIteration(IterationData data) Span stackMemory = randomizeMemory ? stackalloc byte[random.Next(32)] : Span.Empty; // Measure - var clock = Clock.Start(); - action(invokeCount / unrollFactor); - var clockSpan = clock.GetElapsed(); + var op = action(invokeCount / unrollFactor, Clock); + var clockSpan = awaitHelper.GetResult(op); if (EngineEventSource.Log.IsEnabled()) EngineEventSource.Log.IterationStop(data.IterationMode, data.IterationStage, totalOperations); @@ -201,7 +203,8 @@ public Measurement RunIteration(IterationData data) var initialThreadingStats = ThreadingStats.ReadInitial(); // this method might allocate var initialGcStats = GcStats.ReadInitial(); - WorkloadAction(data.InvokeCount / data.UnrollFactor); + var op = WorkloadAction(data.InvokeCount / data.UnrollFactor, Clock); + awaitHelper.GetResult(op); var finalGcStats = GcStats.ReadFinal(); var finalThreadingStats = ThreadingStats.ReadFinal(); diff --git a/src/BenchmarkDotNet/Engines/EngineFactory.cs b/src/BenchmarkDotNet/Engines/EngineFactory.cs index 0588218522..f2a807b560 100644 --- a/src/BenchmarkDotNet/Engines/EngineFactory.cs +++ b/src/BenchmarkDotNet/Engines/EngineFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using BenchmarkDotNet.Jobs; using Perfolizer.Horology; @@ -109,7 +110,7 @@ private static Engine CreateSingleActionEngine(EngineParameters engineParameters engineParameters.OverheadActionNoUnroll, engineParameters.WorkloadActionNoUnroll); - private static Engine CreateEngine(EngineParameters engineParameters, Job job, Action idle, Action main) + private static Engine CreateEngine(EngineParameters engineParameters, Job job, Func> idle, Func> main) => new Engine( engineParameters.Host, EngineParameters.DefaultResolver, diff --git a/src/BenchmarkDotNet/Engines/EngineParameters.cs b/src/BenchmarkDotNet/Engines/EngineParameters.cs index ec61582529..c7361b3a07 100644 --- a/src/BenchmarkDotNet/Engines/EngineParameters.cs +++ b/src/BenchmarkDotNet/Engines/EngineParameters.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using BenchmarkDotNet.Characteristics; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; @@ -12,19 +13,19 @@ public class EngineParameters public static readonly IResolver DefaultResolver = new CompositeResolver(BenchmarkRunnerClean.DefaultResolver, EngineResolver.Instance); public IHost Host { get; set; } - public Action WorkloadActionNoUnroll { get; set; } - public Action WorkloadActionUnroll { get; set; } + public Func> WorkloadActionNoUnroll { get; set; } + public Func> WorkloadActionUnroll { get; set; } public Action Dummy1Action { get; set; } public Action Dummy2Action { get; set; } public Action Dummy3Action { get; set; } - public Action OverheadActionNoUnroll { get; set; } - public Action OverheadActionUnroll { get; set; } + public Func> OverheadActionNoUnroll { get; set; } + public Func> OverheadActionUnroll { get; set; } public Job TargetJob { get; set; } = Job.Default; public long OperationsPerInvoke { get; set; } = 1; - public Action GlobalSetupAction { get; set; } - public Action GlobalCleanupAction { get; set; } - public Action IterationSetupAction { get; set; } - public Action IterationCleanupAction { get; set; } + public Func GlobalSetupAction { get; set; } + public Func GlobalCleanupAction { get; set; } + public Func IterationSetupAction { get; set; } + public Func IterationCleanupAction { get; set; } public bool MeasureExtraStats { get; set; } [PublicAPI] public string BenchmarkName { get; set; } diff --git a/src/BenchmarkDotNet/Engines/IEngine.cs b/src/BenchmarkDotNet/Engines/IEngine.cs index c502a97e16..c01a398ed0 100644 --- a/src/BenchmarkDotNet/Engines/IEngine.cs +++ b/src/BenchmarkDotNet/Engines/IEngine.cs @@ -1,10 +1,11 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; using BenchmarkDotNet.Characteristics; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Reports; using JetBrains.Annotations; -using NotNullAttribute = JetBrains.Annotations.NotNullAttribute; +using Perfolizer.Horology; namespace BenchmarkDotNet.Engines { @@ -24,16 +25,16 @@ public interface IEngine : IDisposable long OperationsPerInvoke { get; } [CanBeNull] - Action GlobalSetupAction { get; } + Func GlobalSetupAction { get; } [CanBeNull] - Action GlobalCleanupAction { get; } + Func GlobalCleanupAction { get; } [NotNull] - Action WorkloadAction { get; } + Func> WorkloadAction { get; } [NotNull] - Action OverheadAction { get; } + Func> OverheadAction { get; } IResolver Resolver { get; } diff --git a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs index 602da95ccb..f72e1ffacf 100644 --- a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs +++ b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs @@ -75,5 +75,28 @@ public T GetResult(ValueTask task) } return awaiter.GetResult(); } + + public static ValueTask ToValueTaskVoid(Task task) + { + return new ValueTask(task); + } + + public static ValueTask ToValueTaskVoid(Task task) + { + return new ValueTask(task); + } + + public static ValueTask ToValueTaskVoid(ValueTask task) + { + return task; + } + + // ValueTask unfortunately can't be converted to a ValueTask for free, so we must create a state machine. + // It's not a big deal though, as this is only used for Setup/Cleanup where allocations aren't measured. + // And in practice, this should never be used, as (Value)Task Setup/Cleanup methods have no utility. + public static async ValueTask ToValueTaskVoid(ValueTask task) + { + _ = await task.ConfigureAwait(false); + } } } diff --git a/src/BenchmarkDotNet/Helpers/ManualResetValueTaskSource.cs b/src/BenchmarkDotNet/Helpers/ManualResetValueTaskSource.cs new file mode 100644 index 0000000000..a3f00251bc --- /dev/null +++ b/src/BenchmarkDotNet/Helpers/ManualResetValueTaskSource.cs @@ -0,0 +1,179 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using JetBrains.Annotations; +using System; +using System.Diagnostics; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks.Sources; + +namespace BenchmarkDotNet.Helpers +{ + /// Provides the core logic for implementing a manual-reset or . + /// + [StructLayout(LayoutKind.Auto)] + public class ManualResetValueTaskSource : IValueTaskSource, IValueTaskSource + { + /// + /// The callback to invoke when the operation completes if was called before the operation completed, + /// or if the operation completed before a callback was supplied, + /// or null if a callback hasn't yet been provided and the operation hasn't yet completed. + /// + [CanBeNull] private Action _continuation; + /// State to pass to . + [CanBeNull] private object _continuationState; + /// Whether the current operation has completed. + private bool _completed; + /// The result with which the operation succeeded, or the default value if it hasn't yet completed or failed. + [CanBeNull] private TResult _result; + /// The exception with which the operation failed, or null if it hasn't yet completed or completed successfully. + [CanBeNull] private ExceptionDispatchInfo _error; + /// The current version of this value, used to help prevent misuse. + private short _version; + + /// Resets to prepare for the next operation. + public void Reset() + { + // Reset/update state for the next use/await of this instance. + _version++; + _completed = false; + _result = default; + _error = null; + _continuation = null; + _continuationState = null; + } + + /// Completes with a successful result. + /// The result. + public void SetResult(TResult result) + { + _result = result; + SignalCompletion(); + } + + /// Completes with an error. + /// The exception. + public void SetException(Exception error) + { + _error = ExceptionDispatchInfo.Capture(error); + SignalCompletion(); + } + + /// Gets the operation version. + public short Version => _version; + + /// Gets the status of the operation. + /// Opaque value that was provided to the 's constructor. + public ValueTaskSourceStatus GetStatus(short token) + { + ValidateToken(token); + return + _continuation == null || !_completed ? ValueTaskSourceStatus.Pending : + _error == null ? ValueTaskSourceStatus.Succeeded : + _error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled : + ValueTaskSourceStatus.Faulted; + } + + /// Gets the result of the operation. + /// Opaque value that was provided to the 's constructor. + public TResult GetResult(short token) + { + ValidateToken(token); + if (!_completed) + { + throw new InvalidOperationException(); + } + + _error?.Throw(); + return _result; + } + + public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) + { + if (continuation == null) + { + throw new ArgumentNullException(nameof(continuation)); + } + ValidateToken(token); + + // We need to set the continuation state before we swap in the delegate, so that + // if there's a race between this and SetResult/Exception and SetResult/Exception + // sees the _continuation as non-null, it'll be able to invoke it with the state + // stored here. However, this also means that if this is used incorrectly (e.g. + // awaited twice concurrently), _continuationState might get erroneously overwritten. + // To minimize the chances of that, we check preemptively whether _continuation + // is already set to something other than the completion sentinel. + + object oldContinuation = _continuation; + if (oldContinuation == null) + { + _continuationState = state; + oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null); + } + + if (oldContinuation != null) + { + // Operation already completed, so we need to call the supplied callback. + if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel)) + { + throw new InvalidOperationException(); + } + + continuation(state); + } + } + + /// Ensures that the specified token matches the current version. + /// The token supplied by . + private void ValidateToken(short token) + { + if (token != _version) + { + throw new InvalidOperationException(); + } + } + + /// Signals that the operation has completed. Invoked after the result or error has been set. + private void SignalCompletion() + { + if (_completed) + { + throw new InvalidOperationException(); + } + _completed = true; + + if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null) + { + InvokeContinuation(); + } + } + + /// + /// Invokes the continuation with the appropriate captured context / scheduler. + /// This assumes that if is not null we're already + /// running within that . + /// + private void InvokeContinuation() + { + Debug.Assert(_continuation != null); + _continuation(_continuationState); + } + + void IValueTaskSource.GetResult(short token) + { + GetResult(token); + } + } + + internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication + { + internal static readonly Action s_sentinel = CompletionSentinel; + private static void CompletionSentinel(object _) // named method to aid debugging + { + Debug.Fail("The sentinel delegate should never be invoked."); + throw new InvalidOperationException(); + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Running/BenchmarkCase.cs b/src/BenchmarkDotNet/Running/BenchmarkCase.cs index b517ab5676..410446c00d 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkCase.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkCase.cs @@ -11,7 +11,7 @@ namespace BenchmarkDotNet.Running public class BenchmarkCase : IComparable, IDisposable { public Descriptor Descriptor { get; } - public Job Job { get; } + public Job Job { get; private set; } public ParameterInstances Parameters { get; } public ImmutableConfig Config { get; } @@ -32,6 +32,11 @@ public Runtime GetRuntime() => Job.Environment.HasValue(EnvironmentMode.RuntimeC ? Job.Environment.Runtime : RuntimeInformation.GetCurrentRuntime(); + internal void ForceUnrollFactorForAsync() + { + Job = Job.WithUnrollFactor(1); + } + public void Dispose() => Parameters.Dispose(); public int CompareTo(BenchmarkCase other) => string.Compare(FolderInfo, other.FolderInfo, StringComparison.Ordinal); diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index 7a215a4c93..aa4e0ef18e 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -57,8 +57,9 @@ public Runnable_$ID$() { - awaitHelper = new BenchmarkDotNet.Helpers.AwaitHelper(); - +#if RETURNS_AWAITABLE_$ID$ + continuation = __Continuation; +#endif globalSetupAction = $GlobalSetupMethodName$; globalCleanupAction = $GlobalCleanupMethodName$; iterationSetupAction = $IterationSetupMethodName$; @@ -68,12 +69,10 @@ $InitializeArgumentFields$ } - private readonly BenchmarkDotNet.Helpers.AwaitHelper awaitHelper; - - private System.Action globalSetupAction; - private System.Action globalCleanupAction; - private System.Action iterationSetupAction; - private System.Action iterationCleanupAction; + private System.Func globalSetupAction; + private System.Func globalCleanupAction; + private System.Func iterationSetupAction; + private System.Func iterationCleanupAction; private BenchmarkDotNet.Autogenerated.Runnable_$ID$.OverheadDelegate overheadDelegate; private BenchmarkDotNet.Autogenerated.Runnable_$ID$.WorkloadDelegate workloadDelegate; $DeclareArgumentFields$ @@ -93,19 +92,19 @@ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private void Dummy1() { - @DummyUnroll@ + dummyVar++;@DummyUnroll@ } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private void Dummy2() { - @DummyUnroll@ + dummyVar++;@DummyUnroll@ } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private void Dummy3() { - @DummyUnroll@ + dummyVar++;@DummyUnroll@ } private $OverheadMethodReturnTypeName$ __Overhead($ArgumentsDefinition$) // __ is to avoid possible name conflict @@ -113,56 +112,172 @@ $OverheadImplementation$ } -#if RETURNS_CONSUMABLE_$ID$ +#if RETURNS_AWAITABLE_$ID$ + + private readonly BenchmarkDotNet.Helpers.ManualResetValueTaskSource valueTaskSource = new BenchmarkDotNet.Helpers.ManualResetValueTaskSource(); + private System.Int64 repeatsRemaining; + private readonly System.Action continuation; + private Perfolizer.Horology.StartedClock startedClock; + private $AwaiterTypeName$ currentAwaiter; + + // Awaits are not unrolled. + private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) + { + return OverheadActionImpl(invokeCount, clock); + } + + private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) + { + return OverheadActionImpl(invokeCount, clock); + } + + private System.Threading.Tasks.ValueTask OverheadActionImpl(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) + { + repeatsRemaining = invokeCount; + $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); + startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); + try + { + $LoadArguments$ + while (--repeatsRemaining >= 0) + { + value = overheadDelegate($PassArguments$); + } + } + catch (System.Exception) + { + throw; + } + var elapsed = startedClock.GetElapsed(); + BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); + return new System.Threading.Tasks.ValueTask(elapsed); + } + + private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) + { + return WorkloadActionImpl(invokeCount, clock); + } + + private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) + { + return WorkloadActionImpl(invokeCount, clock); + } + + private System.Threading.Tasks.ValueTask WorkloadActionImpl(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) + { + repeatsRemaining = invokeCount; + valueTaskSource.Reset(); + startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); + __RunTask(); + return new System.Threading.Tasks.ValueTask(valueTaskSource, valueTaskSource.Version); + } + + private void __RunTask() + { + try + { + $LoadArguments$ + while (--repeatsRemaining >= 0) + { + currentAwaiter = workloadDelegate($PassArguments$).GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + currentAwaiter.GetResult(); + } + } + catch (System.Exception e) + { + __SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default($AwaiterTypeName$); + startedClock = default(Perfolizer.Horology.StartedClock); + valueTaskSource.SetResult(clockspan); + } + + private void __Continuation() + { + try + { + currentAwaiter.GetResult(); + } + catch (System.Exception e) + { + __SetException(e); + return; + } + __RunTask(); + } + + private void __SetException(System.Exception e) + { + currentAwaiter = default($AwaiterTypeName$); + startedClock = default(Perfolizer.Horology.StartedClock); + valueTaskSource.SetException(e); + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + public $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$() + { + if (NotEleven == 11) + { + $LoadArguments$ + return $WorkloadMethodCall$; + } + + return default($WorkloadMethodReturnType$); + } + +#elif RETURNS_CONSUMABLE_$ID$ private BenchmarkDotNet.Engines.Consumer consumer = new BenchmarkDotNet.Engines.Consumer(); -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { consumer.Consume(overheadDelegate($PassArguments$));@Unroll@ } + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { consumer.Consume(overheadDelegate($PassArguments$)); } + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { consumer.Consume(workloadDelegate($PassArguments$)$ConsumeField$);@Unroll@ } + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { consumer.Consume(workloadDelegate($PassArguments$)$ConsumeField$); } + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] @@ -179,60 +294,60 @@ #elif RETURNS_NON_CONSUMABLE_STRUCT_$ID$ -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ $OverheadMethodReturnTypeName$ result = default($OverheadMethodReturnTypeName$); + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { result = overheadDelegate($PassArguments$);@Unroll@ } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); + return new System.Threading.Tasks.ValueTask(elapsed); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ $OverheadMethodReturnTypeName$ result = default($OverheadMethodReturnTypeName$); + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { result = overheadDelegate($PassArguments$); } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); + return new System.Threading.Tasks.ValueTask(elapsed); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ $WorkloadMethodReturnType$ result = default($WorkloadMethodReturnType$); + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { result = workloadDelegate($PassArguments$);@Unroll@ } + var elapsed = startedClock.GetElapsed(); NonGenericKeepAliveWithoutBoxing(result); + return new System.Threading.Tasks.ValueTask(elapsed); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ $WorkloadMethodReturnType$ result = default($WorkloadMethodReturnType$); + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { result = workloadDelegate($PassArguments$); } + var elapsed = startedClock.GetElapsed(); NonGenericKeepAliveWithoutBoxing(result); + return new System.Threading.Tasks.ValueTask(elapsed); } // we must not simply use DeadCodeEliminationHelper.KeepAliveWithoutBoxing because it's generic method @@ -254,62 +369,62 @@ #elif RETURNS_BYREF_$ID$ -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { value = overheadDelegate($PassArguments$);@Unroll@ } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); + return new System.Threading.Tasks.ValueTask(elapsed); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { value = overheadDelegate($PassArguments$); } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); + return new System.Threading.Tasks.ValueTask(elapsed); } private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { alias = workloadDelegate($PassArguments$);@Unroll@ } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); + return new System.Threading.Tasks.ValueTask(elapsed); } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) + + private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { alias = workloadDelegate($PassArguments$); } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); + return new System.Threading.Tasks.ValueTask(elapsed); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] @@ -325,62 +440,62 @@ } #elif RETURNS_BYREF_READONLY_$ID$ -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { value = overheadDelegate($PassArguments$);@Unroll@ } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); + return new System.Threading.Tasks.ValueTask(elapsed); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ $OverheadMethodReturnTypeName$ value = default($OverheadMethodReturnTypeName$); + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { value = overheadDelegate($PassArguments$); } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); + return new System.Threading.Tasks.ValueTask(elapsed); } private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { alias = workloadDelegate($PassArguments$);@Unroll@ } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); + return new System.Threading.Tasks.ValueTask(elapsed); } - -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) + + private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder; + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { alias = workloadDelegate($PassArguments$); } + var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); + return new System.Threading.Tasks.ValueTask(elapsed); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] @@ -396,52 +511,48 @@ } #elif RETURNS_VOID_$ID$ -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { overheadDelegate($PassArguments$);@Unroll@ } + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void OverheadActionNoUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { overheadDelegate($PassArguments$); } + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { workloadDelegate($PassArguments$);@Unroll@ } + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } -#if NETCOREAPP3_0_OR_GREATER - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] -#endif - private void WorkloadActionNoUnroll(System.Int64 invokeCount) + private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ + var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); for (System.Int64 i = 0; i < invokeCount; i++) { workloadDelegate($PassArguments$); } + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] @@ -453,5 +564,5 @@ $WorkloadMethodCall$; } } -#endif // RETURNS +#endif } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index 4551dfaf64..83fc550906 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -355,12 +355,18 @@ private void InitForEmitRunnable(BenchmarkBuildInfo newBenchmark) // Init current state argFields = new List(); benchmark = newBenchmark; + dummyUnrollFactor = DummyUnrollFactor; + + consumableInfo = new ConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.WorkloadMethod.ReturnType); + if (consumableInfo.IsAwaitable) + { + benchmark.BenchmarkCase.ForceUnrollFactorForAsync(); + } + jobUnrollFactor = benchmark.BenchmarkCase.Job.ResolveValue( RunMode.UnrollFactorCharacteristic, buildPartition.Resolver); - dummyUnrollFactor = DummyUnrollFactor; - consumableInfo = new ConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.WorkloadMethod.ReturnType); consumeEmitter = ConsumeEmitter.GetConsumeEmitter(consumableInfo); globalSetupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalSetupMethod?.ReturnType); globalCleanupReturnInfo = GetConsumableTypeInfo(benchmark.BenchmarkCase.Descriptor.GlobalCleanupMethod?.ReturnType); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableReflectionHelpers.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableReflectionHelpers.cs index f8cfd02ad1..d1fa515d00 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableReflectionHelpers.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableReflectionHelpers.cs @@ -1,8 +1,10 @@ using System; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using BenchmarkDotNet.Parameters; using BenchmarkDotNet.Running; +using Perfolizer.Horology; using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants; namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation @@ -104,9 +106,9 @@ public static void SetParameter( } } - public static Action CallbackFromField(T instance, string memberName) + public static Func CallbackFromField(T instance, string memberName) { - return GetFieldValueCore(instance, memberName); + return GetFieldValueCore>(instance, memberName); } public static Action CallbackFromMethod(T instance, string memberName) @@ -114,9 +116,9 @@ public static Action CallbackFromMethod(T instance, string memberName) return GetDelegateCore(instance, memberName); } - public static Action LoopCallbackFromMethod(T instance, string memberName) + public static Func> LoopCallbackFromMethod(T instance, string memberName) { - return GetDelegateCore>(instance, memberName); + return GetDelegateCore>>(instance, memberName); } private static TResult GetFieldValueCore(T instance, string memberName) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkAction.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkAction.cs index df1911d0b0..01b69fccae 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkAction.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkAction.cs @@ -1,6 +1,7 @@ using System; - +using System.Threading.Tasks; using JetBrains.Annotations; +using Perfolizer.Horology; namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit { @@ -8,16 +9,9 @@ namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit [PublicAPI] public abstract class BenchmarkAction { - /// Gets or sets invoke single callback. - /// Invoke single callback. - public Action InvokeSingle { get; protected set; } - - /// Gets or sets invoke multiple times callback. - /// Invoke multiple times callback. - public Action InvokeMultiple { get; protected set; } - - /// Gets the last run result. - /// The last run result. + public Func InvokeSingle { get; protected set; } + public Func> InvokeUnroll { get; protected set; } + public Func> InvokeNoUnroll { get; protected set; } public virtual object LastRunResult => null; } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory.cs index 130c67dbf3..603466ed05 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory.cs @@ -101,6 +101,21 @@ private static void FallbackMethod() { } private static readonly MethodInfo FallbackSignature = new Action(FallbackMethod).GetMethodInfo(); private static readonly MethodInfo DummyMethod = typeof(DummyInstance).GetMethod(nameof(DummyInstance.Dummy)); + internal static int GetUnrollFactor(BenchmarkCase benchmarkCase) + { + // Only support (Value)Task for async benchmarks. + var methodReturnType = benchmarkCase.Descriptor.WorkloadMethod.ReturnType; + bool isAwaitable = methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask) + || (methodReturnType.GetTypeInfo().IsGenericType + && (methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) + || methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>))); + if (isAwaitable) + { + benchmarkCase.ForceUnrollFactorForAsync(); + } + return benchmarkCase.Job.ResolveValue(Jobs.RunMode.UnrollFactorCharacteristic, Environments.EnvironmentResolver.Instance); + } + /// Creates run benchmark action. /// Descriptor info. /// Instance of target. diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs index d0f1df8daf..7273b11319 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs @@ -1,5 +1,8 @@ -using System; +using BenchmarkDotNet.Helpers; +using Perfolizer.Horology; +using System; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit @@ -22,19 +25,36 @@ internal class BenchmarkActionVoid : BenchmarkActionBase public BenchmarkActionVoid(object instance, MethodInfo method, int unrollFactor) { callback = CreateWorkloadOrOverhead(instance, method, OverheadStatic, OverheadInstance); - InvokeSingle = callback; + InvokeSingle = InvokeSingleHardcoded; unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + InvokeUnroll = InvokeUnrollHardcoded; + InvokeNoUnroll = InvokeNoUnrollHardcoded; } private static void OverheadStatic() { } private void OverheadInstance() { } - private void InvokeMultipleHardcoded(long repeatCount) + private ValueTask InvokeSingleHardcoded() { + callback(); + return new ValueTask(); + } + + private ValueTask InvokeUnrollHardcoded(long repeatCount, IClock clock) + { + var startedClock = clock.Start(); for (long i = 0; i < repeatCount; i++) unrolledCallback(); + return new ValueTask(startedClock.GetElapsed()); + } + + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + var startedClock = clock.Start(); + for (long i = 0; i < repeatCount; i++) + callback(); + return new ValueTask(startedClock.GetElapsed()); } } @@ -50,18 +70,34 @@ public BenchmarkAction(object instance, MethodInfo method, int unrollFactor) InvokeSingle = InvokeSingleHardcoded; unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + InvokeUnroll = InvokeUnrollHardcoded; + InvokeNoUnroll = InvokeNoUnrollHardcoded; } private static T OverheadStatic() => default; private T OverheadInstance() => default; - private void InvokeSingleHardcoded() => result = callback(); + private ValueTask InvokeSingleHardcoded() + { + result = callback(); + return new ValueTask(); + } - private void InvokeMultipleHardcoded(long repeatCount) + private ValueTask InvokeUnrollHardcoded(long repeatCount, IClock clock) { + var startedClock = clock.Start(); for (long i = 0; i < repeatCount; i++) result = unrolledCallback(); + unrolledCallback(); + return new ValueTask(startedClock.GetElapsed()); + } + + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + var startedClock = clock.Start(); + for (long i = 0; i < repeatCount; i++) + callback(); + return new ValueTask(startedClock.GetElapsed()); } public override object LastRunResult => result; @@ -69,82 +105,234 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionTask : BenchmarkActionBase { - private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); - private readonly Func startTaskCallback; - private readonly Action callback; - private readonly Action unrolledCallback; + private readonly Func callback; + private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private long repeatsRemaining; + private readonly Action continuation; + private StartedClock startedClock; + private TaskAwaiter currentAwaiter; public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) { + continuation = Continuation; bool isIdle = method == null; if (!isIdle) { - startTaskCallback = CreateWorkload>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkload>(instance, method); + InvokeSingle = InvokeSingleHardcoded; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcoded; } else { callback = Overhead; + InvokeSingle = InvokeSingleHardcodedOverhead; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcodedOverhead; } + } - InvokeSingle = callback; + private Task Overhead() => default; - unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + private ValueTask InvokeSingleHardcodedOverhead() + { + InvokeNoUnrollHardcodedOverhead(1, null); + return new ValueTask(); + } + private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + startedClock = clock?.Start() ?? default; + try + { + while (--repeatsRemaining >= 0) + { + callback(); + } + } + catch (Exception) + { + throw; + } + return new ValueTask(startedClock.GetElapsed()); } - // must be kept in sync with VoidDeclarationsProvider.IdleImplementation - private void Overhead() { } + private ValueTask InvokeSingleHardcoded() + { + InvokeNoUnrollHardcoded(1, null); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate - private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + valueTaskSource.Reset(); + startedClock = clock?.Start() ?? default; + RunTask(); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - private void InvokeMultipleHardcoded(long repeatCount) + private void RunTask() { - for (long i = 0; i < repeatCount; i++) - unrolledCallback(); + try + { + while (--repeatsRemaining >= 0) + { + currentAwaiter = callback().GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + currentAwaiter.GetResult(); + } + } + catch (Exception e) + { + SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetResult(clockspan); + } + + private void Continuation() + { + try + { + currentAwaiter.GetResult(); + } + catch (Exception e) + { + SetException(e); + return; + } + RunTask(); + } + + private void SetException(Exception e) + { + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetException(e); } } internal class BenchmarkActionTask : BenchmarkActionBase { - private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); - private readonly Func> startTaskCallback; - private readonly Func callback; - private readonly Func unrolledCallback; + private readonly Func> callback; + private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private long repeatsRemaining; + private readonly Action continuation; + private StartedClock startedClock; + private TaskAwaiter currentAwaiter; private T result; public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) { - bool isOverhead = method == null; - if (!isOverhead) + continuation = Continuation; + bool isIdle = method == null; + if (!isIdle) { - startTaskCallback = CreateWorkload>>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkload>>(instance, method); + InvokeSingle = InvokeSingleHardcoded; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcoded; } else { callback = Overhead; + InvokeSingle = InvokeSingleHardcodedOverhead; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcodedOverhead; } + } - InvokeSingle = InvokeSingleHardcoded; + private Task Overhead() => default; - unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + private ValueTask InvokeSingleHardcodedOverhead() + { + InvokeNoUnrollHardcodedOverhead(1, null); + return new ValueTask(); } - private T Overhead() => default; + private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + startedClock = clock?.Start() ?? default; + try + { + while (--repeatsRemaining >= 0) + { + callback(); + } + } + catch (Exception) + { + throw; + } + return new ValueTask(startedClock.GetElapsed()); + } - // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); + private ValueTask InvokeSingleHardcoded() + { + InvokeNoUnrollHardcoded(1, null); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - private void InvokeSingleHardcoded() => result = callback(); + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + valueTaskSource.Reset(); + startedClock = clock?.Start() ?? default; + RunTask(); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - private void InvokeMultipleHardcoded(long repeatCount) + private void RunTask() { - for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + try + { + while (--repeatsRemaining >= 0) + { + currentAwaiter = callback().GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + result = currentAwaiter.GetResult(); + } + } + catch (Exception e) + { + SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetResult(clockspan); + } + + private void Continuation() + { + try + { + result = currentAwaiter.GetResult(); + } + catch (Exception e) + { + SetException(e); + return; + } + RunTask(); + } + + private void SetException(Exception e) + { + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetException(e); } public override object LastRunResult => result; @@ -152,83 +340,234 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionValueTask : BenchmarkActionBase { - private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); - private readonly Func startTaskCallback; - private readonly Action callback; - private readonly Action unrolledCallback; + private readonly Func callback; + private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private long repeatsRemaining; + private readonly Action continuation; + private StartedClock startedClock; + private ValueTaskAwaiter currentAwaiter; public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFactor) { + continuation = Continuation; bool isIdle = method == null; if (!isIdle) { - startTaskCallback = CreateWorkload>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkload>(instance, method); + InvokeSingle = InvokeSingleHardcoded; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcoded; } else { callback = Overhead; + InvokeSingle = InvokeSingleHardcodedOverhead; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcodedOverhead; } + } - InvokeSingle = callback; + private ValueTask Overhead() => default; - unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + private ValueTask InvokeSingleHardcodedOverhead() + { + InvokeNoUnrollHardcodedOverhead(1, null); + return new ValueTask(); + } + + private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + startedClock = clock?.Start() ?? default; + try + { + while (--repeatsRemaining >= 0) + { + callback(); + } + } + catch (Exception) + { + throw; + } + return new ValueTask(startedClock.GetElapsed()); + } + private ValueTask InvokeSingleHardcoded() + { + InvokeNoUnrollHardcoded(1, null); + return new ValueTask(valueTaskSource, valueTaskSource.Version); } - // must be kept in sync with VoidDeclarationsProvider.IdleImplementation - private void Overhead() { } + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + valueTaskSource.Reset(); + startedClock = clock?.Start() ?? default; + RunTask(); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate - private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); + private void RunTask() + { + try + { + while (--repeatsRemaining >= 0) + { + currentAwaiter = callback().GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + currentAwaiter.GetResult(); + } + } + catch (Exception e) + { + SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetResult(clockspan); + } - private void InvokeMultipleHardcoded(long repeatCount) + private void Continuation() { - for (long i = 0; i < repeatCount; i++) - unrolledCallback(); + try + { + currentAwaiter.GetResult(); + } + catch (Exception e) + { + SetException(e); + return; + } + RunTask(); + } + + private void SetException(Exception e) + { + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetException(e); } } internal class BenchmarkActionValueTask : BenchmarkActionBase { - private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); - private readonly Func> startTaskCallback; - private readonly Func callback; - private readonly Func unrolledCallback; + private readonly Func> callback; + private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private long repeatsRemaining; + private readonly Action continuation; + private StartedClock startedClock; + private ValueTaskAwaiter currentAwaiter; private T result; public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFactor) { - bool isOverhead = method == null; - if (!isOverhead) + continuation = Continuation; + bool isIdle = method == null; + if (!isIdle) { - startTaskCallback = CreateWorkload>>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkload>>(instance, method); + InvokeSingle = InvokeSingleHardcoded; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcoded; } else { callback = Overhead; + InvokeSingle = InvokeSingleHardcodedOverhead; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcodedOverhead; } + } - InvokeSingle = InvokeSingleHardcoded; + private ValueTask Overhead() => default; + private ValueTask InvokeSingleHardcodedOverhead() + { + InvokeNoUnrollHardcodedOverhead(1, null); + return new ValueTask(); + } - unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + startedClock = clock?.Start() ?? default; + try + { + while (--repeatsRemaining >= 0) + { + callback(); + } + } + catch (Exception) + { + throw; + } + return new ValueTask(startedClock.GetElapsed()); + } + + private ValueTask InvokeSingleHardcoded() + { + InvokeNoUnrollHardcoded(1, null); + return new ValueTask(valueTaskSource, valueTaskSource.Version); } - private T Overhead() => default; + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + valueTaskSource.Reset(); + startedClock = clock?.Start() ?? default; + RunTask(); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); + private void RunTask() + { + try + { + while (--repeatsRemaining >= 0) + { + currentAwaiter = callback().GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + result = currentAwaiter.GetResult(); + } + } + catch (Exception e) + { + SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetResult(clockspan); + } - private void InvokeSingleHardcoded() => result = callback(); + private void Continuation() + { + try + { + result = currentAwaiter.GetResult(); + } + catch (Exception e) + { + SetException(e); + return; + } + RunTask(); + } - private void InvokeMultipleHardcoded(long repeatCount) + private void SetException(Exception e) { - for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetException(e); } public override object LastRunResult => result; diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/InProcessNoEmitRunner.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/InProcessNoEmitRunner.cs index e890000683..54b4af37fc 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/InProcessNoEmitRunner.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/InProcessNoEmitRunner.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Threading.Tasks; using BenchmarkDotNet.Engines; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Exporters; @@ -7,6 +8,7 @@ using BenchmarkDotNet.Running; using JetBrains.Annotations; +using Perfolizer.Horology; namespace BenchmarkDotNet.Toolchains.InProcess.NoEmit { @@ -104,7 +106,7 @@ public static void RunCore(IHost host, BenchmarkCase benchmarkCase) { var target = benchmarkCase.Descriptor; var job = benchmarkCase.Job; // TODO: filter job (same as SourceCodePresenter does)? - int unrollFactor = benchmarkCase.Job.ResolveValue(RunMode.UnrollFactorCharacteristic, EnvironmentResolver.Instance); + int unrollFactor = BenchmarkActionFactory.GetUnrollFactor(benchmarkCase); // DONTTOUCH: these should be allocated together var instance = Activator.CreateInstance(benchmarkCase.Descriptor.Type); @@ -129,21 +131,13 @@ public static void RunCore(IHost host, BenchmarkCase benchmarkCase) var engineParameters = new EngineParameters { Host = host, - WorkloadActionNoUnroll = invocationCount => - { - for (int i = 0; i < invocationCount; i++) - workloadAction.InvokeSingle(); - }, - WorkloadActionUnroll = workloadAction.InvokeMultiple, - Dummy1Action = dummy1.InvokeSingle, - Dummy2Action = dummy2.InvokeSingle, - Dummy3Action = dummy3.InvokeSingle, - OverheadActionNoUnroll = invocationCount => - { - for (int i = 0; i < invocationCount; i++) - overheadAction.InvokeSingle(); - }, - OverheadActionUnroll = overheadAction.InvokeMultiple, + WorkloadActionNoUnroll = workloadAction.InvokeNoUnroll, + WorkloadActionUnroll = workloadAction.InvokeUnroll, + Dummy1Action = () => dummy1.InvokeSingle(), + Dummy2Action = () => dummy2.InvokeSingle(), + Dummy3Action = () => dummy3.InvokeSingle(), + OverheadActionNoUnroll = overheadAction.InvokeNoUnroll, + OverheadActionUnroll = overheadAction.InvokeUnroll, GlobalSetupAction = globalSetupAction.InvokeSingle, GlobalCleanupAction = globalCleanupAction.InvokeSingle, IterationSetupAction = iterationSetupAction.InvokeSingle, diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkAction.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkAction.cs index 2cc2363f49..ca87993536 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkAction.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkAction.cs @@ -1,4 +1,6 @@ -using System; +using Perfolizer.Horology; +using System; +using System.Threading.Tasks; namespace BenchmarkDotNet.Toolchains.InProcess { @@ -6,16 +8,9 @@ namespace BenchmarkDotNet.Toolchains.InProcess [Obsolete("Please use BenchmarkDotNet.Toolchains.InProcess.NoEmit.* classes")] public abstract class BenchmarkAction { - /// Gets or sets invoke single callback. - /// Invoke single callback. - public Action InvokeSingle { get; protected set; } - - /// Gets or sets invoke multiple times callback. - /// Invoke multiple times callback. - public Action InvokeMultiple { get; protected set; } - - /// Gets the last run result. - /// The last run result. + public Func InvokeSingle { get; protected set; } + public Func> InvokeUnroll { get; protected set; } + public Func> InvokeNoUnroll { get; protected set; } public virtual object LastRunResult => null; } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs index 987a4c81b6..250e040786 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs @@ -100,6 +100,21 @@ private static void FallbackMethod() { } private static readonly MethodInfo FallbackSignature = new Action(FallbackMethod).GetMethodInfo(); private static readonly MethodInfo DummyMethod = typeof(DummyInstance).GetMethod(nameof(DummyInstance.Dummy)); + internal static int GetUnrollFactor(BenchmarkCase benchmarkCase) + { + // Only support (Value)Task for async benchmarks. + var methodReturnType = benchmarkCase.Descriptor.WorkloadMethod.ReturnType; + bool isAwaitable = methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask) + || (methodReturnType.GetTypeInfo().IsGenericType + && (methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) + || methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>))); + if (isAwaitable) + { + benchmarkCase.ForceUnrollFactorForAsync(); + } + return benchmarkCase.Job.ResolveValue(Jobs.RunMode.UnrollFactorCharacteristic, Environments.EnvironmentResolver.Instance); + } + /// Creates run benchmark action. /// Descriptor info. /// Instance of target. diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs index 5b39e445f5..cc8e193a28 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs @@ -1,5 +1,8 @@ -using System; +using BenchmarkDotNet.Helpers; +using Perfolizer.Horology; +using System; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace BenchmarkDotNet.Toolchains.InProcess @@ -18,30 +21,56 @@ internal class BenchmarkActionVoid : BenchmarkActionBase { private readonly Action callback; private readonly Action unrolledCallback; + private readonly Action emittedUnrolledCallback; public BenchmarkActionVoid(object instance, MethodInfo method, BenchmarkActionCodegen codegenMode, int unrollFactor) { callback = CreateWorkloadOrOverhead(instance, method, OverheadStatic, OverheadInstance); - InvokeSingle = callback; + InvokeSingle = InvokeSingleHardcoded; if (UseFallbackCode(codegenMode, unrollFactor)) { unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + InvokeUnroll = InvokeUnrollHardcoded; } else { - InvokeMultiple = EmitInvokeMultiple(this, nameof(callback), null, unrollFactor); + emittedUnrolledCallback = EmitInvokeMultiple(this, nameof(callback), null, unrollFactor); + InvokeUnroll = InvokeEmittedUnrollHardcoded; } + InvokeNoUnroll = InvokeNoUnrollHardcoded; } private static void OverheadStatic() { } private void OverheadInstance() { } - private void InvokeMultipleHardcoded(long repeatCount) + private ValueTask InvokeSingleHardcoded() { + callback(); + return new ValueTask(); + } + + private ValueTask InvokeUnrollHardcoded(long repeatCount, IClock clock) + { + var startedClock = clock.Start(); for (long i = 0; i < repeatCount; i++) unrolledCallback(); + return new ValueTask(startedClock.GetElapsed()); + } + + private ValueTask InvokeEmittedUnrollHardcoded(long repeatCount, IClock clock) + { + var startedClock = clock.Start(); + emittedUnrolledCallback(repeatCount); + return new ValueTask(startedClock.GetElapsed()); + } + + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + var startedClock = clock.Start(); + for (long i = 0; i < repeatCount; i++) + callback(); + return new ValueTask(startedClock.GetElapsed()); } } @@ -49,6 +78,7 @@ internal class BenchmarkAction : BenchmarkActionBase { private readonly Func callback; private readonly Func unrolledCallback; + private readonly Action emittedUnrolledCallback; private T result; public BenchmarkAction(object instance, MethodInfo method, BenchmarkActionCodegen codegenMode, int unrollFactor) @@ -59,23 +89,47 @@ public BenchmarkAction(object instance, MethodInfo method, BenchmarkActionCodege if (UseFallbackCode(codegenMode, unrollFactor)) { unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + InvokeUnroll = InvokeUnrollHardcoded; } else { - InvokeMultiple = EmitInvokeMultiple(this, nameof(callback), nameof(result), unrollFactor); + emittedUnrolledCallback = EmitInvokeMultiple(this, nameof(callback), nameof(result), unrollFactor); + InvokeUnroll = InvokeEmittedUnrollHardcoded; } + InvokeNoUnroll = InvokeNoUnrollHardcoded; } private static T OverheadStatic() => default; private T OverheadInstance() => default; - private void InvokeSingleHardcoded() => result = callback(); + private ValueTask InvokeSingleHardcoded() + { + result = callback(); + return new ValueTask(); + } - private void InvokeMultipleHardcoded(long repeatCount) + private ValueTask InvokeUnrollHardcoded(long repeatCount, IClock clock) { + var startedClock = clock.Start(); for (long i = 0; i < repeatCount; i++) result = unrolledCallback(); + unrolledCallback(); + return new ValueTask(startedClock.GetElapsed()); + } + + private ValueTask InvokeEmittedUnrollHardcoded(long repeatCount, IClock clock) + { + var startedClock = clock.Start(); + emittedUnrolledCallback(repeatCount); + return new ValueTask(startedClock.GetElapsed()); + } + + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + var startedClock = clock.Start(); + for (long i = 0; i < repeatCount; i++) + callback(); + return new ValueTask(startedClock.GetElapsed()); } public override object LastRunResult => result; @@ -83,95 +137,234 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionTask : BenchmarkActionBase { - private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); - private readonly Func startTaskCallback; - private readonly Action callback; - private readonly Action unrolledCallback; + private readonly Func callback; + private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private long repeatsRemaining; + private readonly Action continuation; + private StartedClock startedClock; + private TaskAwaiter currentAwaiter; public BenchmarkActionTask(object instance, MethodInfo method, BenchmarkActionCodegen codegenMode, int unrollFactor) { + continuation = Continuation; bool isIdle = method == null; if (!isIdle) { - startTaskCallback = CreateWorkload>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkload>(instance, method); + InvokeSingle = InvokeSingleHardcoded; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcoded; } else { callback = Overhead; + InvokeSingle = InvokeSingleHardcodedOverhead; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcodedOverhead; } + } - InvokeSingle = callback; + private Task Overhead() => default; - if (UseFallbackCode(codegenMode, unrollFactor)) + private ValueTask InvokeSingleHardcodedOverhead() + { + InvokeNoUnrollHardcodedOverhead(1, null); + return new ValueTask(); + } + + private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + startedClock = clock?.Start() ?? default; + try { - unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + while (--repeatsRemaining >= 0) + { + callback(); + } } - else + catch (Exception) { - InvokeMultiple = EmitInvokeMultiple(this, nameof(callback), null, unrollFactor); + throw; } + return new ValueTask(startedClock.GetElapsed()); } - // must be kept in sync with VoidDeclarationsProvider.IdleImplementation - private void Overhead() { } + private ValueTask InvokeSingleHardcoded() + { + InvokeNoUnrollHardcoded(1, null); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate - private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + valueTaskSource.Reset(); + startedClock = clock?.Start() ?? default; + RunTask(); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - private void InvokeMultipleHardcoded(long repeatCount) + private void RunTask() { - for (long i = 0; i < repeatCount; i++) - unrolledCallback(); + try + { + while (--repeatsRemaining >= 0) + { + currentAwaiter = callback().GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + currentAwaiter.GetResult(); + } + } + catch (Exception e) + { + SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetResult(clockspan); + } + + private void Continuation() + { + try + { + currentAwaiter.GetResult(); + } + catch (Exception e) + { + SetException(e); + return; + } + RunTask(); + } + + private void SetException(Exception e) + { + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetException(e); } } internal class BenchmarkActionTask : BenchmarkActionBase { - private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); - private readonly Func> startTaskCallback; - private readonly Func callback; - private readonly Func unrolledCallback; + private readonly Func> callback; + private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private long repeatsRemaining; + private readonly Action continuation; + private StartedClock startedClock; + private TaskAwaiter currentAwaiter; private T result; public BenchmarkActionTask(object instance, MethodInfo method, BenchmarkActionCodegen codegenMode, int unrollFactor) { - bool isOverhead = method == null; - if (!isOverhead) + continuation = Continuation; + bool isIdle = method == null; + if (!isIdle) { - startTaskCallback = CreateWorkload>>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkload>>(instance, method); + InvokeSingle = InvokeSingleHardcoded; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcoded; } else { callback = Overhead; + InvokeSingle = InvokeSingleHardcodedOverhead; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcodedOverhead; } + } - InvokeSingle = InvokeSingleHardcoded; + private Task Overhead() => default; - if (UseFallbackCode(codegenMode, unrollFactor)) + private ValueTask InvokeSingleHardcodedOverhead() + { + InvokeNoUnrollHardcodedOverhead(1, null); + return new ValueTask(); + } + + private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + startedClock = clock?.Start() ?? default; + try { - unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + while (--repeatsRemaining >= 0) + { + callback(); + } } - else + catch (Exception) { - InvokeMultiple = EmitInvokeMultiple(this, nameof(callback), nameof(result), unrollFactor); + throw; } + return new ValueTask(startedClock.GetElapsed()); } - private T Overhead() => default; + private ValueTask InvokeSingleHardcoded() + { + InvokeNoUnrollHardcoded(1, null); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } + + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + valueTaskSource.Reset(); + startedClock = clock?.Start() ?? default; + RunTask(); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback()); + private void RunTask() + { + try + { + while (--repeatsRemaining >= 0) + { + currentAwaiter = callback().GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + result = currentAwaiter.GetResult(); + } + } + catch (Exception e) + { + SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetResult(clockspan); + } - private void InvokeSingleHardcoded() => result = callback(); + private void Continuation() + { + try + { + result = currentAwaiter.GetResult(); + } + catch (Exception e) + { + SetException(e); + return; + } + RunTask(); + } - private void InvokeMultipleHardcoded(long repeatCount) + private void SetException(Exception e) { - for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetException(e); } public override object LastRunResult => result; @@ -179,95 +372,234 @@ private void InvokeMultipleHardcoded(long repeatCount) internal class BenchmarkActionValueTask : BenchmarkActionBase { - private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); - private readonly Func startTaskCallback; - private readonly Action callback; - private readonly Action unrolledCallback; + private readonly Func callback; + private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private long repeatsRemaining; + private readonly Action continuation; + private StartedClock startedClock; + private ValueTaskAwaiter currentAwaiter; public BenchmarkActionValueTask(object instance, MethodInfo method, BenchmarkActionCodegen codegenMode, int unrollFactor) { + continuation = Continuation; bool isIdle = method == null; if (!isIdle) { - startTaskCallback = CreateWorkload>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkload>(instance, method); + InvokeSingle = InvokeSingleHardcoded; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcoded; } else { callback = Overhead; + InvokeSingle = InvokeSingleHardcodedOverhead; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcodedOverhead; } + } - InvokeSingle = callback; + private ValueTask Overhead() => default; - if (UseFallbackCode(codegenMode, unrollFactor)) + private ValueTask InvokeSingleHardcodedOverhead() + { + InvokeNoUnrollHardcodedOverhead(1, null); + return new ValueTask(); + } + + private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + startedClock = clock?.Start() ?? default; + try { - unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + while (--repeatsRemaining >= 0) + { + callback(); + } } - else + catch (Exception) { - InvokeMultiple = EmitInvokeMultiple(this, nameof(callback), null, unrollFactor); + throw; } + return new ValueTask(startedClock.GetElapsed()); } - // must be kept in sync with VoidDeclarationsProvider.IdleImplementation - private void Overhead() { } + private ValueTask InvokeSingleHardcoded() + { + InvokeNoUnrollHardcoded(1, null); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - // must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate - private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke()); + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + valueTaskSource.Reset(); + startedClock = clock?.Start() ?? default; + RunTask(); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - private void InvokeMultipleHardcoded(long repeatCount) + private void RunTask() { - for (long i = 0; i < repeatCount; i++) - unrolledCallback(); + try + { + while (--repeatsRemaining >= 0) + { + currentAwaiter = callback().GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + currentAwaiter.GetResult(); + } + } + catch (Exception e) + { + SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetResult(clockspan); + } + + private void Continuation() + { + try + { + currentAwaiter.GetResult(); + } + catch (Exception e) + { + SetException(e); + return; + } + RunTask(); + } + + private void SetException(Exception e) + { + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetException(e); } } internal class BenchmarkActionValueTask : BenchmarkActionBase { - private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); - private readonly Func> startTaskCallback; - private readonly Func callback; - private readonly Func unrolledCallback; + private readonly Func> callback; + private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private long repeatsRemaining; + private readonly Action continuation; + private StartedClock startedClock; + private ValueTaskAwaiter currentAwaiter; private T result; public BenchmarkActionValueTask(object instance, MethodInfo method, BenchmarkActionCodegen codegenMode, int unrollFactor) { - bool isOverhead = method == null; - if (!isOverhead) + continuation = Continuation; + bool isIdle = method == null; + if (!isIdle) { - startTaskCallback = CreateWorkload>>(instance, method); - callback = ExecuteBlocking; + callback = CreateWorkload>>(instance, method); + InvokeSingle = InvokeSingleHardcoded; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcoded; } else { callback = Overhead; + InvokeSingle = InvokeSingleHardcodedOverhead; + InvokeUnroll = InvokeNoUnroll = InvokeNoUnrollHardcodedOverhead; } + } - InvokeSingle = InvokeSingleHardcoded; + private ValueTask Overhead() => default; - if (UseFallbackCode(codegenMode, unrollFactor)) + private ValueTask InvokeSingleHardcodedOverhead() + { + InvokeNoUnrollHardcodedOverhead(1, null); + return new ValueTask(); + } + + private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + startedClock = clock?.Start() ?? default; + try { - unrolledCallback = Unroll(callback, unrollFactor); - InvokeMultiple = InvokeMultipleHardcoded; + while (--repeatsRemaining >= 0) + { + callback(); + } } - else + catch (Exception) { - InvokeMultiple = EmitInvokeMultiple(this, nameof(callback), nameof(result), unrollFactor); + throw; } + return new ValueTask(startedClock.GetElapsed()); + } + + private ValueTask InvokeSingleHardcoded() + { + InvokeNoUnrollHardcoded(1, null); + return new ValueTask(valueTaskSource, valueTaskSource.Version); } - private T Overhead() => default; + private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) + { + repeatsRemaining = repeatCount; + valueTaskSource.Reset(); + startedClock = clock?.Start() ?? default; + RunTask(); + return new ValueTask(valueTaskSource, valueTaskSource.Version); + } - // must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate - private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback()); + private void RunTask() + { + try + { + while (--repeatsRemaining >= 0) + { + currentAwaiter = callback().GetAwaiter(); + if (!currentAwaiter.IsCompleted) + { + currentAwaiter.UnsafeOnCompleted(continuation); + return; + } + result = currentAwaiter.GetResult(); + } + } + catch (Exception e) + { + SetException(e); + return; + } + var clockspan = startedClock.GetElapsed(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetResult(clockspan); + } - private void InvokeSingleHardcoded() => result = callback(); + private void Continuation() + { + try + { + result = currentAwaiter.GetResult(); + } + catch (Exception e) + { + SetException(e); + return; + } + RunTask(); + } - private void InvokeMultipleHardcoded(long repeatCount) + private void SetException(Exception e) { - for (long i = 0; i < repeatCount; i++) - result = unrolledCallback(); + currentAwaiter = default; + startedClock = default; + valueTaskSource.SetException(e); } public override object LastRunResult => result; diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs b/src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs index aedd8f6788..9c97ac840b 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs @@ -103,7 +103,7 @@ public static void RunCore(IHost host, BenchmarkCase benchmarkCase, BenchmarkAct { var target = benchmarkCase.Descriptor; var job = benchmarkCase.Job; // TODO: filter job (same as SourceCodePresenter does)? - int unrollFactor = benchmarkCase.Job.ResolveValue(RunMode.UnrollFactorCharacteristic, EnvironmentResolver.Instance); + int unrollFactor = BenchmarkActionFactory.GetUnrollFactor(benchmarkCase); // DONTTOUCH: these should be allocated together var instance = Activator.CreateInstance(benchmarkCase.Descriptor.Type); @@ -128,21 +128,13 @@ public static void RunCore(IHost host, BenchmarkCase benchmarkCase, BenchmarkAct var engineParameters = new EngineParameters { Host = host, - WorkloadActionNoUnroll = invocationCount => - { - for (int i = 0; i < invocationCount; i++) - workloadAction.InvokeSingle(); - }, - WorkloadActionUnroll = workloadAction.InvokeMultiple, - Dummy1Action = dummy1.InvokeSingle, - Dummy2Action = dummy2.InvokeSingle, - Dummy3Action = dummy3.InvokeSingle, - OverheadActionNoUnroll = invocationCount => - { - for (int i = 0; i < invocationCount; i++) - overheadAction.InvokeSingle(); - }, - OverheadActionUnroll = overheadAction.InvokeMultiple, + WorkloadActionNoUnroll = workloadAction.InvokeNoUnroll, + WorkloadActionUnroll = workloadAction.InvokeUnroll, + Dummy1Action = () => dummy1.InvokeSingle(), + Dummy2Action = () => dummy2.InvokeSingle(), + Dummy3Action = () => dummy3.InvokeSingle(), + OverheadActionNoUnroll = overheadAction.InvokeNoUnroll, + OverheadActionUnroll = overheadAction.InvokeUnroll, GlobalSetupAction = globalSetupAction.InvokeSingle, GlobalCleanupAction = globalCleanupAction.InvokeSingle, IterationSetupAction = iterationSetupAction.InvokeSingle, diff --git a/tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs b/tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs index b1e268673c..db967c6d76 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs @@ -10,6 +10,8 @@ using BenchmarkDotNet.Reports; using BenchmarkDotNet.Characteristics; using Perfolizer.Mathematics.OutlierDetection; +using System.Threading.Tasks; +using Perfolizer.Horology; namespace BenchmarkDotNet.IntegrationTests { @@ -90,10 +92,10 @@ public void WriteLine() { } public void WriteLine(string line) { } public Job TargetJob { get; } public long OperationsPerInvoke { get; } - public Action GlobalSetupAction { get; set; } - public Action GlobalCleanupAction { get; set; } - public Action WorkloadAction { get; } - public Action OverheadAction { get; } + public Func GlobalSetupAction { get; set; } + public Func GlobalCleanupAction { get; set; } + public Func> WorkloadAction { get; } + public Func> OverheadAction { get; } public IResolver Resolver { get; } public Measurement RunIteration(IterationData data) { throw new NotImplementedException(); } diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs index 6415cf181f..d36f63872e 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs @@ -12,8 +12,10 @@ using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Running; using BenchmarkDotNet.Tests.Loggers; +using BenchmarkDotNet.Tests.Mocks; using BenchmarkDotNet.Toolchains.InProcess; using JetBrains.Annotations; +using Perfolizer.Horology; using Xunit; using Xunit.Abstractions; @@ -31,6 +33,8 @@ public InProcessTest(ITestOutputHelper output) : base(output) private const string StringResult = "42"; private const int UnrollFactor = 16; + private readonly IClock clock = new MockClock(TimeInterval.Millisecond.ToFrequency()); + private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper(); [Fact] public void BenchmarkActionGlobalSetupSupported() => TestInvoke(x => BenchmarkAllCases.GlobalSetup(), UnrollFactor); @@ -60,28 +64,28 @@ public InProcessTest(ITestOutputHelper output) : base(output) public void BenchmarkActionValueTaskOfTSupported() => TestInvoke(x => x.InvokeOnceValueTaskOfT(), UnrollFactor, DecimalResult); [Fact] - public void BenchmarkActionGlobalSetupTaskSupported() => TestInvokeSetupCleanupTask(x => BenchmarkSetupCleanupTask.GlobalSetup(), UnrollFactor); + public void BenchmarkActionGlobalSetupTaskSupported() => TestInvokeSetupCleanupTask(x => BenchmarkSetupCleanupTask.GlobalSetup()); [Fact] - public void BenchmarkActionGlobalCleanupTaskSupported() => TestInvokeSetupCleanupTask(x => x.GlobalCleanup(), UnrollFactor); + public void BenchmarkActionGlobalCleanupTaskSupported() => TestInvokeSetupCleanupTask(x => x.GlobalCleanup()); [Fact] - public void BenchmarkActionIterationSetupTaskSupported() => TestInvokeSetupCleanupTask(x => BenchmarkSetupCleanupTask.GlobalSetup(), UnrollFactor); + public void BenchmarkActionIterationSetupTaskSupported() => TestInvokeSetupCleanupTask(x => BenchmarkSetupCleanupTask.GlobalSetup()); [Fact] - public void BenchmarkActionIterationCleanupTaskSupported() => TestInvokeSetupCleanupTask(x => x.GlobalCleanup(), UnrollFactor); + public void BenchmarkActionIterationCleanupTaskSupported() => TestInvokeSetupCleanupTask(x => x.GlobalCleanup()); [Fact] - public void BenchmarkActionGlobalSetupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => BenchmarkSetupCleanupValueTask.GlobalSetup(), UnrollFactor); + public void BenchmarkActionGlobalSetupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => BenchmarkSetupCleanupValueTask.GlobalSetup()); [Fact] - public void BenchmarkActionGlobalCleanupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => x.GlobalCleanup(), UnrollFactor); + public void BenchmarkActionGlobalCleanupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => x.GlobalCleanup()); [Fact] - public void BenchmarkActionIterationSetupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => BenchmarkSetupCleanupValueTask.GlobalSetup(), UnrollFactor); + public void BenchmarkActionIterationSetupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => BenchmarkSetupCleanupValueTask.GlobalSetup()); [Fact] - public void BenchmarkActionIterationCleanupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => x.GlobalCleanup(), UnrollFactor); + public void BenchmarkActionIterationCleanupValueTaskSupported() => TestInvokeSetupCleanupValueTask(x => x.GlobalCleanup()); [AssertionMethod] private void TestInvoke(Expression> methodCall, int unrollFactor) @@ -127,6 +131,16 @@ private void TestInvoke(Expression> methodCall, in var targetMethod = ((MethodCallExpression) methodCall.Body).Method; var descriptor = new Descriptor(typeof(BenchmarkAllCases), targetMethod); + var methodReturnType = typeof(T); + bool isAwaitable = methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask) + || (methodReturnType.GetTypeInfo().IsGenericType + && (methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) + || methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>))); + if (isAwaitable) + { + unrollFactor = 1; + } + // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkAllCases(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); TestInvoke(action, unrollFactor, false, expectedResult, ref BenchmarkAllCases.Counter); @@ -135,14 +149,14 @@ private void TestInvoke(Expression> methodCall, in // Idle mode - bool isValueTask = typeof(T).IsConstructedGenericType && typeof(T).GetGenericTypeDefinition() == typeof(ValueTask<>); + bool isValueTask = methodReturnType.IsConstructedGenericType && methodReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>); object idleExpected; if (isValueTask) - idleExpected = GetDefault(typeof(T).GetGenericArguments()[0]); - else if (expectedResult == null || typeof(T) == typeof(Task) || typeof(T) == typeof(ValueTask)) + idleExpected = GetDefault(methodReturnType.GetGenericArguments()[0]); + else if (expectedResult == null || methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask)) idleExpected = null; - else if (typeof(T).GetTypeInfo().IsValueType) + else if (methodReturnType.GetTypeInfo().IsValueType) idleExpected = 0; else idleExpected = GetDefault(expectedResult.GetType()); @@ -154,10 +168,11 @@ private void TestInvoke(Expression> methodCall, in } [AssertionMethod] - private void TestInvokeSetupCleanupTask(Expression> methodCall, int unrollFactor) + private void TestInvokeSetupCleanupTask(Expression> methodCall) { var targetMethod = ((MethodCallExpression) methodCall.Body).Method; var descriptor = new Descriptor(typeof(BenchmarkSetupCleanupTask), targetMethod, targetMethod, targetMethod, targetMethod, targetMethod); + int unrollFactor = 1; // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkSetupCleanupTask(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); @@ -192,10 +207,11 @@ private void TestInvokeSetupCleanupTask(Expression> methodCall, int unrollFactor) + private void TestInvokeSetupCleanupValueTask(Expression> methodCall) { var targetMethod = ((MethodCallExpression) methodCall.Body).Method; var descriptor = new Descriptor(typeof(BenchmarkSetupCleanupValueTask), targetMethod, targetMethod, targetMethod, targetMethod, targetMethod); + int unrollFactor = 1; // Run mode var action = BenchmarkActionFactory.CreateWorkload(descriptor, new BenchmarkSetupCleanupValueTask(), BenchmarkActionCodegen.ReflectionEmit, unrollFactor); @@ -247,20 +263,20 @@ private void TestInvoke(BenchmarkAction benchmarkAction, int unrollFactor, bool if (isIdle) { - benchmarkAction.InvokeSingle(); + awaitHelper.GetResult(benchmarkAction.InvokeSingle()); Assert.Equal(0, counter); - benchmarkAction.InvokeMultiple(0); + awaitHelper.GetResult(benchmarkAction.InvokeUnroll(0, clock)); Assert.Equal(0, counter); - benchmarkAction.InvokeMultiple(11); + awaitHelper.GetResult(benchmarkAction.InvokeUnroll(11, clock)); Assert.Equal(0, counter); } else { - benchmarkAction.InvokeSingle(); + awaitHelper.GetResult(benchmarkAction.InvokeSingle()); Assert.Equal(1, counter); - benchmarkAction.InvokeMultiple(0); + awaitHelper.GetResult(benchmarkAction.InvokeUnroll(0, clock)); Assert.Equal(1, counter); - benchmarkAction.InvokeMultiple(11); + awaitHelper.GetResult(benchmarkAction.InvokeUnroll(11, clock)); Assert.Equal(1 + unrollFactor * 11, counter); } diff --git a/tests/BenchmarkDotNet.Tests/Engine/EngineFactoryTests.cs b/tests/BenchmarkDotNet.Tests/Engine/EngineFactoryTests.cs index 89c6fa05bd..27eb1431f9 100644 --- a/tests/BenchmarkDotNet.Tests/Engine/EngineFactoryTests.cs +++ b/tests/BenchmarkDotNet.Tests/Engine/EngineFactoryTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; +using System.Threading.Tasks; using BenchmarkDotNet.Characteristics; using BenchmarkDotNet.Engines; using BenchmarkDotNet.Jobs; @@ -21,32 +22,72 @@ public class EngineFactoryTests private IResolver DefaultResolver => BenchmarkRunnerClean.DefaultResolver; - private void GlobalSetup() => timesGlobalSetupCalled++; - private void IterationSetup() => timesIterationSetupCalled++; - private void IterationCleanup() => timesIterationCleanupCalled++; - private void GlobalCleanup() => timesGlobalCleanupCalled++; + private ValueTask GlobalSetup() + { + timesGlobalSetupCalled++; + return new ValueTask(); + } + private ValueTask IterationSetup() + { + timesIterationSetupCalled++; + return new ValueTask(); + } + private ValueTask IterationCleanup() + { + timesIterationCleanupCalled++; + return new ValueTask(); + } + private ValueTask GlobalCleanup() + { + timesGlobalCleanupCalled++; + return new ValueTask(); + } - private void Throwing(long _) => throw new InvalidOperationException("must NOT be called"); + private ValueTask Throwing(long _, IClock __) => throw new InvalidOperationException("must NOT be called"); - private void VeryTimeConsumingSingle(long _) + private ValueTask VeryTimeConsumingSingle(long _, IClock clock) { + var startedClock = clock.Start(); timesBenchmarkCalled++; Thread.Sleep(IterationTime); + return new ValueTask(startedClock.GetElapsed()); } - private void TimeConsumingOnlyForTheFirstCall(long _) + private ValueTask TimeConsumingOnlyForTheFirstCall(long _, IClock clock) { + var startedClock = clock.Start(); if (timesBenchmarkCalled++ == 0) { Thread.Sleep(IterationTime); } + return new ValueTask(startedClock.GetElapsed()); } - private void InstantNoUnroll(long invocationCount) => timesBenchmarkCalled += (int) invocationCount; - private void InstantUnroll(long _) => timesBenchmarkCalled += 16; + private ValueTask InstantNoUnroll(long invocationCount, IClock clock) + { + var startedClock = clock.Start(); + timesBenchmarkCalled += (int) invocationCount; + return new ValueTask(startedClock.GetElapsed()); + } + private ValueTask InstantUnroll(long _, IClock clock) + { + var startedClock = clock.Start(); + timesBenchmarkCalled += 16; + return new ValueTask(startedClock.GetElapsed()); + } - private void OverheadNoUnroll(long invocationCount) => timesOverheadCalled += (int) invocationCount; - private void OverheadUnroll(long _) => timesOverheadCalled += 16; + private ValueTask OverheadNoUnroll(long invocationCount, IClock clock) + { + var startedClock = clock.Start(); + timesOverheadCalled += (int) invocationCount; + return new ValueTask(startedClock.GetElapsed()); + } + private ValueTask OverheadUnroll(long _, IClock clock) + { + var startedClock = clock.Start(); + timesOverheadCalled += 16; + return new ValueTask(startedClock.GetElapsed()); + } private static readonly Dictionary JobsWhichDontRequireJitting = new Dictionary { @@ -197,22 +238,26 @@ public void MediumTimeConsumingBenchmarksShouldStartPilotFrom2AndIncrementItWith var mediumTime = TimeSpan.FromMilliseconds(IterationTime.TotalMilliseconds / times); - void MediumNoUnroll(long invocationCount) + ValueTask MediumNoUnroll(long invocationCount, IClock clock) { + var startedClock = clock.Start(); for (int i = 0; i < invocationCount; i++) { timesBenchmarkCalled++; Thread.Sleep(mediumTime); } + return new ValueTask(startedClock.GetElapsed()); } - void MediumUnroll(long _) + ValueTask MediumUnroll(long _, IClock clock) { + var startedClock = clock.Start(); timesBenchmarkCalled += unrollFactor; for (int i = 0; i < unrollFactor; i++) // the real unroll factor obviously does not use loop ;) Thread.Sleep(mediumTime); + return new ValueTask(startedClock.GetElapsed()); } var engineParameters = CreateEngineParameters(mainNoUnroll: MediumNoUnroll, mainUnroll: MediumUnroll, job: Job.Default); @@ -239,7 +284,7 @@ void MediumUnroll(long _) Assert.Equal(1, timesGlobalCleanupCalled); } - private EngineParameters CreateEngineParameters(Action mainNoUnroll, Action mainUnroll, Job job) + private EngineParameters CreateEngineParameters(Func> mainNoUnroll, Func> mainUnroll, Job job) => new EngineParameters { Dummy1Action = () => { }, From 8bd86067e14f782e8c04bbc03b7222bd1bbfe4f6 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 22 Mar 2022 14:52:47 -0400 Subject: [PATCH 13/22] Fixed compile errors with .Net Framework and Mono runtimes. Properly force unroll factor to 1. Wait for Setup/Cleanup actions in engine. --- src/BenchmarkDotNet/Code/CodeGenerator.cs | 2 ++ src/BenchmarkDotNet/Engines/Engine.cs | 12 ++++++------ src/BenchmarkDotNet/Engines/EngineFactory.cs | 2 +- src/BenchmarkDotNet/Toolchains/Roslyn/Generator.cs | 6 ++++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/BenchmarkDotNet/Code/CodeGenerator.cs b/src/BenchmarkDotNet/Code/CodeGenerator.cs index 478ff8866e..917110e807 100644 --- a/src/BenchmarkDotNet/Code/CodeGenerator.cs +++ b/src/BenchmarkDotNet/Code/CodeGenerator.cs @@ -35,6 +35,8 @@ internal static string Generate(BuildPartition buildPartition) var provider = GetDeclarationsProvider(benchmark.Descriptor); + provider.OverrideUnrollFactor(benchmark); + string passArguments = GetPassArguments(benchmark); string compilationId = $"{provider.ReturnsDefinition}_{buildInfo.Id}"; diff --git a/src/BenchmarkDotNet/Engines/Engine.cs b/src/BenchmarkDotNet/Engines/Engine.cs index ee5e254265..c843ae841a 100644 --- a/src/BenchmarkDotNet/Engines/Engine.cs +++ b/src/BenchmarkDotNet/Engines/Engine.cs @@ -158,7 +158,7 @@ public Measurement RunIteration(IterationData data) var action = isOverhead ? OverheadAction : WorkloadAction; if (!isOverhead) - IterationSetupAction(); + awaitHelper.GetResult(IterationSetupAction()); GcCollect(); @@ -175,7 +175,7 @@ public Measurement RunIteration(IterationData data) EngineEventSource.Log.IterationStop(data.IterationMode, data.IterationStage, totalOperations); if (!isOverhead) - IterationCleanupAction(); + awaitHelper.GetResult(IterationCleanupAction()); if (randomizeMemory) RandomizeManagedHeapMemory(); @@ -198,7 +198,7 @@ public Measurement RunIteration(IterationData data) // it does not matter, because we have already obtained the results! EnableMonitoring(); - IterationSetupAction(); // we run iteration setup first, so even if it allocates, it is not included in the results + awaitHelper.GetResult(IterationSetupAction()); // we run iteration setup first, so even if it allocates, it is not included in the results var initialThreadingStats = ThreadingStats.ReadInitial(); // this method might allocate var initialGcStats = GcStats.ReadInitial(); @@ -209,7 +209,7 @@ public Measurement RunIteration(IterationData data) var finalGcStats = GcStats.ReadFinal(); var finalThreadingStats = ThreadingStats.ReadFinal(); - IterationCleanupAction(); // we run iteration cleanup after collecting GC stats + awaitHelper.GetResult(IterationCleanupAction()); // we run iteration cleanup after collecting GC stats GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke); ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke); @@ -223,14 +223,14 @@ private void Consume(in Span _) { } private void RandomizeManagedHeapMemory() { // invoke global cleanup before global setup - GlobalCleanupAction?.Invoke(); + awaitHelper.GetResult(GlobalCleanupAction.Invoke()); var gen0object = new byte[random.Next(32)]; var lohObject = new byte[85 * 1024 + random.Next(32)]; // we expect the key allocations to happen in global setup (not ctor) // so we call it while keeping the random-size objects alive - GlobalSetupAction?.Invoke(); + awaitHelper.GetResult(GlobalSetupAction.Invoke()); GC.KeepAlive(gen0object); GC.KeepAlive(lohObject); diff --git a/src/BenchmarkDotNet/Engines/EngineFactory.cs b/src/BenchmarkDotNet/Engines/EngineFactory.cs index f2a807b560..e311a927ee 100644 --- a/src/BenchmarkDotNet/Engines/EngineFactory.cs +++ b/src/BenchmarkDotNet/Engines/EngineFactory.cs @@ -26,7 +26,7 @@ public IEngine CreateReadyToRun(EngineParameters engineParameters) if (engineParameters.TargetJob == null) throw new ArgumentNullException(nameof(engineParameters.TargetJob)); - engineParameters.GlobalSetupAction?.Invoke(); // whatever the settings are, we MUST call global setup here, the global cleanup is part of Engine's Dispose + engineParameters.GlobalSetupAction.Invoke().AsTask().GetAwaiter().GetResult(); // whatever the settings are, we MUST call global setup here, the global cleanup is part of Engine's Dispose if (!engineParameters.NeedsJitting) // just create the engine, do NOT jit return CreateMultiActionEngine(engineParameters); diff --git a/src/BenchmarkDotNet/Toolchains/Roslyn/Generator.cs b/src/BenchmarkDotNet/Toolchains/Roslyn/Generator.cs index ef11c17d91..aee6d271ae 100644 --- a/src/BenchmarkDotNet/Toolchains/Roslyn/Generator.cs +++ b/src/BenchmarkDotNet/Toolchains/Roslyn/Generator.cs @@ -55,8 +55,10 @@ internal static IEnumerable GetAllReferences(BenchmarkCase benchmarkCa .Concat( new[] { - benchmarkCase.Descriptor.Type.GetTypeInfo().Assembly, // this assembly does not has to have a reference to BenchmarkDotNet (e.g. custom framework for benchmarking that internally uses BenchmarkDotNet - typeof(BenchmarkCase).Assembly // BenchmarkDotNet + benchmarkCase.Descriptor.Type.GetTypeInfo().Assembly, // this assembly does not have to have a reference to BenchmarkDotNet (e.g. custom framework for benchmarking that internally uses BenchmarkDotNet + typeof(BenchmarkCase).Assembly, // BenchmarkDotNet + typeof(System.Threading.Tasks.ValueTask).Assembly, // TaskExtensions + typeof(Perfolizer.Horology.IClock).Assembly // Perfolizer }) .Distinct(); } From 24e5b0169d7995ed2b864ef4bd14c12be07a9f5f Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 26 Mar 2022 03:44:24 -0400 Subject: [PATCH 14/22] Update RunnableEmitter, WIP. TODO: debug cause of crash --- .../IlGeneratorStatementExtensions.cs | 84 ++- .../Templates/BenchmarkType.txt | 5 +- .../ConsumableTypeInfo.cs | 39 +- .../Emitters/ConsumeEmitter.cs | 119 ++++ .../Emitters/RunnableEmitter.cs | 287 +++----- .../Emitters/TaskConsumeEmitter.cs | 658 ++++++++++++++++++ .../Runnable/RunnableConstants.cs | 13 +- .../BenchmarkActionFactory_Implementations.cs | 16 +- .../BenchmarkDotNet.Tests/Mocks/MockEngine.cs | 9 +- 9 files changed, 940 insertions(+), 290 deletions(-) create mode 100644 src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs index 65b016a0dd..118ba517f4 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs @@ -42,37 +42,6 @@ public static void EmitVoidReturn(this ILGenerator ilBuilder, MethodBuilder meth ilBuilder.Emit(OpCodes.Ret); } - public static void EmitSetFieldToNewInstance( - this ILGenerator ilBuilder, - FieldBuilder field, - Type instanceType) - { - if (field.IsStatic) - throw new ArgumentException("The field should be instance field", nameof(field)); - - if (instanceType != null) - { - /* - IL_0006: ldarg.0 - IL_0007: newobj instance void BenchmarkDotNet.Helpers.AwaitHelper::.ctor() - IL_000c: stfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Autogenerated.Runnable_0::awaitHelper - */ - var ctor = instanceType.GetConstructor(Array.Empty()); - if (ctor == null) - throw new InvalidOperationException($"Bug: instanceType {instanceType.Name} does not have a 0-parameter accessible constructor."); - - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Newobj, ctor); - ilBuilder.Emit(OpCodes.Stfld, field); - } - else - { - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldnull); - ilBuilder.Emit(OpCodes.Stfld, field); - } - } - public static void EmitSetDelegateToThisField( this ILGenerator ilBuilder, FieldBuilder delegateField, @@ -174,5 +143,58 @@ public static void EmitLoopEndFromLocToArg( ilBuilder.EmitLdarg(toArg); ilBuilder.Emit(OpCodes.Blt, loopStartLabel); } + + public static void EmitLoopBeginFromFldTo0( + this ILGenerator ilBuilder, + Label loopStartLabel, + Label loopHeadLabel) + { + // IL_001b: br.s IL_0029 // loop start (head: IL_0029) + ilBuilder.Emit(OpCodes.Br, loopHeadLabel); + + // loop start (head: IL_0036) + ilBuilder.MarkLabel(loopStartLabel); + } + + public static void EmitLoopEndFromFldTo0( + this ILGenerator ilBuilder, + Label loopStartLabel, + Label loopHeadLabel, + FieldBuilder counterField, + LocalBuilder counterLocal) + { + // loop counter stored as loc0, loop max passed as arg1 + /* + // while (--repeatsRemaining >= 0) + IL_0029: ldarg.0 + IL_002a: ldarg.0 + IL_002b: ldfld int64 BenchmarkRunner_0::repeatsRemaining + IL_0030: ldc.i4.1 + IL_0031: conv.i8 + IL_0032: sub + IL_0033: stloc.1 + IL_0034: ldloc.1 + IL_0035: stfld int64 BenchmarkRunner_0::repeatsRemaining + IL_003a: ldloc.1 + IL_003b: ldc.i4.0 + IL_003c: conv.i8 + IL_003d: bge.s IL_001d + // end loop + */ + ilBuilder.MarkLabel(loopHeadLabel); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, counterField); + ilBuilder.Emit(OpCodes.Ldc_I4_1); + ilBuilder.Emit(OpCodes.Conv_I8); + ilBuilder.Emit(OpCodes.Sub); + ilBuilder.EmitStloc(counterLocal); + ilBuilder.EmitLdloc(counterLocal); + ilBuilder.Emit(OpCodes.Stfld, counterField); + ilBuilder.EmitLdloc(counterLocal); + ilBuilder.Emit(OpCodes.Ldc_I4_0); + ilBuilder.Emit(OpCodes.Conv_I8); + ilBuilder.Emit(OpCodes.Bge_S, loopStartLabel); + } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index aa4e0ef18e..6a53e55ef5 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -146,11 +146,10 @@ } catch (System.Exception) { + BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); throw; } - var elapsed = startedClock.GetElapsed(); - BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs index b0a7189e3f..709ce82d31 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs @@ -17,7 +17,7 @@ public ConsumableTypeInfo(Type methodReturnType) if (methodReturnType == null) throw new ArgumentNullException(nameof(methodReturnType)); - OriginMethodReturnType = methodReturnType; + WorkloadMethodReturnType = methodReturnType; // Only support (Value)Task for parity with other toolchains (and so we can use AwaitHelper). IsAwaitable = methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask) @@ -25,38 +25,6 @@ public ConsumableTypeInfo(Type methodReturnType) && (methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) || methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(ValueTask<>))); - if (!IsAwaitable) - { - WorkloadMethodReturnType = methodReturnType; - } - else - { - WorkloadMethodReturnType = methodReturnType - .GetMethod(nameof(Task.GetAwaiter), BindingFlagsPublicInstance) - .ReturnType - .GetMethod(nameof(TaskAwaiter.GetResult), BindingFlagsPublicInstance) - .ReturnType; - if (methodReturnType.GetTypeInfo().IsGenericType) - { - Type compareType = methodReturnType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) - ? typeof(Task<>) - : typeof(ValueTask<>); - GetResultMethod = typeof(Helpers.AwaitHelper).GetMethods(BindingFlagsPublicInstance) - .First(m => - { - if (m.Name != nameof(Helpers.AwaitHelper.GetResult)) return false; - Type paramType = m.GetParameters().First().ParameterType; - // We have to compare the types indirectly, == check doesn't work. - return paramType.Assembly == compareType.Assembly && paramType.Namespace == compareType.Namespace && paramType.Name == compareType.Name; - }) - .MakeGenericMethod(new Type[1] { WorkloadMethodReturnType }); - } - else - { - GetResultMethod = typeof(Helpers.AwaitHelper).GetMethod(nameof(Helpers.AwaitHelper.GetResult), BindingFlagsPublicInstance, null, new Type[1] { methodReturnType }, null); - } - } - if (WorkloadMethodReturnType == null) throw new InvalidOperationException("Bug: (WorkloadMethodReturnType == null"); @@ -87,16 +55,11 @@ public ConsumableTypeInfo(Type methodReturnType) throw new InvalidOperationException("Bug: (OverheadResultType == null"); } - [NotNull] - public Type OriginMethodReturnType { get; } [NotNull] public Type WorkloadMethodReturnType { get; } [NotNull] public Type OverheadMethodReturnType { get; } - [CanBeNull] - public MethodInfo GetResultMethod { get; } - public bool IsVoid { get; } public bool IsByRef { get; } public bool IsConsumable { get; } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumeEmitter.cs index 62fe06c649..f4ecb98e35 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumeEmitter.cs @@ -1,7 +1,12 @@ using System; using System.Reflection; using System.Reflection.Emit; +using System.Threading.Tasks; +using BenchmarkDotNet.Helpers.Reflection.Emit; using JetBrains.Annotations; +using Perfolizer.Horology; +using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants; +using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableReflectionHelpers; namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation { @@ -12,6 +17,8 @@ public static ConsumeEmitter GetConsumeEmitter(ConsumableTypeInfo consumableType if (consumableTypeInfo == null) throw new ArgumentNullException(nameof(consumableTypeInfo)); + if (consumableTypeInfo.IsAwaitable) + return new TaskConsumeEmitter(consumableTypeInfo); if (consumableTypeInfo.IsVoid) return new VoidConsumeEmitter(consumableTypeInfo); if (consumableTypeInfo.IsByRef) @@ -223,5 +230,117 @@ public void EmitActionAfterCall(ILGenerator ilBuilder) protected virtual void EmitActionAfterCallOverride(ILGenerator ilBuilder) { } + + public virtual MethodBuilder EmitActionImpl(RunnableEmitter runnableEmitter, string methodName, RunnableActionKind actionKind, int unrollFactor) + { + FieldInfo actionDelegateField; + MethodInfo actionInvokeMethod; + switch (actionKind) + { + case RunnableActionKind.Overhead: + actionDelegateField = runnableEmitter.overheadDelegateField; + actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(runnableEmitter.overheadDelegateType); + break; + case RunnableActionKind.Workload: + actionDelegateField = runnableEmitter.workloadDelegateField; + actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(runnableEmitter.workloadDelegateType); + break; + default: + throw new ArgumentOutOfRangeException(nameof(actionKind), actionKind, null); + } + + /* + .method private hidebysig + instance valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1 WorkloadActionNoUnroll ( + int64 invokeCount, + class Perfolizer.Horology.IClock clock + ) cil managed + */ + var toArg = new EmitParameterInfo(0, InvokeCountParamName, typeof(long)); + var clockArg = new EmitParameterInfo(1, ClockParamName, typeof(IClock)); + var actionMethodBuilder = runnableEmitter.runnableBuilder.DefineNonVirtualInstanceMethod( + methodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnParameter(typeof(ValueTask)), + toArg, clockArg); + toArg.SetMember(actionMethodBuilder); + clockArg.SetMember(actionMethodBuilder); + + // Emit impl + var ilBuilder = actionMethodBuilder.GetILGenerator(); + BeginEmitAction(actionMethodBuilder, ilBuilder, actionInvokeMethod, actionKind); + + // init locals + var argLocals = runnableEmitter.EmitDeclareArgLocals(ilBuilder); + DeclareActionLocals(ilBuilder); + var startedClockLocal = ilBuilder.DeclareLocal(typeof(StartedClock)); + var indexLocal = ilBuilder.DeclareLocal(typeof(long)); + + // load fields + runnableEmitter.EmitLoadArgFieldsToLocals(ilBuilder, argLocals); + EmitActionBeforeLoop(ilBuilder); + + // start clock + /* + // var startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); + IL_0000: ldarg.2 + IL_0001: call valuetype Perfolizer.Horology.StartedClock Perfolizer.Horology.ClockExtensions::Start(class Perfolizer.Horology.IClock) + IL_0006: stloc.0 + */ + ilBuilder.EmitLdarg(clockArg); + ilBuilder.Emit(OpCodes.Call, runnableEmitter.startClockMethod); + ilBuilder.EmitStloc(startedClockLocal); + + // loop + var loopStartLabel = ilBuilder.DefineLabel(); + var loopHeadLabel = ilBuilder.DefineLabel(); + ilBuilder.EmitLoopBeginFromLocToArg(loopStartLabel, loopHeadLabel, indexLocal, toArg); + { + /* + // overheadDelegate(); + IL_0005: ldarg.0 + IL_0006: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate + IL_000b: callvirt instance void BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke() + // -or- + // consumer.Consume(overheadDelegate(_argField)); + IL_000c: ldarg.0 + IL_000d: ldfld class [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer BenchmarkDotNet.Autogenerated.Runnable_0::consumer + IL_0012: ldarg.0 + IL_0013: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate + IL_0018: ldloc.0 + IL_0019: callvirt instance int32 BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke(int64) + IL_001e: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(int32) + */ + for (int u = 0; u < unrollFactor; u++) + { + EmitActionBeforeCall(ilBuilder); + + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, actionDelegateField); + ilBuilder.EmitInstanceCallThisValueOnStack(null, actionInvokeMethod, argLocals); + + EmitActionAfterCall(ilBuilder); + } + } + ilBuilder.EmitLoopEndFromLocToArg(loopStartLabel, loopHeadLabel, indexLocal, toArg); + + EmitActionAfterLoop(ilBuilder); + CompleteEmitAction(ilBuilder); + + /* + // return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); + IL_0021: ldloca.s 0 + IL_0023: call instance valuetype Perfolizer.Horology.ClockSpan Perfolizer.Horology.StartedClock::GetElapsed() + IL_0028: newobj instance void valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1::.ctor(!0) + IL_002d: ret + */ + ilBuilder.EmitLdloca(startedClockLocal); + ilBuilder.Emit(OpCodes.Call, runnableEmitter.getElapsedMethod); + var ctor = typeof(ValueTask).GetConstructor(new[] { typeof(ClockSpan) }); + ilBuilder.Emit(OpCodes.Newobj, ctor); + ilBuilder.Emit(OpCodes.Ret); + + return actionMethodBuilder; + } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index 83fc550906..c995ab8e92 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -7,6 +7,7 @@ using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security; +using System.Threading.Tasks; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Engines; using BenchmarkDotNet.Helpers.Reflection.Emit; @@ -15,6 +16,7 @@ using BenchmarkDotNet.Running; using BenchmarkDotNet.Toolchains.Results; using JetBrains.Annotations; +using Perfolizer.Horology; using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants; using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableReflectionHelpers; @@ -240,9 +242,9 @@ private static void EmitNoArgsMethodCallPopReturn( private int jobUnrollFactor; private int dummyUnrollFactor; - private Type overheadDelegateType; - private Type workloadDelegateType; - private TypeBuilder runnableBuilder; + public Type overheadDelegateType; + public Type workloadDelegateType; + public TypeBuilder runnableBuilder; private ConsumableTypeInfo consumableInfo; private ConsumeEmitter consumeEmitter; private ConsumableTypeInfo globalSetupReturnInfo; @@ -250,13 +252,12 @@ private static void EmitNoArgsMethodCallPopReturn( private ConsumableTypeInfo iterationSetupReturnInfo; private ConsumableTypeInfo iterationCleanupReturnInfo; - private FieldBuilder awaitHelperField; private FieldBuilder globalSetupActionField; private FieldBuilder globalCleanupActionField; private FieldBuilder iterationSetupActionField; private FieldBuilder iterationCleanupActionField; - private FieldBuilder overheadDelegateField; - private FieldBuilder workloadDelegateField; + public FieldBuilder overheadDelegateField; + public FieldBuilder workloadDelegateField; private FieldBuilder notElevenField; private FieldBuilder dummyVarField; @@ -266,7 +267,6 @@ private static void EmitNoArgsMethodCallPopReturn( private MethodBuilder dummy1Method; private MethodBuilder dummy2Method; private MethodBuilder dummy3Method; - private MethodInfo workloadImplementationMethod; private MethodBuilder overheadImplementationMethod; private MethodBuilder overheadActionUnrollMethod; private MethodBuilder overheadActionNoUnrollMethod; @@ -282,6 +282,9 @@ private static void EmitNoArgsMethodCallPopReturn( private MethodBuilder runMethod; // ReSharper restore NotAccessedField.Local + public readonly MethodInfo getElapsedMethod; + public readonly MethodInfo startClockMethod; + private RunnableEmitter([NotNull] BuildPartition buildPartition, [NotNull] ModuleBuilder moduleBuilder) { if (buildPartition == null) @@ -291,6 +294,8 @@ private RunnableEmitter([NotNull] BuildPartition buildPartition, [NotNull] Modul this.buildPartition = buildPartition; this.moduleBuilder = moduleBuilder; + getElapsedMethod = typeof(StartedClock).GetMethod(nameof(StartedClock.GetElapsed), BindingFlagsPublicInstance); + startClockMethod = typeof(ClockExtensions).GetMethod(nameof(ClockExtensions.Start), BindingFlagsPublicStatic); } [NotNull] @@ -325,7 +330,6 @@ private Type EmitRunnableCore(BenchmarkBuildInfo newBenchmark) overheadActionNoUnrollMethod = EmitOverheadAction(OverheadActionNoUnrollMethodName, 1); // Workload impl - workloadImplementationMethod = EmitWorkloadImplementation(WorkloadImplementationMethodName); workloadActionUnrollMethod = EmitWorkloadAction(WorkloadActionUnrollMethodName, jobUnrollFactor); workloadActionNoUnrollMethod = EmitWorkloadAction(WorkloadActionNoUnrollMethodName, 1); @@ -432,16 +436,14 @@ private Type EmitWorkloadDelegateType() private void DefineFields() { - awaitHelperField = - runnableBuilder.DefineField(AwaitHelperFieldName, typeof(Helpers.AwaitHelper), FieldAttributes.Private | FieldAttributes.InitOnly); globalSetupActionField = - runnableBuilder.DefineField(GlobalSetupActionFieldName, typeof(Action), FieldAttributes.Private); + runnableBuilder.DefineField(GlobalSetupActionFieldName, typeof(Func), FieldAttributes.Private); globalCleanupActionField = - runnableBuilder.DefineField(GlobalCleanupActionFieldName, typeof(Action), FieldAttributes.Private); + runnableBuilder.DefineField(GlobalCleanupActionFieldName, typeof(Func), FieldAttributes.Private); iterationSetupActionField = - runnableBuilder.DefineField(IterationSetupActionFieldName, typeof(Action), FieldAttributes.Private); + runnableBuilder.DefineField(IterationSetupActionFieldName, typeof(Func), FieldAttributes.Private); iterationCleanupActionField = - runnableBuilder.DefineField(IterationCleanupActionFieldName, typeof(Action), FieldAttributes.Private); + runnableBuilder.DefineField(IterationCleanupActionFieldName, typeof(Func), FieldAttributes.Private); overheadDelegateField = runnableBuilder.DefineField(OverheadDelegateFieldName, overheadDelegateType, FieldAttributes.Private); workloadDelegateField = @@ -586,155 +588,17 @@ private MethodBuilder EmitOverheadImplementation(string methodName) return methodBuilder; } - private MethodInfo EmitWorkloadImplementation(string methodName) - { - // Shortcut: DO NOT emit method if the result type is not awaitable - if (!consumableInfo.IsAwaitable) - return Descriptor.WorkloadMethod; - - var workloadInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(workloadDelegateType); - - //.method private hidebysig - // instance int32 __Workload(int64 arg0) cil managed - var args = workloadInvokeMethod.GetParameters(); - var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( - methodName, - MethodAttributes.Private, - workloadInvokeMethod.ReturnParameter, - args); - args = methodBuilder.GetEmitParameters(args); - - var ilBuilder = methodBuilder.GetILGenerator(); - - /* - IL_0007: ldarg.0 - IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); - - /* - IL_0026: ldarg.0 - IL_0027: ldloc.0 - IL_0028: ldloc.1 - IL_0029: ldloc.2 - IL_002a: ldloc.3 - IL_002b: call instance class [System.Private.CoreLib]System.Threading.Tasks.Task`1 BenchmarkDotNet.Helpers.Runnable_0::WorkloadMethod(string, string, string, string) - */ - if (!Descriptor.WorkloadMethod.IsStatic) - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.EmitLdargs(args); - ilBuilder.Emit(OpCodes.Call, Descriptor.WorkloadMethod); - - /* - // awaitHelper.GetResult(...); - IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) - */ - - ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod); - - /* - IL_0014: ret - */ - ilBuilder.Emit(OpCodes.Ret); - - return methodBuilder; - } - private MethodBuilder EmitOverheadAction(string methodName, int unrollFactor) { - return EmitActionImpl(methodName, RunnableActionKind.Overhead, unrollFactor); + return consumeEmitter.EmitActionImpl(this, methodName, RunnableActionKind.Overhead, unrollFactor); } private MethodBuilder EmitWorkloadAction(string methodName, int unrollFactor) { - return EmitActionImpl(methodName, RunnableActionKind.Workload, unrollFactor); - } - - private MethodBuilder EmitActionImpl(string methodName, RunnableActionKind actionKind, int unrollFactor) - { - FieldInfo actionDelegateField; - MethodInfo actionInvokeMethod; - switch (actionKind) - { - case RunnableActionKind.Overhead: - actionDelegateField = overheadDelegateField; - actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(overheadDelegateType); - break; - case RunnableActionKind.Workload: - actionDelegateField = workloadDelegateField; - actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(workloadDelegateType); - break; - default: - throw new ArgumentOutOfRangeException(nameof(actionKind), actionKind, null); - } - - // .method private hidebysig - // instance void OverheadActionUnroll(int64 invokeCount) cil managed - var toArg = new EmitParameterInfo(0, InvokeCountParamName, typeof(long)); - var actionMethodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( - methodName, - MethodAttributes.Private, - EmitParameterInfo.CreateReturnVoidParameter(), - toArg); - toArg.SetMember(actionMethodBuilder); - - // Emit impl - var ilBuilder = actionMethodBuilder.GetILGenerator(); - consumeEmitter.BeginEmitAction(actionMethodBuilder, ilBuilder, actionInvokeMethod, actionKind); - - // init locals - var argLocals = EmitDeclareArgLocals(ilBuilder); - consumeEmitter.DeclareActionLocals(ilBuilder); - var indexLocal = ilBuilder.DeclareLocal(typeof(long)); - - // load fields - EmitLoadArgFieldsToLocals(ilBuilder, argLocals); - consumeEmitter.EmitActionBeforeLoop(ilBuilder); - - // loop - var loopStartLabel = ilBuilder.DefineLabel(); - var loopHeadLabel = ilBuilder.DefineLabel(); - ilBuilder.EmitLoopBeginFromLocToArg(loopStartLabel, loopHeadLabel, indexLocal, toArg); - { - /* - // overheadDelegate(); - IL_0005: ldarg.0 - IL_0006: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate - IL_000b: callvirt instance void BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke() - // -or- - // consumer.Consume(overheadDelegate(_argField)); - IL_000c: ldarg.0 - IL_000d: ldfld class [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer BenchmarkDotNet.Autogenerated.Runnable_0::consumer - IL_0012: ldarg.0 - IL_0013: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate - IL_0018: ldloc.0 - IL_0019: callvirt instance int32 BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke(int64) - IL_001e: callvirt instance void [BenchmarkDotNet]BenchmarkDotNet.Engines.Consumer::Consume(int32) - */ - for (int u = 0; u < unrollFactor; u++) - { - consumeEmitter.EmitActionBeforeCall(ilBuilder); - - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, actionDelegateField); - ilBuilder.EmitInstanceCallThisValueOnStack(null, actionInvokeMethod, argLocals); - - consumeEmitter.EmitActionAfterCall(ilBuilder); - } - } - ilBuilder.EmitLoopEndFromLocToArg(loopStartLabel, loopHeadLabel, indexLocal, toArg); - - consumeEmitter.EmitActionAfterLoop(ilBuilder); - consumeEmitter.CompleteEmitAction(ilBuilder); - - // IL_003a: ret - ilBuilder.EmitVoidReturn(actionMethodBuilder); - - return actionMethodBuilder; + return consumeEmitter.EmitActionImpl(this, methodName, RunnableActionKind.Workload, unrollFactor); } - private IReadOnlyList EmitDeclareArgLocals(ILGenerator ilBuilder, bool skipFirst = false) + public IReadOnlyList EmitDeclareArgLocals(ILGenerator ilBuilder, bool skipFirst = false) { // NB: c# compiler does not store first arg in locals for static calls /* @@ -762,7 +626,7 @@ .locals init ( return argLocals; } - private void EmitLoadArgFieldsToLocals(ILGenerator ilBuilder, IReadOnlyList argLocals, bool skipFirstArg = false) + public void EmitLoadArgFieldsToLocals(ILGenerator ilBuilder, IReadOnlyList argLocals, bool skipFirstArg = false) { // NB: c# compiler does not store first arg in locals for static calls int localsOffset = argFields.Count > 0 && skipFirstArg ? -1 : 0; @@ -869,16 +733,6 @@ .locals init ( */ EmitLoadArgFieldsToLocals(ilBuilder, argLocals, skipFirstArg); - if (consumableInfo.IsAwaitable) - { - /* - IL_0026: ldarg.0 - IL_0027: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); - } - /* IL_0026: ldarg.0 IL_0027: ldloc.0 @@ -894,15 +748,6 @@ .locals init ( ilBuilder.EmitLdLocals(argLocals); ilBuilder.Emit(OpCodes.Call, workloadMethod); - if (consumableInfo.IsAwaitable) - { - /* - // awaitHelper.GetResult(...); - IL_0036: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) - */ - ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod); - } - /* IL_0018: ret */ @@ -926,7 +771,7 @@ .locals init ( private void EmitSetupCleanupMethods() { // Emit Setup/Cleanup methods - // We emit empty method instead of EmptyAction = "() => { }" + // We emit simple method instead of simple Action = "() => new System.Threading.Tasks.ValueTask()" globalSetupMethod = EmitWrapperMethod(GlobalSetupMethodName, Descriptor.GlobalSetupMethod, globalSetupReturnInfo); globalCleanupMethod = EmitWrapperMethod(GlobalCleanupMethodName, Descriptor.GlobalCleanupMethod, globalCleanupReturnInfo); iterationSetupMethod = EmitWrapperMethod(IterationSetupMethodName, Descriptor.IterationSetupMethod, iterationSetupReturnInfo); @@ -935,23 +780,37 @@ private void EmitSetupCleanupMethods() private MethodBuilder EmitWrapperMethod(string methodName, MethodInfo optionalTargetMethod, ConsumableTypeInfo returnTypeInfo) { - var methodBuilder = runnableBuilder.DefinePrivateVoidInstanceMethod(methodName); + var methodBuilder = runnableBuilder.DefineNonVirtualInstanceMethod( + methodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnParameter(typeof(ValueTask))); var ilBuilder = methodBuilder.GetILGenerator(); - if (optionalTargetMethod != null) + if (returnTypeInfo?.IsAwaitable == true) { - if (returnTypeInfo?.IsAwaitable == true) - { - EmitAwaitableSetupTeardown(methodBuilder, optionalTargetMethod, ilBuilder, returnTypeInfo); - } - else + EmitAwaitableSetupTeardown(methodBuilder, optionalTargetMethod, ilBuilder, returnTypeInfo); + } + else + { + var valueTaskLocal = ilBuilder.DeclareLocal(typeof(ValueTask)); + + if (optionalTargetMethod != null) { EmitNoArgsMethodCallPopReturn(methodBuilder, optionalTargetMethod, ilBuilder, forceDirectCall: true); } + /* + // return new ValueTask(); + IL_0000: ldloca.s 0 + IL_0002: initobj [System.Private.CoreLib]System.Threading.Tasks.ValueTask + IL_0008: ldloc.0 + */ + ilBuilder.EmitLdloca(valueTaskLocal); + ilBuilder.Emit(OpCodes.Initobj, typeof(ValueTask)); + ilBuilder.EmitLdloc(valueTaskLocal); } - ilBuilder.EmitVoidReturn(methodBuilder); + ilBuilder.Emit(OpCodes.Ret); return methodBuilder; } @@ -965,26 +824,19 @@ private void EmitAwaitableSetupTeardown( if (targetMethod == null) throw new ArgumentNullException(nameof(targetMethod)); - if (returnTypeInfo.WorkloadMethodReturnType == typeof(void)) - { - ilBuilder.Emit(OpCodes.Ldarg_0); - } - /* - IL_0000: ldarg.0 - IL_0001: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField); + // BenchmarkDotNet.Helpers.AwaitHelper.ToValueTaskVoid(workloadDelegate()); /* // call for instance // GlobalSetup(); - IL_0006: ldarg.0 - IL_0007: call instance void [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalSetup() + IL_0000: ldarg.0 + IL_0001: call instance class [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalSetup() + IL_0006: call valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask BenchmarkDotNet.Helpers.AwaitHelper::ToValueTaskVoid(class [System.Private.CoreLib]System.Threading.Tasks.Task) */ /* // call for static // GlobalSetup(); - IL_0006: call string [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalCleanup() + IL_0000: call class [BenchmarkDotNet]BenchmarkDotNet.Samples.SampleBenchmark::GlobalCleanup() + IL_0005: call valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask BenchmarkDotNet.Helpers.AwaitHelper::ToValueTaskVoid(class [System.Private.CoreLib]System.Threading.Tasks.Task) */ if (targetMethod.IsStatic) { @@ -1001,13 +853,35 @@ private void EmitAwaitableSetupTeardown( ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Call, targetMethod); } + ilBuilder.Emit(OpCodes.Call, GetToValueTaskMethod(returnTypeInfo.WorkloadMethodReturnType)); + } - /* - // awaitHelper.GetResult(...); - IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task) - */ - ilBuilder.Emit(OpCodes.Callvirt, returnTypeInfo.GetResultMethod); - ilBuilder.Emit(OpCodes.Pop); + private static MethodInfo GetToValueTaskMethod(Type taskType) + { + var taskYieldType = taskType + .GetMethod(nameof(Task.GetAwaiter), BindingFlagsPublicInstance) + .ReturnType + .GetMethod(nameof(TaskAwaiter.GetResult), BindingFlagsPublicInstance) + .ReturnType; + if (taskYieldType != typeof(void)) + { + Type compareType = taskType.GetTypeInfo().GetGenericTypeDefinition() == typeof(Task<>) + ? typeof(Task<>) + : typeof(ValueTask<>); + return typeof(Helpers.AwaitHelper).GetMethods(BindingFlagsPublicInstance) + .First(m => + { + if (m.Name != nameof(Helpers.AwaitHelper.ToValueTaskVoid)) return false; + Type paramType = m.GetParameters().First().ParameterType; + // We have to compare the types indirectly, == check doesn't work. + return paramType.Assembly == compareType.Assembly && paramType.Namespace == compareType.Namespace && paramType.Name == compareType.Name; + }) + .MakeGenericMethod(new Type[1] { taskYieldType }); + } + else + { + return typeof(Helpers.AwaitHelper).GetMethod(nameof(Helpers.AwaitHelper.ToValueTaskVoid), BindingFlagsPublicStatic, null, new Type[1] { taskType }, null); + } } private void EmitCtorBody() @@ -1018,17 +892,12 @@ private void EmitCtorBody() consumeEmitter.OnEmitCtorBody(ctorMethod, ilBuilder); - ilBuilder.EmitSetFieldToNewInstance(awaitHelperField, typeof(Helpers.AwaitHelper)); ilBuilder.EmitSetDelegateToThisField(globalSetupActionField, globalSetupMethod); ilBuilder.EmitSetDelegateToThisField(globalCleanupActionField, globalCleanupMethod); ilBuilder.EmitSetDelegateToThisField(iterationSetupActionField, iterationSetupMethod); ilBuilder.EmitSetDelegateToThisField(iterationCleanupActionField, iterationCleanupMethod); ilBuilder.EmitSetDelegateToThisField(overheadDelegateField, overheadImplementationMethod); - - if (workloadImplementationMethod == null) - ilBuilder.EmitSetDelegateToThisField(workloadDelegateField, Descriptor.WorkloadMethod); - else - ilBuilder.EmitSetDelegateToThisField(workloadDelegateField, workloadImplementationMethod); + ilBuilder.EmitSetDelegateToThisField(workloadDelegateField, Descriptor.WorkloadMethod); ilBuilder.EmitCtorReturn(ctorMethod); } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs new file mode 100644 index 0000000000..4a757505a2 --- /dev/null +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs @@ -0,0 +1,658 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Helpers.Reflection.Emit; +using Perfolizer.Horology; +using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants; +using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableReflectionHelpers; + +namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation +{ + internal class TaskConsumeEmitter : ConsumeEmitter + { + private MethodInfo overheadKeepAliveWithoutBoxingMethod; + private MethodInfo getResultMethod; + + private LocalBuilder disassemblyDiagnoserLocal; + /* + private readonly BenchmarkDotNet.Helpers.ManualResetValueTaskSource valueTaskSource = new BenchmarkDotNet.Helpers.ManualResetValueTaskSource(); + private System.Int64 repeatsRemaining; + private readonly System.Action continuation; + private Perfolizer.Horology.StartedClock startedClock; + private $AwaiterTypeName$ currentAwaiter; + */ + private FieldBuilder valueTaskSourceField; + private FieldBuilder repeatsRemainingField; + private FieldBuilder continuationField; + private FieldBuilder startedClockField; + private FieldBuilder currentAwaiterField; + + private MethodBuilder overheadActionImplMethod; + private MethodBuilder workloadActionImplMethod; + private MethodBuilder runTaskMethod; + private MethodBuilder continuationMethod; + private MethodBuilder setExceptionMethod; + + public TaskConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) + { + } + + protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder) + { + overheadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods() + .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing) + && !m.GetParameterTypes().First().IsByRef) + .MakeGenericMethod(ConsumableInfo.OverheadMethodReturnType); + + valueTaskSourceField = runnableBuilder.DefineField(ValueTaskSourceFieldName, typeof(Helpers.ManualResetValueTaskSource), FieldAttributes.Private | FieldAttributes.InitOnly); + repeatsRemainingField = runnableBuilder.DefineField(RepeatsRemainingFieldName, typeof(long), FieldAttributes.Private); + continuationField = runnableBuilder.DefineField(ContinuationFieldName, typeof(Action), FieldAttributes.Private | FieldAttributes.InitOnly); + startedClockField = runnableBuilder.DefineField(StartedClockFieldName, typeof(StartedClock), FieldAttributes.Private); + // (Value)TaskAwaiter() + currentAwaiterField = runnableBuilder.DefineField(CurrentAwaiterFieldName, + ConsumableInfo.WorkloadMethodReturnType.GetMethod(nameof(Task.GetAwaiter), BindingFlags.Public | BindingFlags.Instance).ReturnType, + FieldAttributes.Private); + getResultMethod = currentAwaiterField.FieldType.GetMethod(nameof(TaskAwaiter.GetResult), BindingFlagsAllInstance); + } + + protected override void DeclareDisassemblyDiagnoserLocalsOverride(ILGenerator ilBuilder) + { + // optional local if default(T) uses .initobj + disassemblyDiagnoserLocal = ilBuilder.DeclareOptionalLocalForReturnDefault(ConsumableInfo.WorkloadMethodReturnType); + } + + protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder) + { + ilBuilder.EmitReturnDefault(ConsumableInfo.WorkloadMethodReturnType, disassemblyDiagnoserLocal); + } + + protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) + { + var ctor = typeof(Helpers.ManualResetValueTaskSource).GetConstructor(Array.Empty()); + if (ctor == null) + throw new InvalidOperationException($"Cannot get default .ctor for {typeof(Helpers.ManualResetValueTaskSource)}"); + + /* + // valueTaskSourceField = new BenchmarkDotNet.Helpers.ManualResetValueTaskSource(); + IL_0000: ldarg.0 + IL_0001: newobj instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::.ctor() + IL_0006: stfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Newobj, ctor); + ilBuilder.Emit(OpCodes.Stfld, valueTaskSourceField); + // continuation = __Continuation; + ilBuilder.EmitSetDelegateToThisField(continuationField, continuationMethod); + } + + public override MethodBuilder EmitActionImpl(RunnableEmitter runnableEmitter, string methodName, RunnableActionKind actionKind, int unrollFactor) + { + MethodBuilder actionImpl = actionKind switch + { + RunnableActionKind.Overhead => EmitWorkloadActionImpl(runnableEmitter), + RunnableActionKind.Workload => EmitOverheadActionImpl(runnableEmitter), + _ => throw new ArgumentOutOfRangeException(nameof(actionKind), actionKind, null), + }; + + /* + .method private hidebysig + instance valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1 WorkloadActionNoUnroll ( + int64 invokeCount, + class Perfolizer.Horology.IClock clock + ) cil managed + */ + var toArg = new EmitParameterInfo(0, InvokeCountParamName, typeof(long)); + var clockArg = new EmitParameterInfo(1, ClockParamName, typeof(IClock)); + var actionMethodBuilder = runnableEmitter.runnableBuilder.DefineNonVirtualInstanceMethod( + methodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnParameter(typeof(ValueTask)), + toArg, clockArg); + toArg.SetMember(actionMethodBuilder); + clockArg.SetMember(actionMethodBuilder); + + var ilBuilder = actionMethodBuilder.GetILGenerator(); + + /* + // return WorkloadActionImpl(invokeCount, clock); + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldarg.2 + IL_0003: call instance valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1 BenchmarkRunner_0::WorkloadActionImpl(int64, class Perfolizer.Horology.IClock) + IL_0008: ret + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.EmitLdarg(toArg); + ilBuilder.EmitLdarg(clockArg); + ilBuilder.Emit(OpCodes.Call, actionImpl); + ilBuilder.Emit(OpCodes.Ret); + + return actionMethodBuilder; + } + + private MethodBuilder EmitOverheadActionImpl(RunnableEmitter runnableEmitter) + { + if (overheadActionImplMethod != null) + { + return overheadActionImplMethod; + } + + FieldInfo actionDelegateField = runnableEmitter.overheadDelegateField; + MethodInfo actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(runnableEmitter.overheadDelegateType); + + /* + .method private hidebysig + instance valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1 OverheadActionImpl ( + int64 invokeCount, + class Perfolizer.Horology.IClock clock + ) cil managed + */ + var toArg = new EmitParameterInfo(0, InvokeCountParamName, typeof(long)); + var clockArg = new EmitParameterInfo(1, ClockParamName, typeof(IClock)); + var actionMethodBuilder = runnableEmitter.runnableBuilder.DefineNonVirtualInstanceMethod( + OverheadActionImplMethodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnParameter(typeof(ValueTask)), + toArg, clockArg); + toArg.SetMember(actionMethodBuilder); + clockArg.SetMember(actionMethodBuilder); + + var ilBuilder = actionMethodBuilder.GetILGenerator(); + + // init locals + var argLocals = runnableEmitter.EmitDeclareArgLocals(ilBuilder); + var valueLocal = ilBuilder.DeclareLocal(ConsumableInfo.OverheadMethodReturnType); + var indexLocal = ilBuilder.DeclareLocal(typeof(long)); + + /* + // repeatsRemaining = invokeCount; + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld int64 BenchmarkRunner_0::repeatsRemaining + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.EmitLdarg(toArg); + ilBuilder.Emit(OpCodes.Stfld, repeatsRemainingField); + /* + // Task value = default; + IL_0007: ldnull + IL_0008: stloc.0 + */ + ilBuilder.EmitSetLocalToDefault(valueLocal); + /* + // startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); + IL_0009: ldarg.0 + IL_000a: ldarg.2 + IL_000b: call valuetype Perfolizer.Horology.StartedClock Perfolizer.Horology.ClockExtensions::Start(class Perfolizer.Horology.IClock) + IL_0010: stfld valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.EmitLdarg(clockArg); + ilBuilder.Emit(OpCodes.Call, runnableEmitter.startClockMethod); + ilBuilder.Emit(OpCodes.Stfld, startedClockField); + + var exitExceptionBlockLabel = ilBuilder.DefineLabel(); + + // try { ... } + ilBuilder.BeginExceptionBlock(); + { + // load fields + runnableEmitter.EmitLoadArgFieldsToLocals(ilBuilder, argLocals); + + // while (--repeatsRemaining >= 0) { ... } + var loopStartLabel = ilBuilder.DefineLabel(); + var loopHeadLabel = ilBuilder.DefineLabel(); + ilBuilder.EmitLoopBeginFromFldTo0(loopStartLabel, loopHeadLabel); + { + /* + // value = overheadDelegate(); + IL_0017: ldarg.0 + IL_0018: ldfld class BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate BenchmarkDotNet.Autogenerated.Runnable_0::overheadDelegate + IL_001d: callvirt instance void BenchmarkDotNet.Autogenerated.Runnable_0/OverheadDelegate::Invoke() + IL_0022: stloc.0 + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, actionDelegateField); + ilBuilder.EmitInstanceCallThisValueOnStack(null, actionInvokeMethod, argLocals); + ilBuilder.Emit(OpCodes.Callvirt, actionInvokeMethod); + } + ilBuilder.EmitLoopEndFromFldTo0(loopStartLabel, loopHeadLabel, repeatsRemainingField, indexLocal); + + ilBuilder.Emit(OpCodes.Leave_S, exitExceptionBlockLabel); + } + // catch (System.Exception) { ... } + ilBuilder.BeginCatchBlock(typeof(Exception)); + { + // IL_003b: pop + ilBuilder.Emit(OpCodes.Pop); + /* + // BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); + IL_003c: ldloc.0 + IL_003d: call void BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing>(!!0) + */ + ilBuilder.EmitStaticCall(overheadKeepAliveWithoutBoxingMethod, valueLocal); + // IL_0042: rethrow + ilBuilder.Emit(OpCodes.Rethrow); + } + ilBuilder.EndExceptionBlock(); + ilBuilder.MarkLabel(exitExceptionBlockLabel); + + /* + // return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); + IL_0044: ldarg.0 + IL_0045: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_004a: call instance valuetype Perfolizer.Horology.ClockSpan Perfolizer.Horology.StartedClock::GetElapsed() + IL_004f: newobj instance void valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1::.ctor(!0) + IL_0054: ret + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, startedClockField); + ilBuilder.Emit(OpCodes.Call, runnableEmitter.getElapsedMethod); + var ctor = typeof(ValueTask).GetConstructor(new[] { typeof(ClockSpan) }); + ilBuilder.Emit(OpCodes.Newobj, ctor); + ilBuilder.Emit(OpCodes.Ret); + + return overheadActionImplMethod = actionMethodBuilder; + } + + private MethodBuilder EmitWorkloadActionImpl(RunnableEmitter runnableEmitter) + { + if (workloadActionImplMethod != null) + { + return workloadActionImplMethod; + } + + setExceptionMethod = EmitSetExceptionImpl(runnableEmitter); + runTaskMethod = EmitRunTaskImpl(runnableEmitter); + continuationMethod = EmitContinuationImpl(runnableEmitter); + + /* + .method private hidebysig + instance valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1 WorkloadActionImpl ( + int64 invokeCount, + class Perfolizer.Horology.IClock clock + ) cil managed + */ + var toArg = new EmitParameterInfo(0, InvokeCountParamName, typeof(long)); + var clockArg = new EmitParameterInfo(1, ClockParamName, typeof(IClock)); + var actionMethodBuilder = runnableEmitter.runnableBuilder.DefineNonVirtualInstanceMethod( + WorkloadActionImplMethodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnParameter(typeof(ValueTask)), + toArg, clockArg); + toArg.SetMember(actionMethodBuilder); + clockArg.SetMember(actionMethodBuilder); + + var ilBuilder = actionMethodBuilder.GetILGenerator(); + + /* + // repeatsRemaining = invokeCount; + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld int64 BenchmarkRunner_0::repeatsRemaining + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.EmitLdarg(toArg); + ilBuilder.Emit(OpCodes.Stfld, repeatsRemainingField); + /* + // valueTaskSource.Reset(); + IL_0007: ldarg.0 + IL_0008: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_000d: callvirt instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::Reset() + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); + var resetMethod = valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.ManualResetValueTaskSource.Reset), BindingFlagsPublicInstance); + ilBuilder.Emit(OpCodes.Callvirt, resetMethod); + /* + // startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); + IL_0012: ldarg.0 + IL_0013: ldarg.2 + IL_0014: call valuetype Perfolizer.Horology.StartedClock Perfolizer.Horology.ClockExtensions::Start(class Perfolizer.Horology.IClock) + IL_0019: stfld valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.EmitLdarg(clockArg); + ilBuilder.Emit(OpCodes.Call, runnableEmitter.startClockMethod); + ilBuilder.Emit(OpCodes.Stfld, startedClockField); + /* + // __RunTask(); + IL_001e: ldarg.0 + IL_001f: call instance void BenchmarkRunner_0::__RunTask() + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Call, runTaskMethod); + /* + // return new System.Threading.Tasks.ValueTask(valueTaskSource, valueTaskSource.Version); + IL_0024: ldarg.0 + IL_0025: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_002a: ldarg.0 + IL_002b: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0030: callvirt instance int16 class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::get_Version() + IL_0035: newobj instance void valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1::.ctor(class [System.Private.CoreLib]System.Threading.Tasks.Sources.IValueTaskSource`1, int16) + IL_003a: ret + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); + var getVersionMethod = valueTaskSourceField.FieldType.GetProperty(nameof(Helpers.ManualResetValueTaskSource.Version), BindingFlagsPublicInstance).GetGetMethod(true); + ilBuilder.Emit(OpCodes.Callvirt, getVersionMethod); + var ctor = actionMethodBuilder.ReturnType.GetConstructor(new[] { valueTaskSourceField.FieldType, getVersionMethod.ReturnType }); + ilBuilder.Emit(OpCodes.Newobj, ctor); + ilBuilder.Emit(OpCodes.Ret); + + return workloadActionImplMethod = actionMethodBuilder; + } + + private MethodBuilder EmitRunTaskImpl(RunnableEmitter runnableEmitter) + { + /* + .method private hidebysig + instance void __RunTask () cil managed + */ + var actionMethodBuilder = runnableEmitter.runnableBuilder.DefineNonVirtualInstanceMethod( + RunTaskMethodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnVoidParameter()); + + var ilBuilder = actionMethodBuilder.GetILGenerator(); + + FieldInfo actionDelegateField = runnableEmitter.workloadDelegateField; + MethodInfo actionInvokeMethod = TypeBuilderExtensions.GetDelegateInvokeMethod(runnableEmitter.workloadDelegateType); + + // init locals + //.locals init ( + // [0] valuetype Perfolizer.Horology.ClockSpan clockspan, + // // [1] valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1, // If ValueTask + // [1] int64, + // [2] class [System.Private.CoreLib]System.Exception e + //) + var argLocals = runnableEmitter.EmitDeclareArgLocals(ilBuilder); + var clockspanLocal = ilBuilder.DeclareLocal(typeof(ClockSpan)); + LocalBuilder maybeValueTaskLocal = actionInvokeMethod.ReturnType.IsValueType + ? ilBuilder.DeclareLocal(actionInvokeMethod.ReturnType) + : null; + var indexLocal = ilBuilder.DeclareLocal(typeof(long)); + var exceptionLocal = ilBuilder.DeclareLocal(typeof(Exception)); + + var exitExceptionBlockLabel = ilBuilder.DefineLabel(); + var returnLabel = ilBuilder.DefineLabel(); + + // try { ... } + ilBuilder.BeginExceptionBlock(); + { + // load fields + runnableEmitter.EmitLoadArgFieldsToLocals(ilBuilder, argLocals); + + // while (--repeatsRemaining >= 0) { ... } + var loopStartLabel = ilBuilder.DefineLabel(); + var loopHeadLabel = ilBuilder.DefineLabel(); + ilBuilder.EmitLoopBeginFromFldTo0(loopStartLabel, loopHeadLabel); + { + /* + // currentAwaiter = workloadDelegate().GetAwaiter(); + IL_0002: ldarg.0 + IL_0003: ldarg.0 + IL_0004: ldfld class [System.Private.CoreLib]System.Func`1> BenchmarkRunner_0::workloadDelegate + IL_0009: callvirt instance !0 class [System.Private.CoreLib]System.Func`1>::Invoke() + IL_000e: stloc.1 + IL_000f: ldloca.s 1 + IL_0011: call instance valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1::GetAwaiter() + IL_0016: stfld valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, actionDelegateField); + ilBuilder.EmitInstanceCallThisValueOnStack(null, actionInvokeMethod, argLocals); + ilBuilder.EmitInstanceCallThisValueOnStack(maybeValueTaskLocal, ConsumableInfo.WorkloadMethodReturnType.GetMethod(nameof(Task.GetAwaiter), BindingFlagsAllInstance)); + ilBuilder.Emit(OpCodes.Stfld, currentAwaiterField); + /* + // if (!currentAwaiter.IsCompleted) + IL_001b: ldarg.0 + IL_001c: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0021: call instance bool valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1::get_IsCompleted() + IL_0026: brtrue.s IL_003b + */ + var isCompletedLabel = ilBuilder.DefineLabel(); + + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, currentAwaiterField); + ilBuilder.Emit(OpCodes.Call, currentAwaiterField.FieldType.GetProperty(nameof(TaskAwaiter.IsCompleted), BindingFlagsAllInstance).GetGetMethod(true)); + ilBuilder.Emit(OpCodes.Brtrue_S, isCompletedLabel); + { + /* + // currentAwaiter.UnsafeOnCompleted(continuation); + IL_0028: ldarg.0 + IL_0029: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_002e: ldarg.0 + IL_002f: ldfld class [System.Private.CoreLib]System.Action BenchmarkRunner_0::continuation + IL_0034: call instance void valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1::UnsafeOnCompleted(class [System.Private.CoreLib]System.Action) + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, currentAwaiterField); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, continuationField); + ilBuilder.Emit(OpCodes.Call, currentAwaiterField.FieldType.GetMethod(nameof(TaskAwaiter.UnsafeOnCompleted), BindingFlagsAllInstance)); + // return; + ilBuilder.Emit(OpCodes.Leave_S, returnLabel); + } + ilBuilder.MarkLabel(isCompletedLabel); + /* + // currentAwaiter.GetResult(); + IL_003b: ldarg.0 + IL_003c: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0041: call instance !0 valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1::GetResult() + IL_0046: pop + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, currentAwaiterField); + ilBuilder.Emit(OpCodes.Call, getResultMethod); + if (getResultMethod.ReturnType != typeof(void)) + { + ilBuilder.Emit(OpCodes.Pop); + } + } + ilBuilder.EmitLoopEndFromFldTo0(loopStartLabel, loopHeadLabel, repeatsRemainingField, indexLocal); + + ilBuilder.Emit(OpCodes.Leave_S, exitExceptionBlockLabel); + } + // catch (System.Exception) { ... } + ilBuilder.BeginCatchBlock(typeof(Exception)); + { + /* + // __SetException(e); + IL_005f: stloc.3 + IL_0060: ldarg.0 + IL_0061: ldloc.3 + IL_0062: call instance void BenchmarkRunner_0::__SetException(class [System.Private.CoreLib]System.Exception) + */ + ilBuilder.EmitStloc(exceptionLocal); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.EmitLdloc(exceptionLocal); + ilBuilder.Emit(OpCodes.Call, setExceptionMethod); + // return; + ilBuilder.Emit(OpCodes.Leave_S, returnLabel); + } + ilBuilder.EndExceptionBlock(); + ilBuilder.MarkLabel(exitExceptionBlockLabel); + + /* + // var clockspan = startedClock.GetElapsed(); + IL_0069: ldarg.0 + IL_006a: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_006f: call instance valuetype Perfolizer.Horology.ClockSpan Perfolizer.Horology.StartedClock::GetElapsed() + IL_0074: stloc.0 + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, startedClockField); + ilBuilder.Emit(OpCodes.Call, runnableEmitter.getElapsedMethod); + ilBuilder.EmitStloc(clockspanLocal); + /* + // currentAwaiter = default; + IL_0075: ldarg.0 + IL_0076: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_007b: initobj valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, currentAwaiterField); + ilBuilder.Emit(OpCodes.Initobj, currentAwaiterField.FieldType); + /* + // startedClock = default(Perfolizer.Horology.StartedClock); + IL_0081: ldarg.0 + IL_0082: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_0087: initobj Perfolizer.Horology.StartedClock + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, startedClockField); + ilBuilder.Emit(OpCodes.Initobj, startedClockField.FieldType); + /* + // valueTaskSource.SetResult(clockspan); + IL_008d: ldarg.0 + IL_008e: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0093: ldloc.0 + IL_0094: callvirt instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::SetResult(!0) + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, valueTaskSourceField); + ilBuilder.EmitLdloc(clockspanLocal); + ilBuilder.Emit(OpCodes.Callvirt, valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.ManualResetValueTaskSource.SetResult), BindingFlagsPublicInstance)); + + ilBuilder.MarkLabel(returnLabel); + ilBuilder.Emit(OpCodes.Ret); + + return actionMethodBuilder; + } + + private MethodBuilder EmitContinuationImpl(RunnableEmitter runnableEmitter) + { + /* + .method private hidebysig + instance void __Continuation () cil managed + */ + var actionMethodBuilder = runnableEmitter.runnableBuilder.DefineNonVirtualInstanceMethod( + ContinuationMethodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnVoidParameter()); + + var ilBuilder = actionMethodBuilder.GetILGenerator(); + + // init locals + var exceptionLocal = ilBuilder.DeclareLocal(typeof(Exception)); + + var exitExceptionBlockLabel = ilBuilder.DefineLabel(); + var returnLabel = ilBuilder.DefineLabel(); + + // try { ... } + ilBuilder.BeginExceptionBlock(); + { + /* + // currentAwaiter.GetResult(); + IL_0000: ldarg.0 + IL_0001: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0006: call instance !0 valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1::GetResult() + IL_000b: pop + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, currentAwaiterField); + ilBuilder.Emit(OpCodes.Call, getResultMethod); + if (getResultMethod.ReturnType != typeof(void)) + { + ilBuilder.Emit(OpCodes.Pop); + } + + ilBuilder.Emit(OpCodes.Leave_S, exitExceptionBlockLabel); + } + // catch (System.Exception e) { ... } + ilBuilder.BeginCatchBlock(typeof(Exception)); + { + // IL_000e: stloc.0 + ilBuilder.EmitStloc(exceptionLocal); + /* + // __SetException(e); + IL_000f: ldarg.0 + IL_0010: ldloc.0 + IL_0011: call instance void BenchmarkRunner_0::__SetException(class [System.Private.CoreLib]System.Exception) + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.EmitLdloc(exceptionLocal); + ilBuilder.Emit(OpCodes.Call, setExceptionMethod); + // return; + ilBuilder.Emit(OpCodes.Leave_S, returnLabel); + } + ilBuilder.EndExceptionBlock(); + ilBuilder.MarkLabel(exitExceptionBlockLabel); + + /* + // __RunTask(); + IL_0018: ldarg.0 + IL_0019: call instance void BenchmarkRunner_0::__RunTask() + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Call, runTaskMethod); + + // return; + ilBuilder.MarkLabel(returnLabel); + ilBuilder.Emit(OpCodes.Ret); + + return actionMethodBuilder; + } + + private MethodBuilder EmitSetExceptionImpl(RunnableEmitter runnableEmitter) + { + /* + .method private hidebysig + instance void __SetException ( + class [System.Private.CoreLib]System.Exception e + ) cil managed + */ + var exceptionArg = new EmitParameterInfo(0, "e", typeof(Exception)); + var actionMethodBuilder = runnableEmitter.runnableBuilder.DefineNonVirtualInstanceMethod( + SetExceptionMethodName, + MethodAttributes.Private, + EmitParameterInfo.CreateReturnVoidParameter(), + exceptionArg); + exceptionArg.SetMember(actionMethodBuilder); + + var ilBuilder = actionMethodBuilder.GetILGenerator(); + /* + // currentAwaiter = default; + IL_0000: ldarg.0 + IL_0001: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0006: initobj valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, currentAwaiterField); + ilBuilder.Emit(OpCodes.Initobj, currentAwaiterField.FieldType); + /* + // startedClock = default(Perfolizer.Horology.StartedClock); + IL_000c: ldarg.0 + IL_000d: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_0012: initobj Perfolizer.Horology.StartedClock + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldflda, startedClockField); + ilBuilder.Emit(OpCodes.Initobj, startedClockField.FieldType); + /* + // valueTaskSource.SetException(e); + IL_0018: ldarg.0 + IL_0019: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_001e: ldarg.1 + IL_001f: callvirt instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::SetException(class [System.Private.CoreLib]System.Exception) + IL_0024: ret + */ + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); + ilBuilder.EmitLdarg(exceptionArg); + var setExceptionMethod = valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.ManualResetValueTaskSource.SetException), BindingFlagsPublicInstance); + ilBuilder.Emit(OpCodes.Callvirt, setExceptionMethod); + ilBuilder.Emit(OpCodes.Ret); + + return actionMethodBuilder; + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs index 920fabb03e..00bda018d9 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs @@ -16,7 +16,6 @@ public class RunnableConstants public const string ArgFieldPrefix = "__argField"; public const string ArgParamPrefix = "arg"; - public const string AwaitHelperFieldName = "awaitHelper"; public const string GlobalSetupActionFieldName = "globalSetupAction"; public const string GlobalCleanupActionFieldName = "globalCleanupAction"; public const string IterationSetupActionFieldName = "iterationSetupAction"; @@ -38,12 +37,24 @@ public class RunnableConstants public const string WorkloadActionNoUnrollMethodName = "WorkloadActionNoUnroll"; public const string ForDisassemblyDiagnoserMethodName = "__ForDisassemblyDiagnoser__"; public const string InvokeCountParamName = "invokeCount"; + public const string ClockParamName = "clock"; public const string ConsumerFieldName = "consumer"; public const string NonGenericKeepAliveWithoutBoxingMethodName = "NonGenericKeepAliveWithoutBoxing"; public const string DummyParamName = "_"; public const string WorkloadDefaultValueHolderFieldName = "workloadDefaultValueHolder"; + public const string ValueTaskSourceFieldName = "valueTaskSource"; + public const string RepeatsRemainingFieldName = "repeatsRemaining"; + public const string ContinuationFieldName = "continuation"; + public const string StartedClockFieldName = "startedClock"; + public const string CurrentAwaiterFieldName = "currentAwaiter"; + public const string OverheadActionImplMethodName = "OverheadActionImpl"; + public const string WorkloadActionImplMethodName = "WorkloadActionImpl"; + public const string RunTaskMethodName = "__RunTask"; + public const string ContinuationMethodName = "__Continuation"; + public const string SetExceptionMethodName = "__SetException"; + public const string GlobalSetupMethodName = "GlobalSetup"; public const string GlobalCleanupMethodName = "GlobalCleanup"; public const string IterationSetupMethodName = "IterationSetup"; diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs index 7273b11319..66d0e6cedc 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs @@ -141,16 +141,18 @@ private ValueTask InvokeSingleHardcodedOverhead() private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; + Task value = default; startedClock = clock?.Start() ?? default; try { while (--repeatsRemaining >= 0) { - callback(); + value = callback(); } } catch (Exception) { + Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); throw; } return new ValueTask(startedClock.GetElapsed()); @@ -258,16 +260,18 @@ private ValueTask InvokeSingleHardcodedOverhead() private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; + Task value = default; startedClock = clock?.Start() ?? default; try { while (--repeatsRemaining >= 0) { - callback(); + value = callback(); } } catch (Exception) { + Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); throw; } return new ValueTask(startedClock.GetElapsed()); @@ -376,16 +380,18 @@ private ValueTask InvokeSingleHardcodedOverhead() private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; + ValueTask value = default; startedClock = clock?.Start() ?? default; try { while (--repeatsRemaining >= 0) { - callback(); + value = callback(); } } catch (Exception) { + Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); throw; } return new ValueTask(startedClock.GetElapsed()); @@ -493,16 +499,18 @@ private ValueTask InvokeSingleHardcodedOverhead() private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; + ValueTask value = default; startedClock = clock?.Start() ?? default; try { while (--repeatsRemaining >= 0) { - callback(); + value = callback(); } } catch (Exception) { + Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); throw; } return new ValueTask(startedClock.GetElapsed()); diff --git a/tests/BenchmarkDotNet.Tests/Mocks/MockEngine.cs b/tests/BenchmarkDotNet.Tests/Mocks/MockEngine.cs index 6b1fad64a6..5b784a2691 100644 --- a/tests/BenchmarkDotNet.Tests/Mocks/MockEngine.cs +++ b/tests/BenchmarkDotNet.Tests/Mocks/MockEngine.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using BenchmarkDotNet.Characteristics; using BenchmarkDotNet.Engines; using BenchmarkDotNet.Jobs; @@ -32,16 +33,16 @@ public MockEngine(ITestOutputHelper output, Job job, Func GlobalSetupAction { get; set; } [UsedImplicitly] - public Action GlobalCleanupAction { get; set; } + public Func GlobalCleanupAction { get; set; } [UsedImplicitly] public bool IsDiagnoserAttached { get; set; } - public Action WorkloadAction { get; } = _ => { }; - public Action OverheadAction { get; } = _ => { }; + public Func> WorkloadAction { get; } = (invokeCount, clock) => default; + public Func> OverheadAction { get; } = (invokeCount, clock) => default; [UsedImplicitly] public IEngineFactory Factory => null; From c4030a7cee19be88dab9c6c5deb7ba06a4eb89bd Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 26 Mar 2022 20:30:47 -0400 Subject: [PATCH 15/22] Fixed crash --- .../ConsumableTypeInfo.cs | 6 ++++- .../Emitters/TaskConsumeEmitter.cs | 25 +++++-------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs index 709ce82d31..62cc39bc3b 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs @@ -29,7 +29,11 @@ public ConsumableTypeInfo(Type methodReturnType) throw new InvalidOperationException("Bug: (WorkloadMethodReturnType == null"); var consumableField = default(FieldInfo); - if (WorkloadMethodReturnType == typeof(void)) + if (IsAwaitable) + { + OverheadMethodReturnType = WorkloadMethodReturnType; + } + else if (WorkloadMethodReturnType == typeof(void)) { IsVoid = true; OverheadMethodReturnType = WorkloadMethodReturnType; diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs index 4a757505a2..12162afc76 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs @@ -93,8 +93,8 @@ public override MethodBuilder EmitActionImpl(RunnableEmitter runnableEmitter, st { MethodBuilder actionImpl = actionKind switch { - RunnableActionKind.Overhead => EmitWorkloadActionImpl(runnableEmitter), - RunnableActionKind.Workload => EmitOverheadActionImpl(runnableEmitter), + RunnableActionKind.Overhead => EmitOverheadActionImpl(runnableEmitter), + RunnableActionKind.Workload => EmitWorkloadActionImpl(runnableEmitter), _ => throw new ArgumentOutOfRangeException(nameof(actionKind), actionKind, null), }; @@ -195,8 +195,6 @@ class Perfolizer.Horology.IClock clock ilBuilder.Emit(OpCodes.Call, runnableEmitter.startClockMethod); ilBuilder.Emit(OpCodes.Stfld, startedClockField); - var exitExceptionBlockLabel = ilBuilder.DefineLabel(); - // try { ... } ilBuilder.BeginExceptionBlock(); { @@ -217,12 +215,10 @@ class Perfolizer.Horology.IClock clock */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldfld, actionDelegateField); - ilBuilder.EmitInstanceCallThisValueOnStack(null, actionInvokeMethod, argLocals); ilBuilder.Emit(OpCodes.Callvirt, actionInvokeMethod); + ilBuilder.EmitStloc(valueLocal); } ilBuilder.EmitLoopEndFromFldTo0(loopStartLabel, loopHeadLabel, repeatsRemainingField, indexLocal); - - ilBuilder.Emit(OpCodes.Leave_S, exitExceptionBlockLabel); } // catch (System.Exception) { ... } ilBuilder.BeginCatchBlock(typeof(Exception)); @@ -239,7 +235,6 @@ class Perfolizer.Horology.IClock clock ilBuilder.Emit(OpCodes.Rethrow); } ilBuilder.EndExceptionBlock(); - ilBuilder.MarkLabel(exitExceptionBlockLabel); /* // return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); @@ -380,7 +375,6 @@ instance void __RunTask () cil managed var indexLocal = ilBuilder.DeclareLocal(typeof(long)); var exceptionLocal = ilBuilder.DeclareLocal(typeof(Exception)); - var exitExceptionBlockLabel = ilBuilder.DefineLabel(); var returnLabel = ilBuilder.DefineLabel(); // try { ... } @@ -450,7 +444,7 @@ instance void __RunTask () cil managed IL_0046: pop */ ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, currentAwaiterField); + ilBuilder.Emit(OpCodes.Ldflda, currentAwaiterField); ilBuilder.Emit(OpCodes.Call, getResultMethod); if (getResultMethod.ReturnType != typeof(void)) { @@ -458,8 +452,6 @@ instance void __RunTask () cil managed } } ilBuilder.EmitLoopEndFromFldTo0(loopStartLabel, loopHeadLabel, repeatsRemainingField, indexLocal); - - ilBuilder.Emit(OpCodes.Leave_S, exitExceptionBlockLabel); } // catch (System.Exception) { ... } ilBuilder.BeginCatchBlock(typeof(Exception)); @@ -479,7 +471,6 @@ instance void __RunTask () cil managed ilBuilder.Emit(OpCodes.Leave_S, returnLabel); } ilBuilder.EndExceptionBlock(); - ilBuilder.MarkLabel(exitExceptionBlockLabel); /* // var clockspan = startedClock.GetElapsed(); @@ -518,7 +509,7 @@ instance void __RunTask () cil managed IL_0094: callvirt instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::SetResult(!0) */ ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldflda, valueTaskSourceField); + ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); ilBuilder.EmitLdloc(clockspanLocal); ilBuilder.Emit(OpCodes.Callvirt, valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.ManualResetValueTaskSource.SetResult), BindingFlagsPublicInstance)); @@ -544,7 +535,6 @@ instance void __Continuation () cil managed // init locals var exceptionLocal = ilBuilder.DeclareLocal(typeof(Exception)); - var exitExceptionBlockLabel = ilBuilder.DefineLabel(); var returnLabel = ilBuilder.DefineLabel(); // try { ... } @@ -558,14 +548,12 @@ instance void __Continuation () cil managed IL_000b: pop */ ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, currentAwaiterField); + ilBuilder.Emit(OpCodes.Ldflda, currentAwaiterField); ilBuilder.Emit(OpCodes.Call, getResultMethod); if (getResultMethod.ReturnType != typeof(void)) { ilBuilder.Emit(OpCodes.Pop); } - - ilBuilder.Emit(OpCodes.Leave_S, exitExceptionBlockLabel); } // catch (System.Exception e) { ... } ilBuilder.BeginCatchBlock(typeof(Exception)); @@ -585,7 +573,6 @@ instance void __Continuation () cil managed ilBuilder.Emit(OpCodes.Leave_S, returnLabel); } ilBuilder.EndExceptionBlock(); - ilBuilder.MarkLabel(exitExceptionBlockLabel); /* // __RunTask(); From ddf5d0c290f283f2846634e255ed73ba3bdfc734 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 26 Mar 2022 21:18:05 -0400 Subject: [PATCH 16/22] Fixed InProcess (no emit) async tests. --- .../BenchmarkActionFactory_Implementations.cs | 36 +++++++++---------- .../BenchmarkActionFactory_Implementations.cs | 36 +++++++++---------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs index 66d0e6cedc..2430bc3489 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs @@ -134,7 +134,7 @@ public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) private ValueTask InvokeSingleHardcodedOverhead() { - InvokeNoUnrollHardcodedOverhead(1, null); + callback(); return new ValueTask(); } @@ -142,7 +142,7 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I { repeatsRemaining = repeatCount; Task value = default; - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); try { while (--repeatsRemaining >= 0) @@ -160,15 +160,14 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I private ValueTask InvokeSingleHardcoded() { - InvokeNoUnrollHardcoded(1, null); - return new ValueTask(valueTaskSource, valueTaskSource.Version); + return AwaitHelper.ToValueTaskVoid(callback()); } private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; valueTaskSource.Reset(); - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); } @@ -253,7 +252,7 @@ public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor) private ValueTask InvokeSingleHardcodedOverhead() { - InvokeNoUnrollHardcodedOverhead(1, null); + callback(); return new ValueTask(); } @@ -261,7 +260,7 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I { repeatsRemaining = repeatCount; Task value = default; - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); try { while (--repeatsRemaining >= 0) @@ -279,15 +278,14 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I private ValueTask InvokeSingleHardcoded() { - InvokeNoUnrollHardcoded(1, null); - return new ValueTask(valueTaskSource, valueTaskSource.Version); + return AwaitHelper.ToValueTaskVoid(callback()); } private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; valueTaskSource.Reset(); - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); } @@ -373,7 +371,7 @@ public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFa private ValueTask InvokeSingleHardcodedOverhead() { - InvokeNoUnrollHardcodedOverhead(1, null); + callback(); return new ValueTask(); } @@ -381,7 +379,7 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I { repeatsRemaining = repeatCount; ValueTask value = default; - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); try { while (--repeatsRemaining >= 0) @@ -399,15 +397,14 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I private ValueTask InvokeSingleHardcoded() { - InvokeNoUnrollHardcoded(1, null); - return new ValueTask(valueTaskSource, valueTaskSource.Version); + return AwaitHelper.ToValueTaskVoid(callback()); } private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; valueTaskSource.Reset(); - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); } @@ -492,7 +489,7 @@ public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFa private ValueTask InvokeSingleHardcodedOverhead() { - InvokeNoUnrollHardcodedOverhead(1, null); + callback(); return new ValueTask(); } @@ -500,7 +497,7 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I { repeatsRemaining = repeatCount; ValueTask value = default; - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); try { while (--repeatsRemaining >= 0) @@ -518,15 +515,14 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I private ValueTask InvokeSingleHardcoded() { - InvokeNoUnrollHardcoded(1, null); - return new ValueTask(valueTaskSource, valueTaskSource.Version); + return AwaitHelper.ToValueTaskVoid(callback()); } private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; valueTaskSource.Reset(); - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs index cc8e193a28..a794699910 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs @@ -166,14 +166,14 @@ public BenchmarkActionTask(object instance, MethodInfo method, BenchmarkActionCo private ValueTask InvokeSingleHardcodedOverhead() { - InvokeNoUnrollHardcodedOverhead(1, null); + callback(); return new ValueTask(); } private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); try { while (--repeatsRemaining >= 0) @@ -190,15 +190,14 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I private ValueTask InvokeSingleHardcoded() { - InvokeNoUnrollHardcoded(1, null); - return new ValueTask(valueTaskSource, valueTaskSource.Version); + return AwaitHelper.ToValueTaskVoid(callback()); } private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; valueTaskSource.Reset(); - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); } @@ -283,14 +282,14 @@ public BenchmarkActionTask(object instance, MethodInfo method, BenchmarkActionCo private ValueTask InvokeSingleHardcodedOverhead() { - InvokeNoUnrollHardcodedOverhead(1, null); + callback(); return new ValueTask(); } private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); try { while (--repeatsRemaining >= 0) @@ -307,15 +306,14 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I private ValueTask InvokeSingleHardcoded() { - InvokeNoUnrollHardcoded(1, null); - return new ValueTask(valueTaskSource, valueTaskSource.Version); + return AwaitHelper.ToValueTaskVoid(callback()); } private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; valueTaskSource.Reset(); - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); } @@ -401,14 +399,14 @@ public BenchmarkActionValueTask(object instance, MethodInfo method, BenchmarkAct private ValueTask InvokeSingleHardcodedOverhead() { - InvokeNoUnrollHardcodedOverhead(1, null); + callback(); return new ValueTask(); } private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); try { while (--repeatsRemaining >= 0) @@ -425,15 +423,14 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I private ValueTask InvokeSingleHardcoded() { - InvokeNoUnrollHardcoded(1, null); - return new ValueTask(valueTaskSource, valueTaskSource.Version); + return AwaitHelper.ToValueTaskVoid(callback()); } private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; valueTaskSource.Reset(); - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); } @@ -518,14 +515,14 @@ public BenchmarkActionValueTask(object instance, MethodInfo method, BenchmarkAct private ValueTask InvokeSingleHardcodedOverhead() { - InvokeNoUnrollHardcodedOverhead(1, null); + callback(); return new ValueTask(); } private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); try { while (--repeatsRemaining >= 0) @@ -542,15 +539,14 @@ private ValueTask InvokeNoUnrollHardcodedOverhead(long repeatCount, I private ValueTask InvokeSingleHardcoded() { - InvokeNoUnrollHardcoded(1, null); - return new ValueTask(valueTaskSource, valueTaskSource.Version); + return AwaitHelper.ToValueTaskVoid(callback()); } private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; valueTaskSource.Reset(); - startedClock = clock?.Start() ?? default; + startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); } From ec917db26c00a9399b50bf79ec3a1fcb3e59cc74 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 26 Mar 2022 22:52:46 -0400 Subject: [PATCH 17/22] Fixed unroll factor in InProcessNoEmit. Fixed some EmitSameIL tests. --- .../Templates/BenchmarkType.txt | 36 +++++++------------ .../InProcess.NoEmit/InProcessNoEmitRunner.cs | 2 +- .../BenchmarkActionFactory_Implementations.cs | 1 - .../Toolchains/InProcess/InProcessRunner.cs | 2 +- 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index 6a53e55ef5..3a68f75691 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -302,9 +302,8 @@ { result = overheadDelegate($PassArguments$);@Unroll@ } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) @@ -316,9 +315,8 @@ { result = overheadDelegate($PassArguments$); } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(result); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) @@ -330,9 +328,8 @@ { result = workloadDelegate($PassArguments$);@Unroll@ } - var elapsed = startedClock.GetElapsed(); NonGenericKeepAliveWithoutBoxing(result); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) @@ -344,9 +341,8 @@ { result = workloadDelegate($PassArguments$); } - var elapsed = startedClock.GetElapsed(); NonGenericKeepAliveWithoutBoxing(result); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } // we must not simply use DeadCodeEliminationHelper.KeepAliveWithoutBoxing because it's generic method @@ -377,9 +373,8 @@ { value = overheadDelegate($PassArguments$);@Unroll@ } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) @@ -391,9 +386,8 @@ { value = overheadDelegate($PassArguments$); } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); @@ -407,9 +401,8 @@ { alias = workloadDelegate($PassArguments$);@Unroll@ } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) @@ -421,9 +414,8 @@ { alias = workloadDelegate($PassArguments$); } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] @@ -448,9 +440,8 @@ { value = overheadDelegate($PassArguments$);@Unroll@ } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) @@ -462,9 +453,8 @@ { value = overheadDelegate($PassArguments$); } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); @@ -478,9 +468,8 @@ { alias = workloadDelegate($PassArguments$);@Unroll@ } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) @@ -492,9 +481,8 @@ { alias = workloadDelegate($PassArguments$); } - var elapsed = startedClock.GetElapsed(); BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); - return new System.Threading.Tasks.ValueTask(elapsed); + return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/InProcessNoEmitRunner.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/InProcessNoEmitRunner.cs index 54b4af37fc..a6f5e37619 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/InProcessNoEmitRunner.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/InProcessNoEmitRunner.cs @@ -104,9 +104,9 @@ private static class Runnable { public static void RunCore(IHost host, BenchmarkCase benchmarkCase) { + int unrollFactor = BenchmarkActionFactory.GetUnrollFactor(benchmarkCase); var target = benchmarkCase.Descriptor; var job = benchmarkCase.Job; // TODO: filter job (same as SourceCodePresenter does)? - int unrollFactor = BenchmarkActionFactory.GetUnrollFactor(benchmarkCase); // DONTTOUCH: these should be allocated together var instance = Activator.CreateInstance(benchmarkCase.Descriptor.Type); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs index a794699910..2a588eee61 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs @@ -113,7 +113,6 @@ private ValueTask InvokeUnrollHardcoded(long repeatCount, IClock cloc var startedClock = clock.Start(); for (long i = 0; i < repeatCount; i++) result = unrolledCallback(); - unrolledCallback(); return new ValueTask(startedClock.GetElapsed()); } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs b/src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs index 9c97ac840b..2c72dd9aaf 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs @@ -101,9 +101,9 @@ private static class Runnable { public static void RunCore(IHost host, BenchmarkCase benchmarkCase, BenchmarkActionCodegen codegenMode) { + int unrollFactor = BenchmarkActionFactory.GetUnrollFactor(benchmarkCase); var target = benchmarkCase.Descriptor; var job = benchmarkCase.Job; // TODO: filter job (same as SourceCodePresenter does)? - int unrollFactor = BenchmarkActionFactory.GetUnrollFactor(benchmarkCase); // DONTTOUCH: these should be allocated together var instance = Activator.CreateInstance(benchmarkCase.Descriptor.Type); From 9025b27cd9c25582417478389191c043c8148b3d Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 27 Mar 2022 02:21:27 -0400 Subject: [PATCH 18/22] Fixed InProcessEmitTests --- .../IlGeneratorStatementExtensions.cs | 2 +- .../Emitters/TaskConsumeEmitter.cs | 14 +++++----- .../NaiveRunnableEmitDiff.cs | 26 +++++++++++++++---- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs index 118ba517f4..50d4fd86af 100644 --- a/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs +++ b/src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs @@ -194,7 +194,7 @@ public static void EmitLoopEndFromFldTo0( ilBuilder.EmitLdloc(counterLocal); ilBuilder.Emit(OpCodes.Ldc_I4_0); ilBuilder.Emit(OpCodes.Conv_I8); - ilBuilder.Emit(OpCodes.Bge_S, loopStartLabel); + ilBuilder.Emit(OpCodes.Bge, loopStartLabel); } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs index 12162afc76..96eb21a398 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs @@ -164,8 +164,8 @@ class Perfolizer.Horology.IClock clock var ilBuilder = actionMethodBuilder.GetILGenerator(); // init locals - var argLocals = runnableEmitter.EmitDeclareArgLocals(ilBuilder); var valueLocal = ilBuilder.DeclareLocal(ConsumableInfo.OverheadMethodReturnType); + var argLocals = runnableEmitter.EmitDeclareArgLocals(ilBuilder); var indexLocal = ilBuilder.DeclareLocal(typeof(long)); /* @@ -215,7 +215,7 @@ class Perfolizer.Horology.IClock clock */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldfld, actionDelegateField); - ilBuilder.Emit(OpCodes.Callvirt, actionInvokeMethod); + ilBuilder.EmitInstanceCallThisValueOnStack(null, actionInvokeMethod, argLocals); ilBuilder.EmitStloc(valueLocal); } ilBuilder.EmitLoopEndFromFldTo0(loopStartLabel, loopHeadLabel, repeatsRemainingField, indexLocal); @@ -367,8 +367,8 @@ instance void __RunTask () cil managed // [1] int64, // [2] class [System.Private.CoreLib]System.Exception e //) - var argLocals = runnableEmitter.EmitDeclareArgLocals(ilBuilder); var clockspanLocal = ilBuilder.DeclareLocal(typeof(ClockSpan)); + var argLocals = runnableEmitter.EmitDeclareArgLocals(ilBuilder); LocalBuilder maybeValueTaskLocal = actionInvokeMethod.ReturnType.IsValueType ? ilBuilder.DeclareLocal(actionInvokeMethod.ReturnType) : null; @@ -417,7 +417,7 @@ instance void __RunTask () cil managed ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldflda, currentAwaiterField); ilBuilder.Emit(OpCodes.Call, currentAwaiterField.FieldType.GetProperty(nameof(TaskAwaiter.IsCompleted), BindingFlagsAllInstance).GetGetMethod(true)); - ilBuilder.Emit(OpCodes.Brtrue_S, isCompletedLabel); + ilBuilder.Emit(OpCodes.Brtrue, isCompletedLabel); { /* // currentAwaiter.UnsafeOnCompleted(continuation); @@ -433,7 +433,7 @@ instance void __RunTask () cil managed ilBuilder.Emit(OpCodes.Ldfld, continuationField); ilBuilder.Emit(OpCodes.Call, currentAwaiterField.FieldType.GetMethod(nameof(TaskAwaiter.UnsafeOnCompleted), BindingFlagsAllInstance)); // return; - ilBuilder.Emit(OpCodes.Leave_S, returnLabel); + ilBuilder.Emit(OpCodes.Leave, returnLabel); } ilBuilder.MarkLabel(isCompletedLabel); /* @@ -468,7 +468,7 @@ instance void __RunTask () cil managed ilBuilder.EmitLdloc(exceptionLocal); ilBuilder.Emit(OpCodes.Call, setExceptionMethod); // return; - ilBuilder.Emit(OpCodes.Leave_S, returnLabel); + ilBuilder.Emit(OpCodes.Leave, returnLabel); } ilBuilder.EndExceptionBlock(); @@ -570,7 +570,7 @@ instance void __Continuation () cil managed ilBuilder.EmitLdloc(exceptionLocal); ilBuilder.Emit(OpCodes.Call, setExceptionMethod); // return; - ilBuilder.Emit(OpCodes.Leave_S, returnLabel); + ilBuilder.Emit(OpCodes.Leave, returnLabel); } ilBuilder.EndExceptionBlock(); diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs index 65cd9c752e..1cc4f8b033 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcess.EmitTests/NaiveRunnableEmitDiff.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; @@ -31,7 +32,9 @@ public class NaiveRunnableEmitDiff { { OpCodes.Br_S, OpCodes.Br }, { OpCodes.Blt_S, OpCodes.Blt }, - { OpCodes.Bne_Un_S, OpCodes.Bne_Un } + { OpCodes.Bne_Un_S, OpCodes.Bne_Un }, + { OpCodes.Bge_S, OpCodes.Bge }, + { OpCodes.Brtrue_S, OpCodes.Brtrue }, }; public static void RunDiff(string roslynAssemblyPath, string emittedAssemblyPath, ILogger logger) @@ -63,7 +66,15 @@ private static bool AreSameTypeIgnoreNested(TypeReference left, TypeReference ri private static bool AreSameSignature(MethodReference left, MethodReference right) { - return (left.Name == right.Name || (left.Name.StartsWith("<.ctor>") && right.Name == "__Workload")) + var lookup = new HashSet() + { + RunnableConstants.WorkloadImplementationMethodName, + RunnableConstants.GlobalSetupMethodName, + RunnableConstants.GlobalCleanupMethodName, + RunnableConstants.IterationSetupMethodName, + RunnableConstants.IterationCleanupMethodName + }; + return (left.Name == right.Name || (left.Name.StartsWith("<.ctor>") && lookup.Contains(right.Name))) && AreSameTypeIgnoreNested(left.ReturnType, right.ReturnType) && left.Parameters.Count == right.Parameters.Count && left.Parameters @@ -80,7 +91,9 @@ private static List GetOpInstructions(MethodDefinition method) var result = new List(bodyInstructions.Count); foreach (var instruction in bodyInstructions) { - if (compareNops || instruction.OpCode != OpCodes.Nop) + // Skip leave instructions since the IlBuilder forces them differently than Roslyn. + if (instruction.OpCode != OpCodes.Leave && instruction.OpCode != OpCodes.Leave_S + && (compareNops || instruction.OpCode != OpCodes.Nop)) result.Add(instruction); } @@ -289,14 +302,17 @@ private static void DiffMembers(TypeDefinition type1, TypeDefinition type2, ILog } var methods2ByName = type2.Methods.ToLookup(f => f.Name); + var methods2ByComparison = new HashSet(type2.Methods); foreach (var method1 in type1.Methods) { logger.Write($" method {method1.FullName}"); var method2 = methods2ByName[method1.Name].SingleOrDefault(m => AreSameSignature(method1, m)); if (method2 == null) - method2 = type2.Methods.SingleOrDefault(m => AreSameSignature(method1, m)); - if (method2 == null) + method2 = methods2ByComparison.FirstOrDefault(m => AreSameSignature(method1, m)); + if (method2 != null) + methods2ByComparison.Remove(method2); + else method2 = methods2ByName[method1.Name].SingleOrDefault(); if (Diff(method1, method2)) From ac800321dd6dec00bf96bf787791b951902d31a7 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 27 Mar 2022 17:55:36 -0400 Subject: [PATCH 19/22] Changed ManualResetValueTaskSource to AutoResetValueTaskSource. --- ...kSource.cs => AutoResetValueTaskSource.cs} | 59 +++++-------------- .../Templates/BenchmarkType.txt | 3 +- .../Emitters/TaskConsumeEmitter.cs | 44 ++++++-------- .../BenchmarkActionFactory_Implementations.cs | 12 ++-- .../BenchmarkActionFactory_Implementations.cs | 12 ++-- 5 files changed, 42 insertions(+), 88 deletions(-) rename src/BenchmarkDotNet/Helpers/{ManualResetValueTaskSource.cs => AutoResetValueTaskSource.cs} (62%) diff --git a/src/BenchmarkDotNet/Helpers/ManualResetValueTaskSource.cs b/src/BenchmarkDotNet/Helpers/AutoResetValueTaskSource.cs similarity index 62% rename from src/BenchmarkDotNet/Helpers/ManualResetValueTaskSource.cs rename to src/BenchmarkDotNet/Helpers/AutoResetValueTaskSource.cs index a3f00251bc..1a132775f1 100644 --- a/src/BenchmarkDotNet/Helpers/ManualResetValueTaskSource.cs +++ b/src/BenchmarkDotNet/Helpers/AutoResetValueTaskSource.cs @@ -1,40 +1,26 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using JetBrains.Annotations; +using JetBrains.Annotations; using System; using System.Diagnostics; using System.Runtime.ExceptionServices; -using System.Runtime.InteropServices; using System.Threading; +using System.Threading.Tasks; using System.Threading.Tasks.Sources; namespace BenchmarkDotNet.Helpers { - /// Provides the core logic for implementing a manual-reset or . - /// - [StructLayout(LayoutKind.Auto)] - public class ManualResetValueTaskSource : IValueTaskSource, IValueTaskSource + /// + /// Implementation for that will reset itself when awaited so that it can be re-used. + /// + public class AutoResetValueTaskSource : IValueTaskSource, IValueTaskSource { - /// - /// The callback to invoke when the operation completes if was called before the operation completed, - /// or if the operation completed before a callback was supplied, - /// or null if a callback hasn't yet been provided and the operation hasn't yet completed. - /// [CanBeNull] private Action _continuation; - /// State to pass to . [CanBeNull] private object _continuationState; - /// Whether the current operation has completed. private bool _completed; - /// The result with which the operation succeeded, or the default value if it hasn't yet completed or failed. [CanBeNull] private TResult _result; - /// The exception with which the operation failed, or null if it hasn't yet completed or completed successfully. [CanBeNull] private ExceptionDispatchInfo _error; - /// The current version of this value, used to help prevent misuse. private short _version; - /// Resets to prepare for the next operation. - public void Reset() + private void Reset() { // Reset/update state for the next use/await of this instance. _version++; @@ -86,8 +72,11 @@ public TResult GetResult(short token) throw new InvalidOperationException(); } - _error?.Throw(); - return _result; + var result = _result; + var error = _error; + Reset(); + error?.Throw(); + return result; } public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) @@ -116,7 +105,7 @@ public void OnCompleted(Action continuation, object state, short token, if (oldContinuation != null) { // Operation already completed, so we need to call the supplied callback. - if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel)) + if (!ReferenceEquals(oldContinuation, AutoResetValueTaskSourceCoreShared.s_sentinel)) { throw new InvalidOperationException(); } @@ -125,8 +114,6 @@ public void OnCompleted(Action continuation, object state, short token, } } - /// Ensures that the specified token matches the current version. - /// The token supplied by . private void ValidateToken(short token) { if (token != _version) @@ -135,7 +122,6 @@ private void ValidateToken(short token) } } - /// Signals that the operation has completed. Invoked after the result or error has been set. private void SignalCompletion() { if (_completed) @@ -144,21 +130,8 @@ private void SignalCompletion() } _completed = true; - if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null) - { - InvokeContinuation(); - } - } - - /// - /// Invokes the continuation with the appropriate captured context / scheduler. - /// This assumes that if is not null we're already - /// running within that . - /// - private void InvokeContinuation() - { - Debug.Assert(_continuation != null); - _continuation(_continuationState); + Interlocked.CompareExchange(ref _continuation, AutoResetValueTaskSourceCoreShared.s_sentinel, null) + ?.Invoke(_continuationState); } void IValueTaskSource.GetResult(short token) @@ -167,7 +140,7 @@ void IValueTaskSource.GetResult(short token) } } - internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication + internal static class AutoResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication { internal static readonly Action s_sentinel = CompletionSentinel; private static void CompletionSentinel(object _) // named method to aid debugging diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index 3a68f75691..5af34bc14d 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -114,7 +114,7 @@ #if RETURNS_AWAITABLE_$ID$ - private readonly BenchmarkDotNet.Helpers.ManualResetValueTaskSource valueTaskSource = new BenchmarkDotNet.Helpers.ManualResetValueTaskSource(); + private readonly BenchmarkDotNet.Helpers.AutoResetValueTaskSource valueTaskSource = new BenchmarkDotNet.Helpers.AutoResetValueTaskSource(); private System.Int64 repeatsRemaining; private readonly System.Action continuation; private Perfolizer.Horology.StartedClock startedClock; @@ -165,7 +165,6 @@ private System.Threading.Tasks.ValueTask WorkloadActionImpl(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { repeatsRemaining = invokeCount; - valueTaskSource.Reset(); startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); __RunTask(); return new System.Threading.Tasks.ValueTask(valueTaskSource, valueTaskSource.Version); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs index 96eb21a398..1a469d32d7 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs @@ -19,7 +19,7 @@ internal class TaskConsumeEmitter : ConsumeEmitter private LocalBuilder disassemblyDiagnoserLocal; /* - private readonly BenchmarkDotNet.Helpers.ManualResetValueTaskSource valueTaskSource = new BenchmarkDotNet.Helpers.ManualResetValueTaskSource(); + private readonly BenchmarkDotNet.Helpers.AutoResetValueTaskSource valueTaskSource = new BenchmarkDotNet.Helpers.AutoResetValueTaskSource(); private System.Int64 repeatsRemaining; private readonly System.Action continuation; private Perfolizer.Horology.StartedClock startedClock; @@ -48,7 +48,7 @@ protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder) && !m.GetParameterTypes().First().IsByRef) .MakeGenericMethod(ConsumableInfo.OverheadMethodReturnType); - valueTaskSourceField = runnableBuilder.DefineField(ValueTaskSourceFieldName, typeof(Helpers.ManualResetValueTaskSource), FieldAttributes.Private | FieldAttributes.InitOnly); + valueTaskSourceField = runnableBuilder.DefineField(ValueTaskSourceFieldName, typeof(Helpers.AutoResetValueTaskSource), FieldAttributes.Private | FieldAttributes.InitOnly); repeatsRemainingField = runnableBuilder.DefineField(RepeatsRemainingFieldName, typeof(long), FieldAttributes.Private); continuationField = runnableBuilder.DefineField(ContinuationFieldName, typeof(Action), FieldAttributes.Private | FieldAttributes.InitOnly); startedClockField = runnableBuilder.DefineField(StartedClockFieldName, typeof(StartedClock), FieldAttributes.Private); @@ -72,15 +72,15 @@ protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerato protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) { - var ctor = typeof(Helpers.ManualResetValueTaskSource).GetConstructor(Array.Empty()); + var ctor = typeof(Helpers.AutoResetValueTaskSource).GetConstructor(Array.Empty()); if (ctor == null) - throw new InvalidOperationException($"Cannot get default .ctor for {typeof(Helpers.ManualResetValueTaskSource)}"); + throw new InvalidOperationException($"Cannot get default .ctor for {typeof(Helpers.AutoResetValueTaskSource)}"); /* - // valueTaskSourceField = new BenchmarkDotNet.Helpers.ManualResetValueTaskSource(); + // valueTaskSourceField = new BenchmarkDotNet.Helpers.AutoResetValueTaskSource(); IL_0000: ldarg.0 - IL_0001: newobj instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::.ctor() - IL_0006: stfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0001: newobj instance void class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1::.ctor() + IL_0006: stfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Newobj, ctor); @@ -293,16 +293,6 @@ class Perfolizer.Horology.IClock clock ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.EmitLdarg(toArg); ilBuilder.Emit(OpCodes.Stfld, repeatsRemainingField); - /* - // valueTaskSource.Reset(); - IL_0007: ldarg.0 - IL_0008: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource - IL_000d: callvirt instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::Reset() - */ - ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); - var resetMethod = valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.ManualResetValueTaskSource.Reset), BindingFlagsPublicInstance); - ilBuilder.Emit(OpCodes.Callvirt, resetMethod); /* // startedClock = Perfolizer.Horology.ClockExtensions.Start(clock); IL_0012: ldarg.0 @@ -324,10 +314,10 @@ class Perfolizer.Horology.IClock clock /* // return new System.Threading.Tasks.ValueTask(valueTaskSource, valueTaskSource.Version); IL_0024: ldarg.0 - IL_0025: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0025: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource IL_002a: ldarg.0 - IL_002b: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource - IL_0030: callvirt instance int16 class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::get_Version() + IL_002b: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0030: callvirt instance int16 class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1::get_Version() IL_0035: newobj instance void valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1::.ctor(class [System.Private.CoreLib]System.Threading.Tasks.Sources.IValueTaskSource`1, int16) IL_003a: ret */ @@ -335,7 +325,7 @@ class Perfolizer.Horology.IClock clock ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); - var getVersionMethod = valueTaskSourceField.FieldType.GetProperty(nameof(Helpers.ManualResetValueTaskSource.Version), BindingFlagsPublicInstance).GetGetMethod(true); + var getVersionMethod = valueTaskSourceField.FieldType.GetProperty(nameof(Helpers.AutoResetValueTaskSource.Version), BindingFlagsPublicInstance).GetGetMethod(true); ilBuilder.Emit(OpCodes.Callvirt, getVersionMethod); var ctor = actionMethodBuilder.ReturnType.GetConstructor(new[] { valueTaskSourceField.FieldType, getVersionMethod.ReturnType }); ilBuilder.Emit(OpCodes.Newobj, ctor); @@ -504,14 +494,14 @@ instance void __RunTask () cil managed /* // valueTaskSource.SetResult(clockspan); IL_008d: ldarg.0 - IL_008e: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_008e: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource IL_0093: ldloc.0 - IL_0094: callvirt instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::SetResult(!0) + IL_0094: callvirt instance void class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1::SetResult(!0) */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); ilBuilder.EmitLdloc(clockspanLocal); - ilBuilder.Emit(OpCodes.Callvirt, valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.ManualResetValueTaskSource.SetResult), BindingFlagsPublicInstance)); + ilBuilder.Emit(OpCodes.Callvirt, valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.AutoResetValueTaskSource.SetResult), BindingFlagsPublicInstance)); ilBuilder.MarkLabel(returnLabel); ilBuilder.Emit(OpCodes.Ret); @@ -627,15 +617,15 @@ class [System.Private.CoreLib]System.Exception e /* // valueTaskSource.SetException(e); IL_0018: ldarg.0 - IL_0019: ldfld class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0019: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource IL_001e: ldarg.1 - IL_001f: callvirt instance void class BenchmarkDotNet.Helpers.ManualResetValueTaskSource`1::SetException(class [System.Private.CoreLib]System.Exception) + IL_001f: callvirt instance void class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1::SetException(class [System.Private.CoreLib]System.Exception) IL_0024: ret */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldfld, valueTaskSourceField); ilBuilder.EmitLdarg(exceptionArg); - var setExceptionMethod = valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.ManualResetValueTaskSource.SetException), BindingFlagsPublicInstance); + var setExceptionMethod = valueTaskSourceField.FieldType.GetMethod(nameof(Helpers.AutoResetValueTaskSource.SetException), BindingFlagsPublicInstance); ilBuilder.Emit(OpCodes.Callvirt, setExceptionMethod); ilBuilder.Emit(OpCodes.Ret); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs index 2430bc3489..1f548ad5ec 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.NoEmit/BenchmarkActionFactory_Implementations.cs @@ -106,7 +106,7 @@ private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock cl internal class BenchmarkActionTask : BenchmarkActionBase { private readonly Func callback; - private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private readonly AutoResetValueTaskSource valueTaskSource = new AutoResetValueTaskSource(); private long repeatsRemaining; private readonly Action continuation; private StartedClock startedClock; @@ -166,7 +166,6 @@ private ValueTask InvokeSingleHardcoded() private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - valueTaskSource.Reset(); startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); @@ -223,7 +222,7 @@ private void SetException(Exception e) internal class BenchmarkActionTask : BenchmarkActionBase { private readonly Func> callback; - private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private readonly AutoResetValueTaskSource valueTaskSource = new AutoResetValueTaskSource(); private long repeatsRemaining; private readonly Action continuation; private StartedClock startedClock; @@ -284,7 +283,6 @@ private ValueTask InvokeSingleHardcoded() private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - valueTaskSource.Reset(); startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); @@ -343,7 +341,7 @@ private void SetException(Exception e) internal class BenchmarkActionValueTask : BenchmarkActionBase { private readonly Func callback; - private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private readonly AutoResetValueTaskSource valueTaskSource = new AutoResetValueTaskSource(); private long repeatsRemaining; private readonly Action continuation; private StartedClock startedClock; @@ -403,7 +401,6 @@ private ValueTask InvokeSingleHardcoded() private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - valueTaskSource.Reset(); startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); @@ -460,7 +457,7 @@ private void SetException(Exception e) internal class BenchmarkActionValueTask : BenchmarkActionBase { private readonly Func> callback; - private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private readonly AutoResetValueTaskSource valueTaskSource = new AutoResetValueTaskSource(); private long repeatsRemaining; private readonly Action continuation; private StartedClock startedClock; @@ -521,7 +518,6 @@ private ValueTask InvokeSingleHardcoded() private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - valueTaskSource.Reset(); startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs index 2a588eee61..9c9cbf758f 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs @@ -137,7 +137,7 @@ private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock cl internal class BenchmarkActionTask : BenchmarkActionBase { private readonly Func callback; - private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private readonly AutoResetValueTaskSource valueTaskSource = new AutoResetValueTaskSource(); private long repeatsRemaining; private readonly Action continuation; private StartedClock startedClock; @@ -195,7 +195,6 @@ private ValueTask InvokeSingleHardcoded() private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - valueTaskSource.Reset(); startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); @@ -252,7 +251,7 @@ private void SetException(Exception e) internal class BenchmarkActionTask : BenchmarkActionBase { private readonly Func> callback; - private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private readonly AutoResetValueTaskSource valueTaskSource = new AutoResetValueTaskSource(); private long repeatsRemaining; private readonly Action continuation; private StartedClock startedClock; @@ -311,7 +310,6 @@ private ValueTask InvokeSingleHardcoded() private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - valueTaskSource.Reset(); startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); @@ -370,7 +368,7 @@ private void SetException(Exception e) internal class BenchmarkActionValueTask : BenchmarkActionBase { private readonly Func callback; - private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private readonly AutoResetValueTaskSource valueTaskSource = new AutoResetValueTaskSource(); private long repeatsRemaining; private readonly Action continuation; private StartedClock startedClock; @@ -428,7 +426,6 @@ private ValueTask InvokeSingleHardcoded() private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - valueTaskSource.Reset(); startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); @@ -485,7 +482,7 @@ private void SetException(Exception e) internal class BenchmarkActionValueTask : BenchmarkActionBase { private readonly Func> callback; - private readonly ManualResetValueTaskSource valueTaskSource = new ManualResetValueTaskSource(); + private readonly AutoResetValueTaskSource valueTaskSource = new AutoResetValueTaskSource(); private long repeatsRemaining; private readonly Action continuation; private StartedClock startedClock; @@ -544,7 +541,6 @@ private ValueTask InvokeSingleHardcoded() private ValueTask InvokeNoUnrollHardcoded(long repeatCount, IClock clock) { repeatsRemaining = repeatCount; - valueTaskSource.Reset(); startedClock = clock.Start(); RunTask(); return new ValueTask(valueTaskSource, valueTaskSource.Version); From 89efd0add6e8c02bcd6e8839bc0ddf866bae3130 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 6 Apr 2022 23:06:04 -0400 Subject: [PATCH 20/22] Fixed errors after merge upstream. --- .../Templates/BenchmarkType.txt | 12 +-- .../Emitters/ConsumableConsumeEmitter.cs | 2 +- .../Emitters/ConsumeEmitter.cs | 6 +- .../Emitters/RunnableEmitter.cs | 2 +- .../Emitters/TaskConsumeEmitter.cs | 85 ++++++++++++------- .../Runnable/RunnableConstants.cs | 1 + 6 files changed, 67 insertions(+), 41 deletions(-) diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index 5af34bc14d..8b66bab4f9 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -1,5 +1,5 @@ // the type name must be in sync with WindowsDisassembler.BuildArguments - public unsafe class Runnable_$ID$ : global::$WorkloadTypeName$ + public unsafe partial class Runnable_$ID$ : global::$WorkloadTypeName$ { public static void Run(BenchmarkDotNet.Engines.IHost host, System.String benchmarkName) { @@ -57,9 +57,6 @@ public Runnable_$ID$() { -#if RETURNS_AWAITABLE_$ID$ - continuation = __Continuation; -#endif globalSetupAction = $GlobalSetupMethodName$; globalCleanupAction = $GlobalCleanupMethodName$; iterationSetupAction = $IterationSetupMethodName$; @@ -67,6 +64,7 @@ overheadDelegate = __Overhead; workloadDelegate = $WorkloadMethodDelegate$; $InitializeArgumentFields$ + __SetContinuation(); } private System.Func globalSetupAction; @@ -112,14 +110,18 @@ $OverheadImplementation$ } + partial void __SetContinuation(); + #if RETURNS_AWAITABLE_$ID$ private readonly BenchmarkDotNet.Helpers.AutoResetValueTaskSource valueTaskSource = new BenchmarkDotNet.Helpers.AutoResetValueTaskSource(); private System.Int64 repeatsRemaining; - private readonly System.Action continuation; + private System.Action continuation; private Perfolizer.Horology.StartedClock startedClock; private $AwaiterTypeName$ currentAwaiter; + partial void __SetContinuation() => continuation = __Continuation; + // Awaits are not unrolled. private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumableConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumableConsumeEmitter.cs index 76a2a5f505..b92c0228aa 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumableConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumableConsumeEmitter.cs @@ -70,7 +70,7 @@ protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerato ilBuilder.EmitReturnDefault(ConsumableInfo.WorkloadMethodReturnType, disassemblyDiagnoserLocal); } - protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) + protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder, RunnableEmitter runnableEmitter) { var ctor = typeof(Consumer).GetConstructor(Array.Empty()); if (ctor == null) diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumeEmitter.cs index f4ecb98e35..7a8529dc74 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/ConsumeEmitter.cs @@ -104,14 +104,14 @@ protected virtual void OnEmitMembersOverride(TypeBuilder runnableBuilder) { } - public void OnEmitCtorBody(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) + public void OnEmitCtorBody(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder, RunnableEmitter runnableEmitter) { AssertNoBuilder(); - OnEmitCtorBodyOverride(constructorBuilder, ilBuilder); + OnEmitCtorBodyOverride(constructorBuilder, ilBuilder, runnableEmitter); } - protected virtual void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) + protected virtual void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder, RunnableEmitter runnableEmitter) { } diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs index c995ab8e92..883f9e0a80 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs @@ -890,7 +890,7 @@ private void EmitCtorBody() ilBuilder.EmitCallBaseParameterlessCtor(ctorMethod); - consumeEmitter.OnEmitCtorBody(ctorMethod, ilBuilder); + consumeEmitter.OnEmitCtorBody(ctorMethod, ilBuilder, this); ilBuilder.EmitSetDelegateToThisField(globalSetupActionField, globalSetupMethod); ilBuilder.EmitSetDelegateToThisField(globalCleanupActionField, globalCleanupMethod); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs index 1a469d32d7..12997a3654 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs @@ -33,6 +33,7 @@ internal class TaskConsumeEmitter : ConsumeEmitter private MethodBuilder overheadActionImplMethod; private MethodBuilder workloadActionImplMethod; + private MethodBuilder setContinuationMethod; private MethodBuilder runTaskMethod; private MethodBuilder continuationMethod; private MethodBuilder setExceptionMethod; @@ -50,7 +51,7 @@ protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder) valueTaskSourceField = runnableBuilder.DefineField(ValueTaskSourceFieldName, typeof(Helpers.AutoResetValueTaskSource), FieldAttributes.Private | FieldAttributes.InitOnly); repeatsRemainingField = runnableBuilder.DefineField(RepeatsRemainingFieldName, typeof(long), FieldAttributes.Private); - continuationField = runnableBuilder.DefineField(ContinuationFieldName, typeof(Action), FieldAttributes.Private | FieldAttributes.InitOnly); + continuationField = runnableBuilder.DefineField(ContinuationFieldName, typeof(Action), FieldAttributes.Private); startedClockField = runnableBuilder.DefineField(StartedClockFieldName, typeof(StartedClock), FieldAttributes.Private); // (Value)TaskAwaiter() currentAwaiterField = runnableBuilder.DefineField(CurrentAwaiterFieldName, @@ -70,7 +71,7 @@ protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerato ilBuilder.EmitReturnDefault(ConsumableInfo.WorkloadMethodReturnType, disassemblyDiagnoserLocal); } - protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder) + protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBuilder, ILGenerator ilBuilder, RunnableEmitter runnableEmitter) { var ctor = typeof(Helpers.AutoResetValueTaskSource).GetConstructor(Array.Empty()); if (ctor == null) @@ -80,13 +81,19 @@ protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBui // valueTaskSourceField = new BenchmarkDotNet.Helpers.AutoResetValueTaskSource(); IL_0000: ldarg.0 IL_0001: newobj instance void class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1::.ctor() - IL_0006: stfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0006: stfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkDotNet.Autogenerated.Runnable_0::valueTaskSource */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Newobj, ctor); ilBuilder.Emit(OpCodes.Stfld, valueTaskSourceField); - // continuation = __Continuation; - ilBuilder.EmitSetDelegateToThisField(continuationField, continuationMethod); + /* + // __SetContinuation(); + IL_0006: ldarg.0 + IL_0007: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::__SetContinuation() + */ + continuationMethod = EmitSetContinuationImpl(runnableEmitter); + ilBuilder.Emit(OpCodes.Ldarg_0); + ilBuilder.Emit(OpCodes.Call, continuationMethod); } public override MethodBuilder EmitActionImpl(RunnableEmitter runnableEmitter, string methodName, RunnableActionKind actionKind, int unrollFactor) @@ -122,7 +129,7 @@ class Perfolizer.Horology.IClock clock IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: ldarg.2 - IL_0003: call instance valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1 BenchmarkRunner_0::WorkloadActionImpl(int64, class Perfolizer.Horology.IClock) + IL_0003: call instance valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1 BenchmarkDotNet.Autogenerated.Runnable_0::WorkloadActionImpl(int64, class Perfolizer.Horology.IClock) IL_0008: ret */ ilBuilder.Emit(OpCodes.Ldarg_0); @@ -172,7 +179,7 @@ class Perfolizer.Horology.IClock clock // repeatsRemaining = invokeCount; IL_0000: ldarg.0 IL_0001: ldarg.1 - IL_0002: stfld int64 BenchmarkRunner_0::repeatsRemaining + IL_0002: stfld int64 BenchmarkDotNet.Autogenerated.Runnable_0::repeatsRemaining */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.EmitLdarg(toArg); @@ -188,7 +195,7 @@ class Perfolizer.Horology.IClock clock IL_0009: ldarg.0 IL_000a: ldarg.2 IL_000b: call valuetype Perfolizer.Horology.StartedClock Perfolizer.Horology.ClockExtensions::Start(class Perfolizer.Horology.IClock) - IL_0010: stfld valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_0010: stfld valuetype Perfolizer.Horology.StartedClock BenchmarkDotNet.Autogenerated.Runnable_0::startedClock */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.EmitLdarg(clockArg); @@ -239,7 +246,7 @@ class Perfolizer.Horology.IClock clock /* // return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); IL_0044: ldarg.0 - IL_0045: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_0045: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkDotNet.Autogenerated.Runnable_0::startedClock IL_004a: call instance valuetype Perfolizer.Horology.ClockSpan Perfolizer.Horology.StartedClock::GetElapsed() IL_004f: newobj instance void valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1::.ctor(!0) IL_0054: ret @@ -288,7 +295,7 @@ class Perfolizer.Horology.IClock clock // repeatsRemaining = invokeCount; IL_0000: ldarg.0 IL_0001: ldarg.1 - IL_0002: stfld int64 BenchmarkRunner_0::repeatsRemaining + IL_0002: stfld int64 BenchmarkDotNet.Autogenerated.Runnable_0::repeatsRemaining */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.EmitLdarg(toArg); @@ -298,7 +305,7 @@ class Perfolizer.Horology.IClock clock IL_0012: ldarg.0 IL_0013: ldarg.2 IL_0014: call valuetype Perfolizer.Horology.StartedClock Perfolizer.Horology.ClockExtensions::Start(class Perfolizer.Horology.IClock) - IL_0019: stfld valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_0019: stfld valuetype Perfolizer.Horology.StartedClock BenchmarkDotNet.Autogenerated.Runnable_0::startedClock */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.EmitLdarg(clockArg); @@ -307,16 +314,16 @@ class Perfolizer.Horology.IClock clock /* // __RunTask(); IL_001e: ldarg.0 - IL_001f: call instance void BenchmarkRunner_0::__RunTask() + IL_001f: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::__RunTask() */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Call, runTaskMethod); /* // return new System.Threading.Tasks.ValueTask(valueTaskSource, valueTaskSource.Version); IL_0024: ldarg.0 - IL_0025: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0025: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkDotNet.Autogenerated.Runnable_0::valueTaskSource IL_002a: ldarg.0 - IL_002b: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_002b: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkDotNet.Autogenerated.Runnable_0::valueTaskSource IL_0030: callvirt instance int16 class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1::get_Version() IL_0035: newobj instance void valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1::.ctor(class [System.Private.CoreLib]System.Threading.Tasks.Sources.IValueTaskSource`1, int16) IL_003a: ret @@ -334,6 +341,22 @@ class Perfolizer.Horology.IClock clock return workloadActionImplMethod = actionMethodBuilder; } + private MethodBuilder EmitSetContinuationImpl(RunnableEmitter runnableEmitter) + { + /* + .method private hidebysig + instance void __SetContinuation () cil managed + */ + var actionMethodBuilder = runnableEmitter.runnableBuilder.DefinePrivateVoidInstanceMethod(SetContinuationMethodName); + + var ilBuilder = actionMethodBuilder.GetILGenerator(); + // continuation = __Continuation; + ilBuilder.EmitSetDelegateToThisField(continuationField, continuationMethod); + ilBuilder.Emit(OpCodes.Ret); + + return actionMethodBuilder; + } + private MethodBuilder EmitRunTaskImpl(RunnableEmitter runnableEmitter) { /* @@ -382,12 +405,12 @@ instance void __RunTask () cil managed // currentAwaiter = workloadDelegate().GetAwaiter(); IL_0002: ldarg.0 IL_0003: ldarg.0 - IL_0004: ldfld class [System.Private.CoreLib]System.Func`1> BenchmarkRunner_0::workloadDelegate + IL_0004: ldfld class [System.Private.CoreLib]System.Func`1> BenchmarkDotNet.Autogenerated.Runnable_0::workloadDelegate IL_0009: callvirt instance !0 class [System.Private.CoreLib]System.Func`1>::Invoke() IL_000e: stloc.1 IL_000f: ldloca.s 1 IL_0011: call instance valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 valuetype [System.Private.CoreLib]System.Threading.Tasks.ValueTask`1::GetAwaiter() - IL_0016: stfld valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0016: stfld valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkDotNet.Autogenerated.Runnable_0::currentAwaiter */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Ldarg_0); @@ -398,7 +421,7 @@ instance void __RunTask () cil managed /* // if (!currentAwaiter.IsCompleted) IL_001b: ldarg.0 - IL_001c: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_001c: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkDotNet.Autogenerated.Runnable_0::currentAwaiter IL_0021: call instance bool valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1::get_IsCompleted() IL_0026: brtrue.s IL_003b */ @@ -412,9 +435,9 @@ instance void __RunTask () cil managed /* // currentAwaiter.UnsafeOnCompleted(continuation); IL_0028: ldarg.0 - IL_0029: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0029: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkDotNet.Autogenerated.Runnable_0::currentAwaiter IL_002e: ldarg.0 - IL_002f: ldfld class [System.Private.CoreLib]System.Action BenchmarkRunner_0::continuation + IL_002f: ldfld class [System.Private.CoreLib]System.Action BenchmarkDotNet.Autogenerated.Runnable_0::continuation IL_0034: call instance void valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1::UnsafeOnCompleted(class [System.Private.CoreLib]System.Action) */ ilBuilder.Emit(OpCodes.Ldarg_0); @@ -429,7 +452,7 @@ instance void __RunTask () cil managed /* // currentAwaiter.GetResult(); IL_003b: ldarg.0 - IL_003c: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_003c: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkDotNet.Autogenerated.Runnable_0::currentAwaiter IL_0041: call instance !0 valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1::GetResult() IL_0046: pop */ @@ -451,7 +474,7 @@ instance void __RunTask () cil managed IL_005f: stloc.3 IL_0060: ldarg.0 IL_0061: ldloc.3 - IL_0062: call instance void BenchmarkRunner_0::__SetException(class [System.Private.CoreLib]System.Exception) + IL_0062: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::__SetException(class [System.Private.CoreLib]System.Exception) */ ilBuilder.EmitStloc(exceptionLocal); ilBuilder.Emit(OpCodes.Ldarg_0); @@ -465,7 +488,7 @@ instance void __RunTask () cil managed /* // var clockspan = startedClock.GetElapsed(); IL_0069: ldarg.0 - IL_006a: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_006a: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkDotNet.Autogenerated.Runnable_0::startedClock IL_006f: call instance valuetype Perfolizer.Horology.ClockSpan Perfolizer.Horology.StartedClock::GetElapsed() IL_0074: stloc.0 */ @@ -476,7 +499,7 @@ instance void __RunTask () cil managed /* // currentAwaiter = default; IL_0075: ldarg.0 - IL_0076: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0076: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkDotNet.Autogenerated.Runnable_0::currentAwaiter IL_007b: initobj valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 */ ilBuilder.Emit(OpCodes.Ldarg_0); @@ -485,7 +508,7 @@ instance void __RunTask () cil managed /* // startedClock = default(Perfolizer.Horology.StartedClock); IL_0081: ldarg.0 - IL_0082: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_0082: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkDotNet.Autogenerated.Runnable_0::startedClock IL_0087: initobj Perfolizer.Horology.StartedClock */ ilBuilder.Emit(OpCodes.Ldarg_0); @@ -494,7 +517,7 @@ instance void __RunTask () cil managed /* // valueTaskSource.SetResult(clockspan); IL_008d: ldarg.0 - IL_008e: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_008e: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkDotNet.Autogenerated.Runnable_0::valueTaskSource IL_0093: ldloc.0 IL_0094: callvirt instance void class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1::SetResult(!0) */ @@ -533,7 +556,7 @@ instance void __Continuation () cil managed /* // currentAwaiter.GetResult(); IL_0000: ldarg.0 - IL_0001: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0001: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkDotNet.Autogenerated.Runnable_0::currentAwaiter IL_0006: call instance !0 valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1::GetResult() IL_000b: pop */ @@ -554,7 +577,7 @@ instance void __Continuation () cil managed // __SetException(e); IL_000f: ldarg.0 IL_0010: ldloc.0 - IL_0011: call instance void BenchmarkRunner_0::__SetException(class [System.Private.CoreLib]System.Exception) + IL_0011: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::__SetException(class [System.Private.CoreLib]System.Exception) */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.EmitLdloc(exceptionLocal); @@ -567,7 +590,7 @@ instance void __Continuation () cil managed /* // __RunTask(); IL_0018: ldarg.0 - IL_0019: call instance void BenchmarkRunner_0::__RunTask() + IL_0019: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::__RunTask() */ ilBuilder.Emit(OpCodes.Ldarg_0); ilBuilder.Emit(OpCodes.Call, runTaskMethod); @@ -599,7 +622,7 @@ class [System.Private.CoreLib]System.Exception e /* // currentAwaiter = default; IL_0000: ldarg.0 - IL_0001: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkRunner_0::currentAwaiter + IL_0001: ldflda valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 BenchmarkDotNet.Autogenerated.Runnable_0::currentAwaiter IL_0006: initobj valuetype [System.Private.CoreLib]System.Runtime.CompilerServices.ValueTaskAwaiter`1 */ ilBuilder.Emit(OpCodes.Ldarg_0); @@ -608,7 +631,7 @@ class [System.Private.CoreLib]System.Exception e /* // startedClock = default(Perfolizer.Horology.StartedClock); IL_000c: ldarg.0 - IL_000d: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkRunner_0::startedClock + IL_000d: ldflda valuetype Perfolizer.Horology.StartedClock BenchmarkDotNet.Autogenerated.Runnable_0::startedClock IL_0012: initobj Perfolizer.Horology.StartedClock */ ilBuilder.Emit(OpCodes.Ldarg_0); @@ -617,7 +640,7 @@ class [System.Private.CoreLib]System.Exception e /* // valueTaskSource.SetException(e); IL_0018: ldarg.0 - IL_0019: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkRunner_0::valueTaskSource + IL_0019: ldfld class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1 BenchmarkDotNet.Autogenerated.Runnable_0::valueTaskSource IL_001e: ldarg.1 IL_001f: callvirt instance void class BenchmarkDotNet.Helpers.AutoResetValueTaskSource`1::SetException(class [System.Private.CoreLib]System.Exception) IL_0024: ret diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs index 00bda018d9..63237ee10b 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs @@ -51,6 +51,7 @@ public class RunnableConstants public const string CurrentAwaiterFieldName = "currentAwaiter"; public const string OverheadActionImplMethodName = "OverheadActionImpl"; public const string WorkloadActionImplMethodName = "WorkloadActionImpl"; + public const string SetContinuationMethodName = "__SetContinuation"; public const string RunTaskMethodName = "__RunTask"; public const string ContinuationMethodName = "__Continuation"; public const string SetExceptionMethodName = "__SetException"; From af291959c18f6ba198a4093892c0f4530c456411 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 27 Jul 2022 01:53:54 -0400 Subject: [PATCH 21/22] Use ManualResetValueTaskSourceCore instead of copying source code. --- .../Helpers/AutoResetValueTaskSource.cs | 139 +++--------------- 1 file changed, 20 insertions(+), 119 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/AutoResetValueTaskSource.cs b/src/BenchmarkDotNet/Helpers/AutoResetValueTaskSource.cs index 1a132775f1..7fc093a9ea 100644 --- a/src/BenchmarkDotNet/Helpers/AutoResetValueTaskSource.cs +++ b/src/BenchmarkDotNet/Helpers/AutoResetValueTaskSource.cs @@ -1,8 +1,4 @@ -using JetBrains.Annotations; -using System; -using System.Diagnostics; -using System.Runtime.ExceptionServices; -using System.Threading; +using System; using System.Threading.Tasks; using System.Threading.Tasks.Sources; @@ -13,140 +9,45 @@ namespace BenchmarkDotNet.Helpers /// public class AutoResetValueTaskSource : IValueTaskSource, IValueTaskSource { - [CanBeNull] private Action _continuation; - [CanBeNull] private object _continuationState; - private bool _completed; - [CanBeNull] private TResult _result; - [CanBeNull] private ExceptionDispatchInfo _error; - private short _version; - - private void Reset() - { - // Reset/update state for the next use/await of this instance. - _version++; - _completed = false; - _result = default; - _error = null; - _continuation = null; - _continuationState = null; - } + private ManualResetValueTaskSourceCore _sourceCore; /// Completes with a successful result. /// The result. - public void SetResult(TResult result) - { - _result = result; - SignalCompletion(); - } + public void SetResult(TResult result) => _sourceCore.SetResult(result); /// Completes with an error. /// The exception. - public void SetException(Exception error) - { - _error = ExceptionDispatchInfo.Capture(error); - SignalCompletion(); - } + public void SetException(Exception error) => _sourceCore.SetException(error); /// Gets the operation version. - public short Version => _version; - - /// Gets the status of the operation. - /// Opaque value that was provided to the 's constructor. - public ValueTaskSourceStatus GetStatus(short token) - { - ValidateToken(token); - return - _continuation == null || !_completed ? ValueTaskSourceStatus.Pending : - _error == null ? ValueTaskSourceStatus.Succeeded : - _error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled : - ValueTaskSourceStatus.Faulted; - } + public short Version => _sourceCore.Version; - /// Gets the result of the operation. - /// Opaque value that was provided to the 's constructor. - public TResult GetResult(short token) + private TResult GetResult(short token) { - ValidateToken(token); - if (!_completed) + // We don't want to reset this if the token is invalid. + if (token != Version) { throw new InvalidOperationException(); } - - var result = _result; - var error = _error; - Reset(); - error?.Throw(); - return result; - } - - public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - if (continuation == null) - { - throw new ArgumentNullException(nameof(continuation)); - } - ValidateToken(token); - - // We need to set the continuation state before we swap in the delegate, so that - // if there's a race between this and SetResult/Exception and SetResult/Exception - // sees the _continuation as non-null, it'll be able to invoke it with the state - // stored here. However, this also means that if this is used incorrectly (e.g. - // awaited twice concurrently), _continuationState might get erroneously overwritten. - // To minimize the chances of that, we check preemptively whether _continuation - // is already set to something other than the completion sentinel. - - object oldContinuation = _continuation; - if (oldContinuation == null) - { - _continuationState = state; - oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null); - } - - if (oldContinuation != null) + try { - // Operation already completed, so we need to call the supplied callback. - if (!ReferenceEquals(oldContinuation, AutoResetValueTaskSourceCoreShared.s_sentinel)) - { - throw new InvalidOperationException(); - } - - continuation(state); + return _sourceCore.GetResult(token); } - } - - private void ValidateToken(short token) - { - if (token != _version) + finally { - throw new InvalidOperationException(); + _sourceCore.Reset(); } } - private void SignalCompletion() - { - if (_completed) - { - throw new InvalidOperationException(); - } - _completed = true; + void IValueTaskSource.GetResult(short token) => GetResult(token); + TResult IValueTaskSource.GetResult(short token) => GetResult(token); - Interlocked.CompareExchange(ref _continuation, AutoResetValueTaskSourceCoreShared.s_sentinel, null) - ?.Invoke(_continuationState); - } + ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _sourceCore.GetStatus(token); + ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _sourceCore.GetStatus(token); - void IValueTaskSource.GetResult(short token) - { - GetResult(token); - } - } - - internal static class AutoResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication - { - internal static readonly Action s_sentinel = CompletionSentinel; - private static void CompletionSentinel(object _) // named method to aid debugging - { - Debug.Fail("The sentinel delegate should never be invoked."); - throw new InvalidOperationException(); - } + // Don't pass the flags, we don't want to schedule the continuation on the current SynchronizationContext or TaskScheduler if the user runs this in-process, as that may cause a deadlock when this is waited on synchronously. + // And we don't want to capture the ExecutionContext (we don't use it, and it causes allocations in the full framework). + void IValueTaskSource.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _sourceCore.OnCompleted(continuation, state, token, ValueTaskSourceOnCompletedFlags.None); + void IValueTaskSource.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _sourceCore.OnCompleted(continuation, state, token, ValueTaskSourceOnCompletedFlags.None); } } \ No newline at end of file From bdedcf96eb4ca209f8d750ee107bc06db72d726f Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 14 Aug 2022 21:17:42 -0400 Subject: [PATCH 22/22] Fixed compile error after rebase. --- .../Code/DeclarationsProvider.cs | 2 +- src/BenchmarkDotNet/Engines/IEngine.cs | 1 + .../Templates/BenchmarkType.txt | 96 +++++++++++++++++-- .../Emitters/TaskConsumeEmitter.cs | 6 +- 4 files changed, 95 insertions(+), 10 deletions(-) diff --git a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs index 058f912e53..5f2eca39a6 100644 --- a/src/BenchmarkDotNet/Code/DeclarationsProvider.cs +++ b/src/BenchmarkDotNet/Code/DeclarationsProvider.cs @@ -150,7 +150,7 @@ internal class TaskDeclarationsProvider : DeclarationsProvider { public TaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) { } - public override string ExtraDefines => "#define RETURNS_AWAITABLE"; + public override string ReturnsDefinition => "RETURNS_AWAITABLE"; public override string AwaiterTypeName => WorkloadMethodReturnType.GetMethod(nameof(Task.GetAwaiter), BindingFlags.Public | BindingFlags.Instance).ReturnType.GetCorrectCSharpTypeName(); diff --git a/src/BenchmarkDotNet/Engines/IEngine.cs b/src/BenchmarkDotNet/Engines/IEngine.cs index c01a398ed0..40bb64ce69 100644 --- a/src/BenchmarkDotNet/Engines/IEngine.cs +++ b/src/BenchmarkDotNet/Engines/IEngine.cs @@ -6,6 +6,7 @@ using BenchmarkDotNet.Reports; using JetBrains.Annotations; using Perfolizer.Horology; +using NotNullAttribute = JetBrains.Annotations.NotNullAttribute; namespace BenchmarkDotNet.Engines { diff --git a/src/BenchmarkDotNet/Templates/BenchmarkType.txt b/src/BenchmarkDotNet/Templates/BenchmarkType.txt index 8b66bab4f9..584cce4d91 100644 --- a/src/BenchmarkDotNet/Templates/BenchmarkType.txt +++ b/src/BenchmarkDotNet/Templates/BenchmarkType.txt @@ -90,19 +90,19 @@ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private void Dummy1() { - dummyVar++;@DummyUnroll@ + @DummyUnroll@ } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private void Dummy2() { - dummyVar++;@DummyUnroll@ + @DummyUnroll@ } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private void Dummy3() { - dummyVar++;@DummyUnroll@ + @DummyUnroll@ } private $OverheadMethodReturnTypeName$ __Overhead($ArgumentsDefinition$) // __ is to avoid possible name conflict @@ -122,17 +122,26 @@ partial void __SetContinuation() => continuation = __Continuation; +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif // Awaits are not unrolled. private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { return OverheadActionImpl(invokeCount, clock); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { return OverheadActionImpl(invokeCount, clock); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionImpl(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { repeatsRemaining = invokeCount; @@ -154,16 +163,25 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { return WorkloadActionImpl(invokeCount, clock); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { return WorkloadActionImpl(invokeCount, clock); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionImpl(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { repeatsRemaining = invokeCount; @@ -172,6 +190,9 @@ return new System.Threading.Tasks.ValueTask(valueTaskSource, valueTaskSource.Version); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private void __RunTask() { try @@ -199,6 +220,9 @@ valueTaskSource.SetResult(clockspan); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private void __Continuation() { try @@ -236,6 +260,9 @@ private BenchmarkDotNet.Engines.Consumer consumer = new BenchmarkDotNet.Engines.Consumer(); +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -247,6 +274,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -258,6 +288,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -269,6 +302,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -294,6 +330,9 @@ #elif RETURNS_NON_CONSUMABLE_STRUCT_$ID$ +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -307,6 +346,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -320,6 +362,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -333,6 +378,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -365,6 +413,9 @@ #elif RETURNS_BYREF_$ID$ +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -378,6 +429,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -393,6 +447,9 @@ private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -405,7 +462,10 @@ BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias); return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } - + +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -432,6 +492,9 @@ } #elif RETURNS_BYREF_READONLY_$ID$ +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -445,6 +508,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -460,6 +526,9 @@ private $WorkloadMethodReturnType$ workloadDefaultValueHolder = default($WorkloadMethodReturnType$); +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -472,7 +541,10 @@ BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias); return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } - + +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -499,6 +571,9 @@ } #elif RETURNS_VOID_$ID$ +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -510,6 +585,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask OverheadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -521,6 +599,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -532,6 +613,9 @@ return new System.Threading.Tasks.ValueTask(startedClock.GetElapsed()); } +#if NETCOREAPP3_0_OR_GREATER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveOptimization)] +#endif private System.Threading.Tasks.ValueTask WorkloadActionNoUnroll(System.Int64 invokeCount, Perfolizer.Horology.IClock clock) { $LoadArguments$ @@ -552,5 +636,5 @@ $WorkloadMethodCall$; } } -#endif +#endif // RETURNS } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs index 12997a3654..355dc353f4 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/TaskConsumeEmitter.cs @@ -91,9 +91,9 @@ protected override void OnEmitCtorBodyOverride(ConstructorBuilder constructorBui IL_0006: ldarg.0 IL_0007: call instance void BenchmarkDotNet.Autogenerated.Runnable_0::__SetContinuation() */ - continuationMethod = EmitSetContinuationImpl(runnableEmitter); + setContinuationMethod = EmitSetContinuationImpl(runnableEmitter); ilBuilder.Emit(OpCodes.Ldarg_0); - ilBuilder.Emit(OpCodes.Call, continuationMethod); + ilBuilder.Emit(OpCodes.Call, setContinuationMethod); } public override MethodBuilder EmitActionImpl(RunnableEmitter runnableEmitter, string methodName, RunnableActionKind actionKind, int unrollFactor) @@ -344,7 +344,7 @@ class Perfolizer.Horology.IClock clock private MethodBuilder EmitSetContinuationImpl(RunnableEmitter runnableEmitter) { /* - .method private hidebysig + .method private hidebysig instance void __SetContinuation () cil managed */ var actionMethodBuilder = runnableEmitter.runnableBuilder.DefinePrivateVoidInstanceMethod(SetContinuationMethodName);