-
-
Notifications
You must be signed in to change notification settings - Fork 984
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
Reduce async overhead #2111
base: master
Are you sure you want to change the base?
Reduce async overhead #2111
Conversation
10fb32f
to
4dd871f
Compare
4dd871f
to
cd127e0
Compare
cd127e0
to
fe4dec6
Compare
Force async unroll factor to 1. Support async IterationSetup/IterationCleanup.
fe4dec6
to
9a6c74f
Compare
@YegorStepanov Moving the conversation here since it's off topic for #2114.
Aye, I thought about supporting awaitable types other than And none of that has anything to do with the runtime, that's all compiler/dynamic code/DI stuff. |
Like I did #2114:
Let |
Eh... that's 2 boxes (UniTask and its awaiter are structs), and a 3rd box if the result is a struct. I suppose that's acceptable as long as it's not done in the workload method.
This would not work for awaitable extensions.
Do-able, but still pretty heavy (I mentioned boxing above). Technically, once this PR is merged, there will be no need to add custom awaitable support for
I mean, if everyone is okay with that, I'd be willing to do the work to add it to the toolchains that it can work in. But awaitable extensions still won't work, unless we provide a way for the user to tell us what type the extensions are in. And anyway, neither of those toolchains are usable in Unity's IL2CPP currently. We'd need a new Unity toolchain (#1860), or a new source-gen toolchain (#1770). |
We can calculate the boxing size and subtract it from the result.
I forgot about it. I remembered how someone made We need
|
We can't calculate the extra time that the boxing and reflection will consume, though. But that shouldn't matter anyway if we're not doing that for workload methods (we don't measure setup/cleanup methods in the diagnosers).
What if there are multiple extensions for the same type? We might pick the wrong one. Also, user may not want a return value to be awaited, only consumed normally (like your
IL2CPP is used on many platforms. Required on WebGL, optional on WindowsStandalone.
Aye |
What is a niche case that needs to be handled.
I'm not talking about Unity. |
If we do awaitable interface forwarding, it could look something like this: Details
public interface IForwardAwaitable<TAwaitable, TAwaiter>
{
TAwaiter GetAwaiter(ref TAwaitable awaitable);
bool IsCompleted(ref TAwaiter awaiter);
void OnCompleted(ref TAwaiter awaiter, Action continuation);
void GetResult(ref TAwaiter awaiter);
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class AwaitReturnAttribute : Attribute
{
public Type ForwardAwaitableType { get; }
public AwaitReturnAttribute(Type forwardAwaitableType)
{
ForwardAwaitableType = forwardAwaitableType;
}
} With example: public struct UniTaskForwardAwait : IForwardAwaitable<UniTask, UniTask.Awaiter>
{
public UniTask.Awaiter GetAwaiter(ref UniTask awaitable) => awaitable.GetAwaiter();
public void GetResult(ref UniTask.Awaiter awaiter) => awaiter.GetResult();
public bool IsCompleted(ref UniTask.Awaiter awaiter) => awaiter.IsCompleted;
public void OnCompleted(ref UniTask.Awaiter awaiter, Action continuation) => awaiter.UnsafeOnCompleted(continuation);
}
public struct UniTaskForwardAwait<T> : IForwardAwaitable<UniTask<T>, UniTask<T>.Awaiter>
{
public UniTask<T>.Awaiter GetAwaiter(ref UniTask<T> awaitable) => awaitable.GetAwaiter();
public void GetResult(ref UniTask<T>.Awaiter awaiter) => awaiter.GetResult();
public bool IsCompleted(ref UniTask<T>.Awaiter awaiter) => awaiter.IsCompleted;
public void OnCompleted(ref UniTask<T>.Awaiter awaiter, Action continuation) => awaiter.UnsafeOnCompleted(continuation);
}
public class AsyncBenchmark
{
[Benchmark]
[AwaitReturn(typeof(UniTaskForwardAwait))]
public async UniTask AsyncUniTask()
{
// ...
}
[Benchmark]
[AwaitReturn(typeof(UniTaskForwardAwait<int>))]
public async UniTask<int> AsyncUniTaskInt()
{
// ...
}
[Benchmark]
public async Task AsyncTask()
{
// ...
}
[Benchmark]
public async Task<int> AsyncTaskInt()
{
// ...
}
} [Edit] Or a slightly simpler form: public interface IForwardAwaitable<TAwaitable>
{
void GetAndStoreAwaiter(ref TAwaitable awaitable);
bool IsCompleted { get; }
void OnCompleted(Action continuation);
void GetResult();
} public struct UniTaskForwardAwait : IForwardAwaitable<UniTask>
{
private UniTask.Awaiter awaiter;
public void GetAndStoreAwaiter(ref UniTask awaitable) => awaiter = awaitable.GetAwaiter();
public bool IsCompleted => awaiter.IsCompleted;
public void GetResult() => awaiter.GetResult();
public void OnCompleted(Action continuation) => awaiter.UnsafeOnCompleted(continuation);
} This would work for extensions and |
# Conflicts: # tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs
…ead-new # Conflicts: # tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs
It seems to me that your solution can do a source generator (this is not related to When the user writes: [Benchmark] public UniTask Method();
|
Well yeah, like I said it's easy for source gen and IL emit, but that would be required by the user for |
Anyway, I don't think await extensions are really being used. Especially if there are multiple extensions. Such cases can be resolved by an analyser or validator:
|
Well, would you like to open a new issue for it? I don't want to include it in this PR. I can do a followup PR for it if BDN authorities say the proposal is good. |
Oh, it's just discussion, not a suggestion for this PR. There is currently no need for such support for async. |
# Conflicts: # src/BenchmarkDotNet/Engines/IEngine.cs # src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/ConsumableTypeInfo.cs # src/BenchmarkDotNet/Toolchains/InProcess.Emit.Implementation/Emitters/RunnableEmitter.cs
# Conflicts: # src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkAction.cs # src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory.cs # src/BenchmarkDotNet/Toolchains/InProcess/BenchmarkActionFactory_Implementations.cs # src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/TaskConsumeEmitter.cs # src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs # tests/BenchmarkDotNet.IntegrationTests/InProcessTest.cs
Followup to #2108, (redo of #1968 with more tests).
Refactored delegates to reduce async measurement overhead (sync measurements are the same).
Master:
This PR:
Merging this PR will also make it possible to add async engine support in the future.
Also added IterationSetup/Cleanup async support (As part of the refactor, it was easier to add the support than not to (code re-use). I thought it could be useful, so I added tests for it also).