-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Remove overhead from AsyncValueTaskMethodBuilder.Create #13390
Conversation
AsyncValueTaskMethodBuilder.Create wraps an AsyncTaskMethodBuilder and thus initializes its _methodBuilder field to AsyncTaskMethodBuilder.Create. But even though AsyncTaskMethodBuilder.Create just returns default(AsyncTaskMethodBuilder), a non-trivial amount of code is being generated into AsyncValueTaskMethodBuilder.Create, even though it's really just returning default(AsyncValueTaskMethodBuilder). So while the "right" thing to do is to call Create, this commit just skips it and adds a comment that if AsyncTaskMethodBuilder.Create ever becomes more than a nop, various callers would need to be updated as well (though I doubt we'd ever change it). This reduces the overhead of a nop ValueTask-returning async method by ~30%.
cc @CarolEidt There sure are lots of opportunities here (in the before version) for struct-based optimizations. |
I assume the 3
E899D21A5F call CORINFO_HELP_ASSIGN_BYREF
E894D21A5F call CORINFO_HELP_ASSIGN_BYREF
E88FD21A5F call CORINFO_HELP_ASSIGN_BYREF Are setting the 3 nulls? struct AsyncValueTaskMethodBuilder<int>
{
struct AsyncTaskMethodBuilder<int> m_coreState
{
struct AsyncMethodBuilderCore
{
IAsyncStateMachine m_stateMachine = null;
Action m_defaultContextAction = null;
}
Task<int> m_task = null;
}
} Do they need to be helpers for |
There are no helpers needed to set the field to null. It can be set directly. The problem is that the JIT is only optimizing small structs today: https://github.com/dotnet/coreclr/blob/master/src/jit/lclvars.cpp#L1797. AsyncValueTaskMethodBuilder is a big struct. It is treated as block of memory. The details about content of the block are not tracked. |
@dotnet-bot test Tizen armel Cross Debug Build please |
Remove overhead from AsyncValueTaskMethodBuilder.Create Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Remove overhead from AsyncValueTaskMethodBuilder.Create Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Yeah, although with the changes in #13105, two of those three reference fields will go away, and I believe it should be left as a struct of one reference field, one TResult field, and two bool fields, with AsyncTaskMethodBuilder being a struct of a single reference field.
Should I open an issue for anything, or any such opportunities are already tracked? |
Please open an issue, and/or if you are motivated you could look at these known issues: #1133, #1161, #1636, #3144, #3539, #5556 and if it looks like any of those you could either just add a note (or a reference to the new issue). I hope to have a look at this, but I'm not sure when I'll get to it. |
AsyncValueTaskMethodBuilder<TResult>.Create
wraps anAsyncTaskMethodBuilder<TResult>
and thus initializes its_methodBuilder
field toAsyncTaskMethodBuilder<TResult>.Create()
. But even thoughAsyncTaskMethodBuilder<TResult>.Create()
just returnsdefault(AsyncTaskMethodBuilder<TResult>)
, a non-trivial amount of code is being generated intoAsyncValueTaskMethodBuilder<TResult>.Create
, even though it's really just returningdefault(AsyncValueTaskMethodBuilder)
. So while the "right" thing to do is to callCreate
, this commit just skips it and adds a comment that ifAsyncTaskMethodBuilder<TResult>.Create
ever becomes more than a nop returning the default value, various callers would need to be updated as well (though I doubt we'd ever change it). This reduces the overhead of a nopValueTask
-returningasync
method by ~30%.For reference, here's the JitDisasm output for
AsyncValueTaskMethodBuilder<int>.Create
:cc: @benaadams, @geoffkizer, @jkotas, @AndyAyersMS
And a little microbenchmark: