You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Similar to #75196, but this time focusing on awaiters rather than async method builders.
Consider ValueTask<TResult> that stores the TResult _result in the struct, so that synchronous value retrieval can occur without allocating on the heap. ValueTaskAwaiter<TResult> has a ValueTask<TResult> field, so it can do the same thing. Currently, async state machines store the entire awaiter, whether it completes synchronously or asynchronously. If the ValueTask<TResult> was pending by the time it was awaited, that's wasted space being stored in the state machine. When the async continuation occurs, the result is retrieved from the backing heap object, not from the result stored in the awaiter.
The compiler might recognize a new pattern: public AsyncResultGetterType GetAsyncResultGetter(). When this method exists on an awaiter, the compiler may use that to store on the state machine instead of the awaiter itself.
The async result getter type must have a GetResult method, which can have the shape of void GetResult() for void values, T GetResult() for T values if the type is generic, or T GetResult<T>(). That last one is new and not available for existing awaiters. The idea is that the async value will be retrieved from a heap object, and only 1 awaiter is used at a time, so the state machine can be optimized by re-using a single async result getter type for multiple awaiter types. For example, await FuncAsync<object>(); await FuncAsync<int>();.
Here are benchmark results comparing the current async state machines with the new optimized version:
Method
Mean
Error
StdDev
Code Size
Allocated
Current
290.5 ns
1.11 ns
1.04 ns
3,068 B
248 B
New
240.4 ns
2.12 ns
1.77 ns
2,911 B
168 B
Code
[MemoryDiagnoser(false)][DisassemblyDiagnoser(100)]publicclassAsyncStateMachineBenchmarks{privatereadonlyMyValueTaskSource<object>_source1=newMyValueTaskSource<object>();privatereadonlyMyValueTaskSource<Matrix4x4>_source2=newMyValueTaskSource<Matrix4x4>();[Benchmark]publicvoidCurrent(){_= AsyncFuncCurrent();
_source1._continuation.Invoke();
_source1._continuation =null;
_source2._continuation.Invoke();
_source2._continuation =null;}[Benchmark]publicvoidNew(){_= AsyncFuncNew();
_source1._continuation.Invoke();
_source1._continuation =null;
_source2._continuation.Invoke();
_source2._continuation =null;}//private async Task AsyncFunc()//{// _ = await new MyValueTask<object>(_source1);// _ = await new MyValueTask<Matrix4x4>(_source2);//}[StructLayout(LayoutKind.Auto)]privatestructAsyncFuncCurrentStateMachine:IAsyncStateMachine{publicint__state;publicAsyncTaskMethodBuildert__builder;publicAsyncStateMachineBenchmarks__this;privateMyValueTaskAwaiter<object>u__1;privateMyValueTaskAwaiter<Matrix4x4>u__2;privatevoidMoveNext(){intnum= __state;try{MyValueTaskAwaiter<Matrix4x4>awaiter;MyValueTaskAwaiter<object>awaiter2;if(num!=0){if(num==1){awaiter=u__2;u__2=default;num=(__state=-1);goto IL_00be;}awaiter2=newMyValueTask<object>(__this._source1).GetAwaiter();if(!awaiter2.IsCompleted){num=(__state=0);u__1=awaiter2;
t__builder.AwaitUnsafeOnCompleted(ref awaiter2,refthis);return;}}else{awaiter2=u__1;u__1=default;num=(__state=-1);}
awaiter2.GetResult();awaiter=newMyValueTask<Matrix4x4>(__this._source2).GetAwaiter();if(!awaiter.IsCompleted){num=(__state=1);u__2=awaiter;
t__builder.AwaitUnsafeOnCompleted(ref awaiter,refthis);return;}goto IL_00be;
IL_00be:
awaiter.GetResult();}catch(Exceptionexception){__state=-2;
t__builder.SetException(exception);return;}__state=-2;
t__builder.SetResult();}void IAsyncStateMachine.MoveNext(){//ILSpy generated this explicit interface implementation from .override directive in MoveNextthis.MoveNext();}privatevoidSetStateMachine(IAsyncStateMachinestateMachine){
t__builder.SetStateMachine(stateMachine);}void IAsyncStateMachine.SetStateMachine(IAsyncStateMachinestateMachine){//ILSpy generated this explicit interface implementation from .override directive in SetStateMachinethis.SetStateMachine(stateMachine);}}private Task AsyncFuncCurrent(){AsyncFuncCurrentStateMachinestateMachine=default;
stateMachine.t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.__this =this;
stateMachine.__state =-1;
stateMachine.t__builder.Start(ref stateMachine);return stateMachine.t__builder.Task;}[StructLayout(LayoutKind.Auto)]privatestructAsyncFuncNewStateMachine:IAsyncStateMachine{publicint__state;publicAsyncTaskMethodBuildert__builder;publicAsyncStateMachineBenchmarks__this;privateMyValueTaskResultGetteru__1;privatevoidMoveNext(){intnum= __state;try{MyValueTaskResultGetterresultGetter;if(num!=0){if(num==1){resultGetter=u__1;u__1=default;num=(__state=-1);
resultGetter.GetResult<Matrix4x4>();goto IL_00be;}varawaiter1=newMyValueTask<object>(__this._source1).GetAwaiter();if(!awaiter1.IsCompleted){num=(__state=0);u__1= awaiter1.GetAsyncResultGetter();
t__builder.AwaitUnsafeOnCompleted(ref awaiter1,refthis);return;}
awaiter1.GetResult();}else{resultGetter=u__1;u__1=default;num=(__state=-1);
resultGetter.GetResult<object>();}varawaiter2=newMyValueTask<Matrix4x4>(__this._source2).GetAwaiter();if(!awaiter2.IsCompleted){num=(__state=1);u__1= awaiter2.GetAsyncResultGetter();
t__builder.AwaitUnsafeOnCompleted(ref awaiter2,refthis);return;}
awaiter2.GetResult();goto IL_00be;
IL_00be:;}catch(Exceptionexception){__state=-2;
t__builder.SetException(exception);return;}__state=-2;
t__builder.SetResult();}void IAsyncStateMachine.MoveNext(){//ILSpy generated this explicit interface implementation from .override directive in MoveNextthis.MoveNext();}privatevoidSetStateMachine(IAsyncStateMachinestateMachine){
t__builder.SetStateMachine(stateMachine);}void IAsyncStateMachine.SetStateMachine(IAsyncStateMachinestateMachine){//ILSpy generated this explicit interface implementation from .override directive in SetStateMachinethis.SetStateMachine(stateMachine);}}private Task AsyncFuncNew(){AsyncFuncNewStateMachinestateMachine=default;
stateMachine.t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.__this =this;
stateMachine.__state =-1;
stateMachine.t__builder.Start(ref stateMachine);return stateMachine.t__builder.Task;}}publicreadonlystructMyValueTask<T>{internalreadonlyMyValueTaskSource<T>_source;internalreadonlyT_completedResult;publicMyValueTask(TcompletedResult){_source=null;_completedResult=completedResult;}internalMyValueTask(MyValueTaskSource<T>source){_source=source;_completedResult=default;}publicMyValueTaskAwaiter<T>GetAwaiter()=>newMyValueTaskAwaiter<T>(this);}internalabstractclassMyValueTaskSourceBase{}internalsealedclassMyValueTaskSource<T>:MyValueTaskSourceBase{internalAction_continuation;internalT_result;}publicreadonlystructMyValueTaskAwaiter<T>:ICriticalNotifyCompletion{privatereadonlyMyValueTask<T>_task;internalMyValueTaskAwaiter(MyValueTask<T>task){_task=task;}publicboolIsCompleted=> _task._source isnull;public T GetResult()=> _task._source isnull? _task._completedResult
: _task._source._result;publicvoidOnCompleted(Actioncontinuation)=> _task._source._continuation =continuation;publicvoidUnsafeOnCompleted(Actioncontinuation)=> OnCompleted(continuation);public MyValueTaskResultGetter GetAsyncResultGetter()=>new MyValueTaskResultGetter(_task._source);}publicreadonlystructMyValueTaskResultGetter{privatereadonlyMyValueTaskSourceBase_source;internalMyValueTaskResultGetter(MyValueTaskSourceBasesource){_source=source;}publicvoidGetResult(){}public TResult GetResult<TResult>()=> Unsafe.As<MyValueTaskSource<TResult>>(_source)._result;}
The text was updated successfully, but these errors were encountered:
Similar to #75196, but this time focusing on awaiters rather than async method builders.
Consider
ValueTask<TResult>
that stores theTResult _result
in the struct, so that synchronous value retrieval can occur without allocating on the heap.ValueTaskAwaiter<TResult>
has aValueTask<TResult>
field, so it can do the same thing. Currently, async state machines store the entire awaiter, whether it completes synchronously or asynchronously. If theValueTask<TResult>
was pending by the time it was awaited, that's wasted space being stored in the state machine. When the async continuation occurs, the result is retrieved from the backing heap object, not from the result stored in the awaiter.The compiler might recognize a new pattern:
public AsyncResultGetterType GetAsyncResultGetter()
. When this method exists on an awaiter, the compiler may use that to store on the state machine instead of the awaiter itself.The async result getter type must have a
GetResult
method, which can have the shape ofvoid GetResult()
for void values,T GetResult()
forT
values if the type is generic, orT GetResult<T>()
. That last one is new and not available for existing awaiters. The idea is that the async value will be retrieved from a heap object, and only 1 awaiter is used at a time, so the state machine can be optimized by re-using a single async result getter type for multiple awaiter types. For example,await FuncAsync<object>(); await FuncAsync<int>();
.Here are benchmark results comparing the current async state machines with the new optimized version:
Code
The text was updated successfully, but these errors were encountered: