-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Proposal: Add IL-level inlining for async methods #15491
Comments
Related: #10449 |
Maybe better, faster and easier is to recycle these frames. Just create some smart cache for frames and implement methods for cleanup before subsequent usage. |
@vbcodec unfortunately those frames are not the same types or sizes... I would like a general purpose (beyond the scope of Specifically for
static async Task A()
{
var a = 0;
await B();
Console.Write(a++);
await B();
Console.Write(a++);
await B();
Console.Write(a++);
await B();
Console.Write(a);
}
[ILInline]
static async Task B()
{
var b = 2;
await Task.Delay(1);
Console.Write(b);
} I'd expect this to compile effectively the same as if it was written: static async Task A()
{
var a = 0;
{
var b = 2;
await Task.Delay(1);
Console.Write(b);
}
Console.Write(a++);
{
var b = 2;
await Task.Delay(1);
Console.Write(b);
}
Console.Write(a++);
{
var b = 2;
await Task.Delay(1);
Console.Write(b);
}
Console.Write(a++);
{
var b = 2;
await Task.Delay(1);
Console.Write(b);
}
Console.Write(a);
}
[ILInline]
static async Task B()
{
var b = 2;
await Task.Delay(1);
Console.Write(b);
} that is:
On the other hand, the code for the MoveNext method of the second state machine could be rewritten with this procedure:
full gist of such a machine: https://gist.github.com/bbarry/01690992031f27de542997a4682db391 |
updated gist with an alternative implementation: https://gist.github.com/bbarry/01690992031f27de542997a4682db391 Alternatively, the
When another state machine calls a method with this attribute, the callsite is modified by inlining and adding the necessary local fields: from
to
and adding that last line to the continuation site in a similar manner |
|
I don't believe that would be better/faster/easier. Pooling has its own costs and implications, and I would much rather eliminate costs entirely rather than try to workaround them via pooling. It's possible some form of pooling could be explored in addition, but I don't believe it replaces the desire for something along the lines of what I've suggested.
That was my intention.
My suggestion here (which was a rough outline, I'm sure with details that would change) was to place it on the inlinee. I also did not envision this being placed on publicly exposed methods, but rather for use within my own code, where within the same compilation unit the compiler could apply such optimizations. I would expect that the compiler would ignore such an attribute when consuming a method from one assembly into another. |
Interesting. Would this be applicable to iterator state machines as well? Seems like many of the benefits mentioned here (combining implicit |
@jamesqo I'm not sure iterators have the same performance issues as async. I think long chains of nested iterators do (or could) happen in two cases:
Or are there some cases that I'm missing that you think would be common enough to make that worth it? |
Good point. I had Linq in mind when I wrote that, but I had forgotten that doing any sort of inlining optimization would be invalid across library boundaries, since the code in the other library could change and the already-compiled library that contains inlined code would be stale. Besides, a lot of Linq is being converted to use custom iterator types which the compiler won't recognize. 👍 Still, it seems like it would be a nice optimization to have if it is not too much trouble after the work for async is done. |
This has the potential to significantly improve the performance of canceling nested tasks. |
@stephentoub any thoughts on this now? Do you see a future for this? Or is there a different path we'd prefer today to get the benefits? |
I don't know of any other path for this, other than continuing along the current path of copy/paste the code into each method that needs it. |
When an async method yields for the first time, we get several allocations:
For performance-sensitive code bases, these costs can lead developers to avoid refactoring code into smaller async methods in order to avoid introducing additional allocations, or more commonly in my own experience to manually combine async methods as part of perf investigations, often making the code harder to maintain, often leading to duplication, etc. If async method A awaits B awaits C awaits D, and D ends up yielding, then each of A, B, C, and D incur all of the allocations mentioned above; if instead the developer puts all of the code into A, those allocations are only incurred for one frame rather than for each of four.
I propose we add the ability for the language compiler to inline an async method into another; this could be automatic based on some heuristic, it could be manual based on a new [ILInline] attribute or something like that, or some combination (I prefer the attribute for now). Then a developer can have their cake and eat it, too: they can refactor their code into smaller async methods that compose well and make the code more readable/maintainable, but they don’t have to pay the performance penalty for doing so.
This is also an optimization that would be more than the sum of its parts. It’s not just getting rid of Action/MoveNextRunner/Task allocations, but it also affords the opportunity to shrink the combined state machines:
All of that can result in significant savings. There are also other costs it would help avoid:
There are some potential corner-case side effects, which among other reasons leads me to want this to be opt-in with an attribute. For example, given this code:
Today this prints out 21. If the code from B were to just be moved into A, it would instead print out 22. This could be addressed as part of the inlining with additional runtime support, but I’d be happy with it just being a side-effect of opt-in inlining that a developer has to be aware of to use the advanced feature.
The text was updated successfully, but these errors were encountered: