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

Reduce closure allocations associated with AsyncLazy usage #72449

Merged
merged 10 commits into from
Mar 11, 2024

Conversation

ToddGrun
Copy link
Contributor

@ToddGrun ToddGrun commented Mar 8, 2024

Reduce closure allocations associated with AsyncLazy usage

This was noticed while looking at speedometer profiles. This class is used in several high traffic areas, and the current AsyncLazy design doesn't allow usage in a way that prevents closure allocations. This PR adds the "arg" usage pattern to AsyncLazy such that funcs passed to AsyncLazy can take advantage of the arg infrastructure to avoid closure allocations.

This is done by changing AsyncLazy to be an abstract class with two derivations:

  1. AsyncLazyWithoutArg
  2. AsyncLazyWithArg

The former is the same behavior as the prior implementation of AsyncLazy, holding onto the same compute functions as before. The latter holds onto an extra piece of data and compute functions which take that additional argument.

I did talk with Cyrus about an alternate approach where the base class held on to data and there was a derived type which masked that for users that didn't wish to use that mechanism. That approach had the advantage of not having the virtual calls that the approach outlined in this PR has, however, the type declarations were a bit clunky in that they needed to specify the data type. Anothed disadvantage of that approach was that all users of AsyncLazy would pay the cost of having some additional data, even if they weren't using that feature.

The first commit in this PR is the guts of the change, pretty much solely contained within the AsyncLazy`1.cs file. The second commit will contain the changes for the usages I found that were allocating closures.

ToddGrun added 2 commits March 7, 2024 16:01
This was noticed while looking at speedometer profiles. This class is used in several high traffic areas, and the current AsyncLazy design doesn't allow usage in a way that prevents closure allocations. This PR adds the "arg" usage pattern to AsyncLazy such that funcs passed to AsyncLazy can take advantage of the arg infrastructure to avoid closure allocations.

This is done by changing AsyncLazy<T> to be an abstract class with two derivations:

1) AsyncLazyWithoutArg
2) AsyncLazyWithArg

The former is the same behavior as the prior implementation of AsyncLazy, holding onto the same compute functions as before. The latter holds onto an extra piece of data and compute functions which take that additional argument.

I did talk with Cyrus about an alternate approach where the base class held on to data and there was a derived type which masked that for users that didn't wish to use that mechanism. That approach had the advantage of not having the virtual calls that the approach outlined in this PR has, however, the type declarations were a bit clunky in that they needed to specify the data type. Anothed disadvantage of that approach was that all users of AsyncLazy would pay the cost of having some additional data, even if they weren't using that feature.

The first commit in this PR is the guts of the change, pretty much solely contained within the AsyncLazy`1.cs file. The second commit will contain the changes for the usages I found that were allocating closures.
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 8, 2024
@ToddGrun
Copy link
Contributor Author

ToddGrun commented Mar 8, 2024

Added @sharwell and @CyrusNajmabadi and started in draft mode

@ToddGrun
Copy link
Contributor Author

ToddGrun commented Mar 8, 2024

/azp run

Copy link

Azure Pipelines successfully started running 4 pipeline(s).

2) Fix test failure
3) Fix formatting request
4) Change AsyncLazy ctors to protected
5) Rename ResetComputeFunctions to ClearComputeFunctions
var compilationStateCapture = compilationState;
Interlocked.CompareExchange(ref _lazyDependentVersion, AsyncLazy.Create(
c => ComputeDependentVersionAsync(compilationStateCapture, c)), null);
Interlocked.CompareExchange(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 InterlockedOperations.Initialize

…ontain the data, and the version that doesn't need data stored built on that.
@ToddGrun ToddGrun marked this pull request as ready for review March 11, 2024 20:27
@ToddGrun ToddGrun requested review from a team as code owners March 11, 2024 20:27
@ToddGrun ToddGrun merged commit 645ed1b into dotnet:main Mar 11, 2024
27 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Mar 11, 2024
@RikkiGibson RikkiGibson modified the milestones: Next, 17.10 P3 Mar 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants