Skip to content

Commit f0acf70

Browse files
committed
Changed AwaitHelper to static.
1 parent 4b3d20a commit f0acf70

File tree

9 files changed

+63
-133
lines changed

9 files changed

+63
-133
lines changed

src/BenchmarkDotNet/Code/DeclarationsProvider.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ private string GetMethodName(MethodInfo method)
6363
(method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) ||
6464
method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))))
6565
{
66-
return $"() => awaitHelper.GetResult({method.Name}())";
66+
return $"() => BenchmarkDotNet.Helpers.AwaitHelper.GetResult({method.Name}())";
6767
}
6868

6969
return method.Name;
@@ -150,9 +150,9 @@ internal class TaskDeclarationsProvider : VoidDeclarationsProvider
150150
public TaskDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
151151

152152
public override string WorkloadMethodDelegate(string passArguments)
153-
=> $"({passArguments}) => {{ awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}";
153+
=> $"({passArguments}) => {{ BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}";
154154

155-
public override string GetWorkloadMethodCall(string passArguments) => $"awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))";
155+
public override string GetWorkloadMethodCall(string passArguments) => $"BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))";
156156

157157
protected override Type WorkloadMethodReturnType => typeof(void);
158158
}
@@ -167,8 +167,8 @@ public GenericTaskDeclarationsProvider(Descriptor descriptor) : base(descriptor)
167167
protected override Type WorkloadMethodReturnType => Descriptor.WorkloadMethod.ReturnType.GetTypeInfo().GetGenericArguments().Single();
168168

169169
public override string WorkloadMethodDelegate(string passArguments)
170-
=> $"({passArguments}) => {{ return awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}";
170+
=> $"({passArguments}) => {{ return BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments})); }}";
171171

172-
public override string GetWorkloadMethodCall(string passArguments) => $"awaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))";
172+
public override string GetWorkloadMethodCall(string passArguments) => $"BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))";
173173
}
174174
}

src/BenchmarkDotNet/Helpers/AwaitHelper.cs

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66

