-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Async-streams: Consider optimizing return logic of MoveNextAsync()
#31246
Labels
Area-Compilers
New Feature - Async Streams
Async Streams
Resolution-Fixed
The bug has been fixed and/or the requested behavior has been implemented
Milestone
Comments
FYI @stephentoub I tried a simple benchmark. Let me know what you think
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Sources;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace MyBenchmarks
{
[CoreJob]
public class ValueTaskBenchmarkCore
{
private MyValueTaskSource source = new MyValueTaskSource();
[Benchmark]
public void Interface()
{
var task = new ValueTask<bool>(source, source.token);
task.GetAwaiter().GetResult();
}
[Benchmark]
public void ManualCheck()
{
short token = source.token;
var promise = source.promise;
if (promise.GetStatus(token) == ValueTaskSourceStatus.Succeeded)
{
var task = new ValueTask<bool>(promise.GetResult(token));
task.GetAwaiter().GetResult();
return;
}
Thread.Sleep(10000);
throw null;
}
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<ValueTaskBenchmarkCore>();
}
}
public class MyValueTaskSource : IValueTaskSource<bool>
{
public ManualResetValueTaskSourceCore<bool> promise;
public short token;
public MyValueTaskSource()
{
promise = new ManualResetValueTaskSourceCore<bool>();
promise.Reset();
token = promise.Version;
promise.SetResult(true);
}
public bool GetResult(short token) => promise.GetResult(token);
public ValueTaskSourceStatus GetStatus(short token) => promise.GetStatus(token);
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
{
}
}
} |
I have a PR for the compiler to generate: ValueTask<bool> IAsyncEnumerator<string>.MoveNextAsync()
{
if (<>1__state == -2)
{
return default(ValueTask<bool>);
}
<>v__promiseOfValueOrEnd.Reset();
<Iter>d__1 stateMachine = this;
<>t__builder.MoveNext(ref stateMachine);
short version = <>v__promiseOfValueOrEnd.Version;
if (<>v__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded)
{
return new ValueTask<bool>(<>v__promiseOfValueOrEnd.GetResult(version));
}
return new ValueTask<bool>(this, version);
} |
Great. Thanks, @jcouv. The benchmark looks good, but I expect real-world results will actually be even better for the new version, for two reasons:
|
Yup, the benefits of the optimization are even more stark when also calling
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Sources;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
// dotnet run -c Release
namespace MyBenchmarks
{
[CoreJob]
public class ValueTaskBenchmarkCore
{
private MyValueTaskSource source = new MyValueTaskSource();
[Benchmark]
public void Interface()
{
var task = new ValueTask<bool>(source, source.token);
var awaiter = task.GetAwaiter();
_ = awaiter.IsCompleted;
awaiter.GetResult();
}
[Benchmark]
public void ManualCheck()
{
short token = source.token;
var promise = source.promise;
if (promise.GetStatus(token) == ValueTaskSourceStatus.Succeeded)
{
var task = new ValueTask<bool>(promise.GetResult(token));
var awaiter = task.GetAwaiter();
_ = awaiter.IsCompleted;
awaiter.GetResult();
return;
}
Thread.Sleep(10000);
throw null;
}
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<ValueTaskBenchmarkCore>();
}
}
public class MyValueTaskSource : IValueTaskSource<bool>
{
public ManualResetValueTaskSourceCore<bool> promise;
public short token;
public MyValueTaskSource()
{
promise = new ManualResetValueTaskSourceCore<bool>();
promise.Reset();
token = promise.Version;
promise.SetResult(true);
}
public bool GetResult(short token) => promise.GetResult(token);
public ValueTaskSourceStatus GetStatus(short token) => promise.GetStatus(token);
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
{
}
}
} |
jcouv
added
the
Resolution-Fixed
The bug has been fixed and/or the requested behavior has been implemented
label
Dec 18, 2018
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
Area-Compilers
New Feature - Async Streams
Async Streams
Resolution-Fixed
The bug has been fixed and/or the requested behavior has been implemented
Reported by @stephentoub
Right now I see this for MoveNextAsync:
That last line will force the resulting usage of the
ValueTask<bool>
to go through the underlying interface, even if there is already a yielded value available (which we expect to be common). That means that in such a case we’ll end up making two unnecessary interface calls:IsCompleted
andGetResult
. If we were to instead check theManualResetValueTaskSourceCore
to see if there’s already a value available, and if there is, return anew ValueTask<bool>(true)
, it would make that path faster. We should of course benchmark it, but I expect the savings will more than make up for the extra branch.The text was updated successfully, but these errors were encountered: