From f0ff6ebec33fae1c310a0f1b73095e7ae646e154 Mon Sep 17 00:00:00 2001 From: heku Date: Wed, 12 Jun 2024 21:31:44 +0800 Subject: [PATCH] Optimize for Task, ValueTask cases --- .../AsyncInterceptorTests.cs | 16 +++--- .../AsyncAdapter.Factory.cs | 2 - .../AsyncAdapters/AsyncAdapterOfTask.cs | 40 +++++++++++--- .../AsyncAdapters/AsyncAdapterOfValueTask.cs | 40 +++++++++++--- Kunet.AsyncInterceptor/AsyncInterceptor.cs | 26 ++++++++- Kunet.AsyncInterceptor/AsyncStateMachine.cs | 27 ++++++---- Kunet.AsyncInterceptor/AsyncStateMachine`.cs | 53 +++++++++++++++++++ 7 files changed, 165 insertions(+), 39 deletions(-) create mode 100644 Kunet.AsyncInterceptor/AsyncStateMachine`.cs diff --git a/Kunet.AsyncInterceptor.PerfTests/AsyncInterceptorTests.cs b/Kunet.AsyncInterceptor.PerfTests/AsyncInterceptorTests.cs index 830fd83..36352a7 100644 --- a/Kunet.AsyncInterceptor.PerfTests/AsyncInterceptorTests.cs +++ b/Kunet.AsyncInterceptor.PerfTests/AsyncInterceptorTests.cs @@ -114,18 +114,18 @@ public ValueTask KunetRunningValueTaskT() //| KunetRunningValueTaskT | ShortRun-.NET 8.0 | .NET 8.0 | RunningValueTask | 436.08 ns | 0.0725 | 1216 B | //| StakxRunningValueTaskT | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | RunningValueTask | 7,132.29 ns | 0.5951 | 3755 B | //| KunetRunningValueTaskT | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | RunningValueTask | 1,564.72 ns | 0.3147 | 1982 B | -//| StakxTask | ShortRun-.NET 8.0 | .NET 8.0 | Task | 940.71 ns | 0.0448 | 752 B | -//| KunetTask | ShortRun-.NET 8.0 | .NET 8.0 | Task | 77.63 ns | 0.0196 | 328 B | -//| StakxTask | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | Task | 3,320.81 ns | 0.1793 | 1140 B | -//| KunetTask | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | Task | 166.57 ns | 0.0560 | 353 B | +//| StakxTask | ShortRun-.NET 8.0 | .NET 8.0 | Task | 901.41 ns | 0.0448 | 752 B | +//| KunetTask | ShortRun-.NET 8.0 | .NET 8.0 | Task | 57.36 ns | 0.0157 | 264 B | +//| StakxTask | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | Task | 3,259.66 ns | 0.1793 | 1140 B | +//| KunetTask | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | Task | 112.87 ns | 0.0459 | 289 B | //| StakxTaskT | ShortRun-.NET 8.0 | .NET 8.0 | Task | 1,352.91 ns | 0.0668 | 1144 B | //| KunetTaskT | ShortRun-.NET 8.0 | .NET 8.0 | Task | 168.32 ns | 0.0348 | 584 B | //| StakxTaskT | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | Task | 4,475.84 ns | 0.3281 | 2071 B | //| KunetTaskT | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | Task | 601.14 ns | 0.1516 | 955 B | -//| StakxValueTask | ShortRun-.NET 8.0 | .NET 8.0 | ValueTask | 1,832.72 ns | 0.0629 | 1080 B | -//| KunetValueTask | ShortRun-.NET 8.0 | .NET 8.0 | ValueTask | 86.66 ns | 0.0234 | 392 B | -//| StakxValueTask | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | ValueTask | 3,652.26 ns | 0.2022 | 1276 B | -//| KunetValueTask | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | ValueTask | 162.22 ns | 0.0675 | 425 B | +//| StakxValueTask | ShortRun-.NET 8.0 | .NET 8.0 | ValueTask | 1,740.57 ns | 0.0629 | 1080 B | +//| KunetValueTask | ShortRun-.NET 8.0 | .NET 8.0 | ValueTask | 60.98 ns | 0.0196 | 328 B | +//| StakxValueTask | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | ValueTask | 3,711.97 ns | 0.2022 | 1276 B | +//| KunetValueTask | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | ValueTask | 124.07 ns | 0.0572 | 361 B | //| StakxValueTaskT | ShortRun-.NET 8.0 | .NET 8.0 | ValueTask | 2,110.97 ns | 0.0877 | 1472 B | //| KunetValueTaskT | ShortRun-.NET 8.0 | .NET 8.0 | ValueTask | 187.48 ns | 0.0391 | 656 B | //| StakxValueTaskT | ShortRun-.NET Framework 4.8.1 | .NET Framework 4.8.1 | ValueTask | 4,636.58 ns | 0.3204 | 2055 B | diff --git a/Kunet.AsyncInterceptor/AsyncAdapter.Factory.cs b/Kunet.AsyncInterceptor/AsyncAdapter.Factory.cs index ed2443d..cb2d6d3 100644 --- a/Kunet.AsyncInterceptor/AsyncAdapter.Factory.cs +++ b/Kunet.AsyncInterceptor/AsyncAdapter.Factory.cs @@ -15,8 +15,6 @@ public partial class AsyncAdapter static AsyncAdapter() { - Register(x => new AsyncAdapterOfTask(x)); // Task - Register(x => new AsyncAdapterOfValueTask(x)); // ValueTask Register(typeof(Task<>), typeof(AsyncAdapterOfTask<>)); // Task Register(typeof(ValueTask<>), typeof(AsyncAdapterOfValueTask<>)); // ValueTask } diff --git a/Kunet.AsyncInterceptor/AsyncAdapters/AsyncAdapterOfTask.cs b/Kunet.AsyncInterceptor/AsyncAdapters/AsyncAdapterOfTask.cs index 45e5586..78018f8 100644 --- a/Kunet.AsyncInterceptor/AsyncAdapters/AsyncAdapterOfTask.cs +++ b/Kunet.AsyncInterceptor/AsyncAdapters/AsyncAdapterOfTask.cs @@ -1,25 +1,49 @@ using Castle.DynamicProxy; using System; +using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading.Tasks; namespace Kunet.AsyncInterceptor; -internal sealed class AsyncAdapterOfTask : AsyncAdapter +[StructLayout(LayoutKind.Auto)] +internal struct AsyncAdapterOfTask : IAsyncInvocation, IAsyncAdapter { + private readonly IInvocationProceedInfo _proceed; private readonly AsyncTaskMethodBuilder _builder = AsyncTaskMethodBuilder.Create(); - public AsyncAdapterOfTask(IInvocation invocation) : base(invocation) => Task = _builder.Task; + public AsyncAdapterOfTask(IInvocation invocation) + { + _proceed = invocation.CaptureProceedInfo(); + Invocation = invocation; + Task = _builder.Task; + } - protected override ValueTask SetAsyncResult() => new((Task)Invocation.ReturnValue); + public IInvocation Invocation { get; } - public override object Task { get; } + public object AsyncResult { get; set; } - public override void Start(ref TStateMachine stateMachine) => _builder.Start(ref stateMachine); + public object Task { get; } - public override void SetResult(object result) => _builder.SetResult(); + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine => _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); - public override void SetException(Exception exception) => _builder.SetException(exception); + public ValueTask ProceedAsync() + { + _proceed.Invoke(); // Invocation.ReturnValue = NEXT() + if (Invocation.ReturnValue is not null) + { + Debug.Assert(Invocation.ReturnValue is Task); + return new((Task)Invocation.ReturnValue); // AsyncResult = await Invocation.ReturnValue + } + return default; + } - public override void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) => _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + public void SetException(Exception exception) => _builder.SetException(exception); + + public void SetResult(object result) => _builder.SetResult(); + + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => _builder.Start(ref stateMachine); } \ No newline at end of file diff --git a/Kunet.AsyncInterceptor/AsyncAdapters/AsyncAdapterOfValueTask.cs b/Kunet.AsyncInterceptor/AsyncAdapters/AsyncAdapterOfValueTask.cs index ebc7d7b..a251b5a 100644 --- a/Kunet.AsyncInterceptor/AsyncAdapters/AsyncAdapterOfValueTask.cs +++ b/Kunet.AsyncInterceptor/AsyncAdapters/AsyncAdapterOfValueTask.cs @@ -1,25 +1,49 @@ using Castle.DynamicProxy; using System; +using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading.Tasks; namespace Kunet.AsyncInterceptor; -internal sealed class AsyncAdapterOfValueTask : AsyncAdapter +[StructLayout(LayoutKind.Auto)] +internal struct AsyncAdapterOfValueTask : IAsyncInvocation, IAsyncAdapter { + private readonly IInvocationProceedInfo _proceed; private readonly AsyncValueTaskMethodBuilder _builder = AsyncValueTaskMethodBuilder.Create(); - public AsyncAdapterOfValueTask(IInvocation invocation) : base(invocation) => Task = _builder.Task; + public AsyncAdapterOfValueTask(IInvocation invocation) + { + _proceed = invocation.CaptureProceedInfo(); + Invocation = invocation; + Task = _builder.Task; + } - protected override ValueTask SetAsyncResult() => (ValueTask)Invocation.ReturnValue; + public IInvocation Invocation { get; } - public override object Task { get; } + public object AsyncResult { get; set; } - public override void Start(ref TStateMachine stateMachine) => _builder.Start(ref stateMachine); + public object Task { get; } - public override void SetResult(object result) => _builder.SetResult(); + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine => _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); - public override void SetException(Exception exception) => _builder.SetException(exception); + public ValueTask ProceedAsync() + { + _proceed.Invoke(); // Invocation.ReturnValue = NEXT() + if (Invocation.ReturnValue is not null) + { + Debug.Assert(Invocation.ReturnValue is ValueTask); + return (ValueTask)Invocation.ReturnValue; // AsyncResult = await Invocation.ReturnValue + } + return default; + } - public override void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) => _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + public void SetException(Exception exception) => _builder.SetException(exception); + + public void SetResult(object result) => _builder.SetResult(); + + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => _builder.Start(ref stateMachine); } \ No newline at end of file diff --git a/Kunet.AsyncInterceptor/AsyncInterceptor.cs b/Kunet.AsyncInterceptor/AsyncInterceptor.cs index 907ea94..64bb0c3 100644 --- a/Kunet.AsyncInterceptor/AsyncInterceptor.cs +++ b/Kunet.AsyncInterceptor/AsyncInterceptor.cs @@ -1,5 +1,6 @@ using Castle.DynamicProxy; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Kunet.AsyncInterceptor; @@ -10,9 +11,26 @@ public abstract class AsyncInterceptor : IInterceptor { void IInterceptor.Intercept(IInvocation invocation) { - if (AsyncAdapter.TryCreate(invocation, out var adapter)) + var returnType = invocation.Method.ReturnType; + if (returnType == typeof(Task)) { - AsyncStateMachine stateMachine = new(adapter, InterceptAsync); + var adapter = new AsyncAdapterOfTask(invocation); + var stateMachine = new AsyncStateMachine(in adapter, this); + adapter.Start(ref stateMachine); + Debug.Assert(adapter.Task is not null); + invocation.ReturnValue = adapter.Task; + } + else if (returnType == typeof(ValueTask)) + { + var adapter = new AsyncAdapterOfValueTask(invocation); + var stateMachine = new AsyncStateMachine(in adapter, this); + adapter.Start(ref stateMachine); + Debug.Assert(adapter.Task is not null); + invocation.ReturnValue = adapter.Task; + } + else if (AsyncAdapter.TryCreate(invocation, out var adapter)) + { + AsyncStateMachine stateMachine = new(adapter, this); adapter.Start(ref stateMachine); Debug.Assert(adapter.Task is not null); invocation.ReturnValue = adapter.Task; @@ -26,4 +44,8 @@ void IInterceptor.Intercept(IInvocation invocation) protected abstract void Intercept(IInvocation invocation); protected abstract ValueTask InterceptAsync(IAsyncInvocation invocation); + + // Compat. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ValueTask InternalInterceptAsync(IAsyncInvocation invocation) => InterceptAsync(invocation); } \ No newline at end of file diff --git a/Kunet.AsyncInterceptor/AsyncStateMachine.cs b/Kunet.AsyncInterceptor/AsyncStateMachine.cs index 975a82b..5641290 100644 --- a/Kunet.AsyncInterceptor/AsyncStateMachine.cs +++ b/Kunet.AsyncInterceptor/AsyncStateMachine.cs @@ -1,36 +1,40 @@ using System; using System.Runtime.CompilerServices; -using System.Threading.Tasks; +using System.Runtime.InteropServices; namespace Kunet.AsyncInterceptor; +[StructLayout(LayoutKind.Auto)] internal struct AsyncStateMachine : IAsyncStateMachine { private readonly AsyncAdapter _adapter; - private readonly Func _interceptAsync; - private ValueTask? _interceptingTask; + private readonly AsyncInterceptor _interceptor; + private bool _intercepting; + private ValueTaskAwaiter _interceptingAwaiter; - public AsyncStateMachine(AsyncAdapter adapter, Func interceptAsync) + public AsyncStateMachine(AsyncAdapter adapter, AsyncInterceptor interceptor) { _adapter = adapter; - _interceptAsync = interceptAsync; - _interceptingTask = null; + _interceptor = interceptor; } public void MoveNext() { try { - _interceptingTask ??= _interceptAsync(_adapter); - var awaiter = _interceptingTask.Value.GetAwaiter(); - if (awaiter.IsCompleted) + if (_intercepting is false) { - awaiter.GetResult(); // throw exception if there is. + _intercepting = true; + _interceptingAwaiter = _interceptor.InternalInterceptAsync(_adapter).GetAwaiter(); + } + if (_interceptingAwaiter.IsCompleted) + { + _interceptingAwaiter.GetResult(); // throw exception if there is. _adapter.SetResult(_adapter.AsyncResult); } else { - _adapter.AwaitUnsafeOnCompleted(ref awaiter, ref this); + _adapter.AwaitUnsafeOnCompleted(ref _interceptingAwaiter, ref this); } } catch (Exception ex) @@ -39,6 +43,7 @@ public void MoveNext() } } + [Obsolete] public void SetStateMachine(IAsyncStateMachine stateMachine) { // SetStateMachine was originally needed in order to store the boxed state machine reference into the boxed copy. diff --git a/Kunet.AsyncInterceptor/AsyncStateMachine`.cs b/Kunet.AsyncInterceptor/AsyncStateMachine`.cs new file mode 100644 index 0000000..5b143ea --- /dev/null +++ b/Kunet.AsyncInterceptor/AsyncStateMachine`.cs @@ -0,0 +1,53 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Kunet.AsyncInterceptor; + +[StructLayout(LayoutKind.Auto)] +internal struct AsyncStateMachine : IAsyncStateMachine where T : IAsyncAdapter +{ + private readonly T _adapter; + private readonly AsyncInterceptor _interceptor; + private bool _intercepting; + private ValueTaskAwaiter _interceptingAwaiter; + + public AsyncStateMachine(in T adapter, AsyncInterceptor interceptor) + { + _adapter = adapter; + _interceptor = interceptor; + } + + public void MoveNext() + { + try + { + if (_intercepting is false) + { + _intercepting = true; + _interceptingAwaiter = _interceptor.InternalInterceptAsync(_adapter).GetAwaiter(); + } + if (_interceptingAwaiter.IsCompleted) + { + _interceptingAwaiter.GetResult(); // throw exception if there is. + _adapter.SetResult(_adapter.AsyncResult); + } + else + { + _adapter.AwaitUnsafeOnCompleted(ref _interceptingAwaiter, ref this); + } + } + catch (Exception ex) + { + _adapter.SetException(ex); + } + } + + [Obsolete] + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + // SetStateMachine was originally needed in order to store the boxed state machine reference into the boxed copy. + // Now that a normal box is no longer used, SetStateMachine is also legacy. We need not do anything here. + // https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs,70528b49b7e9916f + } +} \ No newline at end of file