77
namespace BenchmarkDotNet.Helpers
88
{
9-
public class AwaitHelper
9+
public static class AwaitHelper
1010
{
1111
private class ValueTaskWaiter
1212
{
13+
// We use thread static field so that multiple threads can use individual lock object and callback.
14+
[ThreadStatic]
15+
private static ValueTaskWaiter ts_current;
16+
internal static ValueTaskWaiter Current => ts_current ??= new ValueTaskWaiter();
17+
1318
private readonly Action awaiterCallback;
1419
private bool awaiterCompleted;
1520

16-
internal ValueTaskWaiter()
21+
private ValueTaskWaiter()
1722
{
1823
awaiterCallback = AwaiterCallback;
1924
}
@@ -28,80 +33,70 @@ private void AwaiterCallback()
2833
}
2934

3035
// Hook up a callback instead of converting to Task to prevent extra allocations on each benchmark run.
31-
internal void GetResult(ValueTask task)
36+
internal void Wait(ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter)
3237
{
33-
// Don't continue on the captured context, as that may result in a deadlock if the user runs this in-process.
34-
var awaiter = task.ConfigureAwait(false).GetAwaiter();
35-
if (!awaiter.IsCompleted)
38+
lock (this)
3639
{
37-
lock (this)
40+
awaiterCompleted = false;
41+
awaiter.UnsafeOnCompleted(awaiterCallback);
42+
// Check if the callback executed synchronously before blocking.
43+
if (!awaiterCompleted)
3844
{
39-
awaiterCompleted = false;
40-
awaiter.UnsafeOnCompleted(awaiterCallback);
41-
// Check if the callback executed synchronously before blocking.
42-
if (!awaiterCompleted)
43-
{
44-
System.Threading.Monitor.Wait(this);
45-
}
45+
System.Threading.Monitor.Wait(this);
4646
}
4747
}
48-
awaiter.GetResult();
4948
}
5049

51-
internal T GetResult<T>(ValueTask<T> task)
50+
internal void Wait<T>(ConfiguredValueTaskAwaitable<T>.ConfiguredValueTaskAwaiter awaiter)
5251
{
53-
// Don't continue on the captured context, as that may result in a deadlock if the user runs this in-process.
54-
var awaiter = task.ConfigureAwait(false).GetAwaiter();
55-
if (!awaiter.IsCompleted)
52+
lock (this)
5653
{
57-
lock (this)
54+
awaiterCompleted = false;
55+
awaiter.UnsafeOnCompleted(awaiterCallback);
56+
// Check if the callback executed synchronously before blocking.
57+
if (!awaiterCompleted)
5858
{
59-
awaiterCompleted = false;
60-
awaiter.UnsafeOnCompleted(awaiterCallback);
61-
// Check if the callback executed synchronously before blocking.
62-
if (!awaiterCompleted)
63-
{
64-
System.Threading.Monitor.Wait(this);
65-
}
59+
System.Threading.Monitor.Wait(this);
6660
}
6761
}
68-
return awaiter.GetResult();
69-
}
70-
}
71-
72-
// We use thread static field so that multiple threads can use individual lock object and callback.
73-
[ThreadStatic]
74-
private static ValueTaskWaiter ts_valueTaskWaiter;
75-
76-
private ValueTaskWaiter CurrentValueTaskWaiter
77-
{
78-
get
79-
{
80-
if (ts_valueTaskWaiter == null)
81-
{
82-
ts_valueTaskWaiter = new ValueTaskWaiter();
83-
}
84-
return ts_valueTaskWaiter;
8562
}
8663
}
8764

8865
// we use GetAwaiter().GetResult() because it's fastest way to obtain the result in blocking way,
8966
// and will eventually throw actual exception, not aggregated one
90-
public void GetResult(Task task) => task.GetAwaiter().GetResult();
67+
public static void GetResult(Task task) => task.GetAwaiter().GetResult();
9168

92-
public T GetResult<T>(Task<T> task) => task.GetAwaiter().GetResult();
69+
public static T GetResult<T>(Task<T> task) => task.GetAwaiter().GetResult();
9370

9471
// ValueTask can be backed by an IValueTaskSource that only supports asynchronous awaits, so we have to hook up a callback instead of calling .GetAwaiter().GetResult() like we do for Task.
9572
// The alternative is to convert it to Task using .AsTask(), but that causes allocations which we must avoid for memory diagnoser.
96-
public void GetResult(ValueTask task) => CurrentValueTaskWaiter.GetResult(task);
73+
public static void GetResult(ValueTask task)
74+
{
75+
// Don't continue on the captured context, as that may result in a deadlock if the user runs this in-process.
76+
var awaiter = task.ConfigureAwait(false).GetAwaiter();
77+
if (!awaiter.IsCompleted)
78+
{
79+
ValueTaskWaiter.Current.Wait(awaiter);
80+
}
81+
awaiter.GetResult();
82+
}
9783

98-
public T GetResult<T>(ValueTask<T> task) => CurrentValueTaskWaiter.GetResult(task);
84+
public static T GetResult<T>(ValueTask<T> task)
85+
{
86+
// Don't continue on the captured context, as that may result in a deadlock if the user runs this in-process.
87+
var awaiter = task.ConfigureAwait(false).GetAwaiter();
88+
if (!awaiter.IsCompleted)
89+
{
90+
ValueTaskWaiter.Current.Wait(awaiter);
91+
}
92+
return awaiter.GetResult();
93+
}
9994

10095
internal static MethodInfo GetGetResultMethod(Type taskType)
10196
{
10297
if (!taskType.IsGenericType)
10398
{
104-
return typeof(AwaitHelper).GetMethod(nameof(AwaitHelper.GetResult), BindingFlags.Public | BindingFlags.Instance, null, new Type[1] { taskType }, null);
99+
return typeof(AwaitHelper).GetMethod(nameof(AwaitHelper.GetResult), BindingFlags.Public | BindingFlags.Static, null, new Type[1] { taskType }, null);
105100
}
106101

107102
Type compareType = taskType.GetGenericTypeDefinition() == typeof(ValueTask<>) ? typeof(ValueTask<>)
@@ -116,7 +111,7 @@ internal static MethodInfo GetGetResultMethod(Type taskType)
116111
.ReturnType
117112
.GetMethod(nameof(TaskAwaiter.GetResult), BindingFlags.Public | BindingFlags.Instance)
118113
.ReturnType;
119-
return typeof(AwaitHelper).GetMethods(BindingFlags.Public | BindingFlags.Instance)
114+
return typeof(AwaitHelper).GetMethods(BindingFlags.Public | BindingFlags.Static)
120115
.First(m =>
121116
{
122117
if (m.Name != nameof(AwaitHelper.GetResult)) return false;

src/BenchmarkDotNet/Helpers/Reflection.Emit/IlGeneratorStatementExtensions.cs

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,37 +42,6 @@ public static void EmitVoidReturn(this ILGenerator ilBuilder, MethodBuilder meth
4242
ilBuilder.Emit(OpCodes.Ret);
4343
}
4444

45-
public static void EmitSetFieldToNewInstance(
46-
this ILGenerator ilBuilder,
47-
FieldBuilder field,
48-
Type instanceType)
49-
{
50-
if (field.IsStatic)
51-
throw new ArgumentException("The field should be instance field", nameof(field));
52-
53-
if (instanceType != null)
54-
{
55-
/*
56-
IL_0006: ldarg.0
57-
IL_0007: newobj instance void BenchmarkDotNet.Helpers.AwaitHelper::.ctor()
58-
IL_000c: stfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Autogenerated.Runnable_0::awaitHelper
59-
*/
60-
var ctor = instanceType.GetConstructor(Array.Empty<Type>());
61-
if (ctor == null)
62-
throw new InvalidOperationException($"Bug: instanceType {instanceType.Name} does not have a 0-parameter accessible constructor.");
63-
64-
ilBuilder.Emit(OpCodes.Ldarg_0);
65-
ilBuilder.Emit(OpCodes.Newobj, ctor);
66-
ilBuilder.Emit(OpCodes.Stfld, field);
67-
}
68-
else
69-
{
70-
ilBuilder.Emit(OpCodes.Ldarg_0);
71-
ilBuilder.Emit(OpCodes.Ldnull);
72-
ilBuilder.Emit(OpCodes.Stfld, field);
73-
}
74-
}
75-
7645
public static void EmitSetDelegateToThisField(
7746
this ILGenerator ilBuilder,
7847
FieldBuilder delegateField,

src/BenchmarkDotNet/Templates/BenchmarkType.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@
5757

5858
public Runnable_$ID$()
5959
{
60-
awaitHelper = new BenchmarkDotNet.Helpers.AwaitHelper();
61-
6260
globalSetupAction = $GlobalSetupMethodName$;
6361
globalCleanupAction = $GlobalCleanupMethodName$;
6462
iterationSetupAction = $IterationSetupMethodName$;
@@ -68,8 +66,6 @@
6866
$InitializeArgumentFields$
6967
}
7068

71-
private readonly BenchmarkDotNet.Helpers.AwaitHelper awaitHelper;
72-
7369
private System.Action globalSetupAction;
7470
private System.Action globalCleanupAction;
7571
private System.Action iterationSetupAction;

src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,6 @@ private static void EmitNoArgsMethodCallPopReturn(
246246
private ConsumableTypeInfo consumableInfo;
247247
private ConsumeEmitter consumeEmitter;
248248

249-
private FieldBuilder awaitHelperField;
250249
private FieldBuilder globalSetupActionField;
251250
private FieldBuilder globalCleanupActionField;
252251
private FieldBuilder iterationSetupActionField;
@@ -413,8 +412,6 @@ private Type EmitWorkloadDelegateType()
413412

414413
private void DefineFields()
415414
{
416-
awaitHelperField =
417-
runnableBuilder.DefineField(AwaitHelperFieldName, typeof(Helpers.AwaitHelper), FieldAttributes.Private | FieldAttributes.InitOnly);
418415
globalSetupActionField =
419416
runnableBuilder.DefineField(GlobalSetupActionFieldName, typeof(Action), FieldAttributes.Private);
420417
globalCleanupActionField =
@@ -587,13 +584,6 @@ private MethodInfo EmitWorkloadImplementation(string methodName)
587584

588585
var ilBuilder = methodBuilder.GetILGenerator();
589586

590-
/*
591-
IL_0007: ldarg.0
592-
IL_0008: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper
593-
*/
594-
ilBuilder.Emit(OpCodes.Ldarg_0);
595-
ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField);
596-
597587
/*
598588
IL_0026: ldarg.0
599589
IL_0027: ldloc.0
@@ -608,11 +598,11 @@ private MethodInfo EmitWorkloadImplementation(string methodName)
608598
ilBuilder.Emit(OpCodes.Call, Descriptor.WorkloadMethod);
609599

610600
/*
611-
// awaitHelper.GetResult(...);
612-
IL_000e: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task)
601+
// BenchmarkDotNet.Helpers.AwaitHelper.GetResult(...);
602+
IL_000e: call !!0 BenchmarkDotNet.Helpers.AwaitHelper::GetResult<int32>(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<!!0>)
613603
*/
614604

615-
ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod);
605+
ilBuilder.Emit(OpCodes.Call, consumableInfo.GetResultMethod);
616606

617607
/*
618608
IL_0014: ret
@@ -850,16 +840,6 @@ .locals init (
850840
*/
851841
EmitLoadArgFieldsToLocals(ilBuilder, argLocals, skipFirstArg);
852842

853-
if (consumableInfo.IsAwaitable)
854-
{
855-
/*
856-
IL_0026: ldarg.0
857-
IL_0027: ldfld class BenchmarkDotNet.Helpers.AwaitHelper BenchmarkDotNet.Helpers.Runnable_0::awaitHelper
858-
*/
859-
ilBuilder.Emit(OpCodes.Ldarg_0);
860-
ilBuilder.Emit(OpCodes.Ldfld, awaitHelperField);
861-
}
862-
863843
/*
864844
IL_0026: ldarg.0
865845
IL_0027: ldloc.0
@@ -878,10 +858,10 @@ .locals init (
878858
if (consumableInfo.IsAwaitable)
879859
{
880860
/*
881-
// awaitHelper.GetResult(...);
882-
IL_0036: callvirt instance void BenchmarkDotNet.Helpers.AwaitHelper::GetResult(class [System.Private.CoreLib]System.Threading.Tasks.Task)
861+
// BenchmarkDotNet.Helpers.AwaitHelper.GetResult(...);
862+
IL_000e: call !!0 BenchmarkDotNet.Helpers.AwaitHelper::GetResult<int32>(valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<!!0>)
883863
*/
884-
ilBuilder.Emit(OpCodes.Callvirt, consumableInfo.GetResultMethod);
864+
ilBuilder.Emit(OpCodes.Call, consumableInfo.GetResultMethod);
885865
}
886866

887867
/*
@@ -944,7 +924,6 @@ private void EmitCtorBody()
944924

945925
consumeEmitter.OnEmitCtorBody(ctorMethod, ilBuilder);
946926

947-
ilBuilder.EmitSetFieldToNewInstance(awaitHelperField, typeof(Helpers.AwaitHelper));
948927
ilBuilder.EmitSetDelegateToThisField(globalSetupActionField, globalSetupMethod);
949928
ilBuilder.EmitSetDelegateToThisField(globalCleanupActionField, globalCleanupMethod);
950929
ilBuilder.EmitSetDelegateToThisField(iterationSetupActionField, iterationSetupMethod);

src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Runnable/RunnableConstants.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ public class RunnableConstants
1616
public const string ArgFieldPrefix = "__argField";
1717
public const string ArgParamPrefix = "arg";
1818

19-
public const string AwaitHelperFieldName = "awaitHelper";
2019
public const string GlobalSetupActionFieldName = "globalSetupAction";
2120
public const string GlobalCleanupActionFieldName = "globalCleanupAction";
2221
public const string IterationSetupActionFieldName = "iterationSetupAction";

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ private void InvokeMultipleHardcoded(long repeatCount)
6969

7070
internal class BenchmarkActionTask : BenchmarkActionBase
7171
{
72-
private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper();
7372
private readonly Func<Task> startTaskCallback;
7473
private readonly Action callback;
7574
private readonly Action unrolledCallback;
@@ -98,7 +97,7 @@ public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor)
9897
private void Overhead() { }
9998

10099
// must be kept in sync with TaskDeclarationsProvider.TargetMethodDelegate
101-
private void ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke());
100+
private void ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke());
102101

103102
private void InvokeMultipleHardcoded(long repeatCount)
104103
{
@@ -109,7 +108,6 @@ private void InvokeMultipleHardcoded(long repeatCount)
109108

110109
internal class BenchmarkActionTask<T> : BenchmarkActionBase
111110
{
112-
private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper();
113111
private readonly Func<Task<T>> startTaskCallback;
114112
private readonly Func<T> callback;
115113
private readonly Func<T> unrolledCallback;
@@ -137,7 +135,7 @@ public BenchmarkActionTask(object instance, MethodInfo method, int unrollFactor)
137135
private T Overhead() => default;
138136

139137
// must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate
140-
private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke());
138+
private T ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke());
141139

142140
private void InvokeSingleHardcoded() => result = callback();
143141

@@ -152,7 +150,6 @@ private void InvokeMultipleHardcoded(long repeatCount)
152150

153151
internal class BenchmarkActionValueTask<T> : BenchmarkActionBase
154152
{
155-
private readonly Helpers.AwaitHelper awaitHelper = new Helpers.AwaitHelper();
156153
private readonly Func<ValueTask<T>> startTaskCallback;
157154
private readonly Func<T> callback;
158155
private readonly Func<T> unrolledCallback;
@@ -181,7 +178,7 @@ public BenchmarkActionValueTask(object instance, MethodInfo method, int unrollFa
181178
private T Overhead() => default;
182179

183180
// must be kept in sync with GenericTaskDeclarationsProvider.TargetMethodDelegate
184-
private T ExecuteBlocking() => awaitHelper.GetResult(startTaskCallback.Invoke());
181+
private T ExecuteBlocking() => Helpers.AwaitHelper.GetResult(startTaskCallback.Invoke());
185182

186183
private void InvokeSingleHardcoded() => result = callback();
187184

0 commit comments

Comments
 (0)