Skip to content

Commit b18086d

Browse files
committed
Fix workload continuer for synchronous continuations.
1 parent d246230 commit b18086d

7 files changed

Lines changed: 319 additions & 422 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using BenchmarkDotNet.Attributes;
2+
3+
namespace BenchmarkDotNet.Samples
4+
{
5+
public class IntroAsync
6+
{
7+
[Benchmark]
8+
public async Task Yield1()
9+
{
10+
await Task.Yield();
11+
}
12+
13+
[GlobalSetup(Target = nameof(Yield2))]
14+
public void Setup2()
15+
{
16+
SynchronizationContext.SetSynchronizationContext(null);
17+
}
18+
19+
[Benchmark]
20+
public async Task Yield2()
21+
{
22+
await Task.Yield();
23+
}
24+
25+
[Benchmark]
26+
public async Task Yield3()
27+
{
28+
SynchronizationContext.SetSynchronizationContext(null);
29+
await Task.Yield();
30+
}
31+
}
32+
}

src/BenchmarkDotNet/Code/DeclarationsProvider.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using BenchmarkDotNet.Attributes;
1+
using BenchmarkDotNet.Attributes;
22
using BenchmarkDotNet.Engines;
33
using BenchmarkDotNet.Environments;
44
using BenchmarkDotNet.Extensions;
@@ -177,7 +177,7 @@ internal class AsyncDeclarationsProvider(BenchmarkCase benchmark) : Declarations
177177
{
178178
public override string[] GetExtraFields() =>
179179
[
180-
$"public {typeof(WorkloadContinuerAndValueTaskSource).GetCorrectCSharpTypeName()} workloadContinuerAndValueTaskSource;",
180+
$"public {typeof(WorkloadValueTaskSource).GetCorrectCSharpTypeName()} workloadContinuerAndValueTaskSource;",
181181
$"public {typeof(IClock).GetCorrectCSharpTypeName()} clock;",
182182
"public long invokeCount;"
183183
];
@@ -224,7 +224,7 @@ protected override SmartStringBuilder ReplaceCore(SmartStringBuilder smartString
224224
this.__fieldsContainer.clock = clock;
225225
if (this.__fieldsContainer.workloadContinuerAndValueTaskSource == null)
226226
{
227-
this.__fieldsContainer.workloadContinuerAndValueTaskSource = new {{typeof(WorkloadContinuerAndValueTaskSource).GetCorrectCSharpTypeName()}}();
227+
this.__fieldsContainer.workloadContinuerAndValueTaskSource = new {{typeof(WorkloadValueTaskSource).GetCorrectCSharpTypeName()}}();
228228
this.__StartWorkload();
229229
}
230230
return this.__fieldsContainer.workloadContinuerAndValueTaskSource.Continue();
@@ -240,14 +240,12 @@ private async void __StartWorkload()
240240
{
241241
try
242242
{
243+
if (await this.__fieldsContainer.workloadContinuerAndValueTaskSource.GetIsComplete())
244+
{
245+
{{finalReturn}}
246+
}
243247
while (true)
244248
{
245-
await this.__fieldsContainer.workloadContinuerAndValueTaskSource;
246-
if (this.__fieldsContainer.workloadContinuerAndValueTaskSource.IsCompleted)
247-
{
248-
{{finalReturn}}
249-
}
250-
251249
{{typeof(StartedClock).GetCorrectCSharpTypeName()}} startedClock = {{typeof(ClockExtensions).GetCorrectCSharpTypeName()}}.Start(this.__fieldsContainer.clock);
252250
while (--this.__fieldsContainer.invokeCount >= 0)
253251
{
@@ -256,7 +254,10 @@ private async void __StartWorkload()
256254
unsafe { awaitable = {{workloadMethodCall}} }
257255
await awaitable;
258256
}
259-
this.__fieldsContainer.workloadContinuerAndValueTaskSource.SetResult(startedClock.GetElapsed());
257+
if (await this.__fieldsContainer.workloadContinuerAndValueTaskSource.SetResultAndGetIsComplete(startedClock.GetElapsed()))
258+
{
259+
{{finalReturn}}
260+
}
260261
}
261262
}
262263
catch (global::System.Exception e)

src/BenchmarkDotNet/Engines/WorkloadContinuerAndValueTaskSource.cs

Lines changed: 0 additions & 65 deletions
This file was deleted.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using JetBrains.Annotations;
2+
using Perfolizer.Horology;
3+
using System.ComponentModel;
4+
using System.Threading.Tasks.Sources;
5+
6+
namespace BenchmarkDotNet.Engines;
7+
8+
// This is used to prevent allocating a new async state machine on every benchmark iteration.
9+
[UsedImplicitly]
10+
[EditorBrowsable(EditorBrowsableState.Never)]
11+
public sealed class WorkloadValueTaskSource : IValueTaskSource<ClockSpan>, IValueTaskSource<bool>
12+
{
13+
private ManualResetValueTaskSourceCore<bool> continuerSource;
14+
private ManualResetValueTaskSourceCore<ClockSpan> clockSpanSource;
15+
16+
public ValueTask<ClockSpan> Continue()
17+
{
18+
clockSpanSource.Reset();
19+
continuerSource.SetResult(false);
20+
return new(this, clockSpanSource.Version);
21+
}
22+
23+
public ValueTask<bool> GetIsComplete()
24+
=> new(this, continuerSource.Version);
25+
26+
public void Complete()
27+
=> continuerSource.SetResult(true);
28+
29+
public ValueTask<bool> SetResultAndGetIsComplete(ClockSpan result)
30+
{
31+
continuerSource.Reset();
32+
clockSpanSource.SetResult(result);
33+
return GetIsComplete();
34+
}
35+
36+
public void SetException(Exception exception)
37+
=> clockSpanSource.SetException(exception);
38+
39+
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
40+
=> continuerSource.GetStatus(token);
41+
42+
void IValueTaskSource<bool>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
43+
=> continuerSource.OnCompleted(continuation, state, token, flags);
44+
45+
bool IValueTaskSource<bool>.GetResult(short token)
46+
=> continuerSource.GetResult(token);
47+
48+
ClockSpan IValueTaskSource<ClockSpan>.GetResult(short token)
49+
=> clockSpanSource.GetResult(token);
50+
51+
ValueTaskSourceStatus IValueTaskSource<ClockSpan>.GetStatus(short token)
52+
=> clockSpanSource.GetStatus(token);
53+
54+
void IValueTaskSource<ClockSpan>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
55+
=> clockSpanSource.OnCompleted(continuation, state, token, flags);
56+
}

0 commit comments

Comments
 (0)