Skip to content
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: Inline async method #43908

Closed
RamType0 opened this issue May 2, 2020 · 16 comments
Closed

Proposal: Inline async method #43908

RamType0 opened this issue May 2, 2020 · 16 comments
Labels
Area-Compilers Feature Request Resolution-Duplicate The described behavior is tracked in another issue

Comments

@RamType0
Copy link

RamType0 commented May 2, 2020

Currently,each async method creates one state machine.
I wanna combine state machine with nested async method invocation.
It works like F# inline methods, assembly internal only.

@svick
Copy link
Contributor

svick commented May 2, 2020

I wanna combine state machine with nested async method invocation.

Why? If it's to improve performance, can you show how much this would help you in a realistic scenario?

@RamType0
Copy link
Author

RamType0 commented May 2, 2020

I wanna combine state machine with nested async method invocation.

Why?

For performance.

If it's to improve performance, can you show how much this would help you in a realistic scenario?

Here's test project.

I got this kind of result.

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.778 (1909/November2018Update/19H2)
Intel Core i9-9900K CPU 3.60GHz (Coffee Lake), 1 CPU, 16 logical and 8 physical cores
  [Host]        : .NET Framework 4.8 (4.8.4150.0), X64 RyuJIT
  .NET 4.8      : .NET Framework 4.8 (4.8.4150.0), X64 RyuJIT
  .NET Core 3.1 : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
  Mono 6.8.0    : Mono 6.8.0 (Visual Studio), X64
Method Job Runtime Mean Error StdDev
NestedNoAsync .NET 4.8 .NET 4.8 1,072.12 ns 1.803 ns 1.599 ns
InlineNoAsync .NET 4.8 .NET 4.8 47.65 ns 0.257 ns 0.214 ns
NestedYieldOnce .NET 4.8 .NET 4.8 6,879.69 ns 125.388 ns 117.288 ns
InlineYieldOnce .NET 4.8 .NET 4.8 1,560.72 ns 20.274 ns 17.973 ns
NestedYieldEach .NET 4.8 .NET 4.8 41,376.44 ns 358.283 ns 335.138 ns
InlineYieldEach .NET 4.8 .NET 4.8 29,016.23 ns 84.470 ns 79.013 ns
NestedNoAsync .NET Core 3.1 .NET Core 3.1 564.90 ns 0.638 ns 0.597 ns
InlineNoAsync .NET Core 3.1 .NET Core 3.1 18.12 ns 0.230 ns 0.215 ns
NestedYieldOnce .NET Core 3.1 .NET Core 3.1 3,217.08 ns 31.706 ns 29.658 ns
InlineYieldOnce .NET Core 3.1 .NET Core 3.1 849.34 ns 3.877 ns 3.238 ns
NestedYieldEach .NET Core 3.1 .NET Core 3.1 28,945.69 ns 577.294 ns 809.285 ns
InlineYieldEach .NET Core 3.1 .NET Core 3.1 20,666.05 ns 166.498 ns 155.742 ns
NestedNoAsync Mono 6.8.0 Mono 6.8.0 891.03 ns 2.358 ns 2.206 ns
InlineNoAsync Mono 6.8.0 Mono 6.8.0 40.26 ns 0.035 ns 0.029 ns
NestedYieldOnce Mono 6.8.0 Mono 6.8.0 16,797.55 ns 311.562 ns 305.996 ns
InlineYieldOnce Mono 6.8.0 Mono 6.8.0 10,095.49 ns 200.777 ns 396.314 ns
NestedYieldEach Mono 6.8.0 Mono 6.8.0 178,924.15 ns 538.445 ns 503.661 ns
InlineYieldEach Mono 6.8.0 Mono 6.8.0 157,137.39 ns 3,116.256 ns 8,154.698 ns
  • XXXNoAsync
    It is marked as async,but it completes synchronously.
    In this pattern, no state machines would be created in heap.(Thanks for ValueTask)
  • XXXYieldOnce
    Call "Task.Yield" and awaits it once. The most blatant, but still realistic scenario(Because async is "infectious").
    • InlineYieldOnce creates one state machine in heap,and continuations are invoked from delegate once.
    • NestedYieldOnce creates 21 state machines in heap,and continuations are invoked from delegate 21 times.
  • XXXYieldEach
    Call "Task.Yield" twenty times and awaits it each times.

Each NestedXXX and InlineXXX method doing exactly same things.
But in every scenario, InlineXXX was definitely faster.

@CyrusNajmabadi
Copy link
Member

It's unclear what is being asked for here. Can you explain what codegen you would want given an existing code pattern that is written?

@RamType0
Copy link
Author

RamType0 commented May 2, 2020

Currently ,if we write this kind of code,these are 5 state machine and 5 tasks created every time we call ShowImageAsync.

async ValueTask ShowImageAsync(string url)
{
   var blob = DownloadBlobAsync(url);
   await RenderImageAsync(blob);
}

async ValueTask<byte[]> DownloadBlobAsync(string url)
{
   var connection = await ConnectAsync(url);
   return await RequestBlobAsync(connection,url);
}

async ValueTask<Connection> ConnectAsync(string url)
{
   ConnectAsync code
}
async ValueTask<byte[]> RequestBlobAsync(Connection connection,string url)
{
   RequestBlobAsync code
}
async ValueTask RenderImageAsync(ReadOnlyMemory<byte> blob)
{
   RenderImageAsync code
}

We could reduce number of tasks,async state machines by this kind of code generation.

async ValueTask ShowImageAsync(string url)
{
   var connection = await  ConnectAsync code;
   var blob = await RequestBlobAsync code;
   await RenderImageAsync code;
}

async ValueTask<byte[]> DownloadBlobAsync(string url)
{
   var connection = await  ConnectAsync code;
   return await RequestBlobAsync code;
}

async ValueTask<Connection> ConnectAsync(string url)
{
   ConnectAsync code
}
async ValueTask<byte[]> RequestBlobAsync(Connection connection,string url)
{
   RequestBlobAsync code
}
async ValueTask RenderImageAsync(ReadOnlyMemory<byte> blob)
{
   RenderImageAsync code
}

With this code generation, only one state machine and task are created every time we call ShowImageAsync.

@CyrusNajmabadi
Copy link
Member

We could reduce number of tasks,async state machines by this kind of code generation.

I don't see how that would be ok. it would be inlining all the code (transitively) of all async methods called. That would greatly increase the side of htese methods.

@alrz
Copy link
Member

alrz commented May 3, 2020

Similar to #15491

@RamType0
Copy link
Author

RamType0 commented May 3, 2020

That would greatly increase the side of htese methods.

You mean the size of these methods?
As I said at the beginning,it works like F# inline methods.
Yeah,F# is already doing such a thing,and is it said that F# inline method's assembly size increasing are harmful?

@alrz
Copy link
Member

alrz commented May 3, 2020

it would be inlining all the code (transitively) of all async methods called. That would greatly increase the side of htese methods.

Instead of inlining the whole code, can't we just pass the state machine instance to the nested method, so it could modify the state on an existing state machine.

meaning: we still have method boundaries but all of them operate on a single state machine.

@jinujoseph
Copy link
Contributor

jinujoseph commented May 7, 2020

Duplicate of #22052

@jinujoseph jinujoseph marked this as a duplicate of #22052 May 7, 2020
@sharwell
Copy link
Member

sharwell commented May 7, 2020

cc @genlu

@sharwell
Copy link
Member

sharwell commented May 7, 2020

@RamType-0 we are interested in providing an "inline method" feature more broadly. This issue captures one possible use case, but there are many different teams that could benefit from having this available in different ways. I believe we are starting fairly conservatively with the implementation approach, but would be interested in expanding coverage to new scenarios as they are identified with use of the feature.

@sharwell sharwell closed this as completed May 7, 2020
@sharwell sharwell added the Resolution-Duplicate The described behavior is tracked in another issue label May 7, 2020
@svick
Copy link
Contributor

svick commented May 8, 2020

@jinujoseph @sharwell How is this a duplicate? #22052 is about a refactoring (i.e. something that changes the C# code in the editor), while, as I understand it, this issue is about a compiler feature (i.e. something that keeps the C# code intact, but changes the generated IL).

Since the two have completely different goals (one is all about removing abstraction, the other is about maintaining it), I don't see how one could be a duplicate of the other.

@sharwell
Copy link
Member

sharwell commented May 8, 2020

@svick I was going based on the information in the first post, which I see is ambiguous. If this is a request for the compiler to automate this inlining (as opposed to a refactoring of the code itself), then this would be a duplicate of #15491.

@jmarolf
Copy link
Contributor

jmarolf commented May 8, 2020

yeah, this is asking for a compiler optimization I do not think this should be tracked as part of #22052

@jmarolf jmarolf reopened this May 8, 2020
@jmarolf jmarolf added Area-Compilers and removed Area-IDE Resolution-Duplicate The described behavior is tracked in another issue labels May 8, 2020
@jmarolf jmarolf removed this from the Backlog milestone May 8, 2020
@sharwell
Copy link
Member

sharwell commented May 8, 2020

Duplicate of #15491

@sharwell sharwell marked this as a duplicate of #15491 May 8, 2020
@sharwell sharwell closed this as completed May 8, 2020
@sharwell sharwell added the Resolution-Duplicate The described behavior is tracked in another issue label May 8, 2020
@jmarolf
Copy link
Contributor

jmarolf commented May 8, 2020

that is the correct dupe :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Feature Request Resolution-Duplicate The described behavior is tracked in another issue
Projects
None yet
Development

No branches or pull requests

7 participants