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 allocations in SolutionCompilationState.CreateCompilationTrackerMap #72596

Conversation

ToddGrun
Copy link
Contributor

@ToddGrun ToddGrun commented Mar 19, 2024

Reduce allocations due to resizes in SolutionCompilationState.CreateCompilationTrackerMap. We now use an ImmutableSegmentedDictionary builder to both simplify the code and allocate less. The builder will only create a new dictionary if it's modified, and it does so in a way that doesn't require multiple resizes (which is where the allocation gains from this change are realized).

Additionally, use of the builder allowed a simplification where the modification callback can be changed to a simple action. The call to CreateCompilationTrackerMap now passes in an optimization flag indicating whether the callback needs to occur for empty collections.

@ToddGrun
Copy link
Contributor Author

PIT run numbers that came through the test insertion look really good. Promoting this PR.

@ToddGrun ToddGrun marked this pull request as ready for review March 20, 2024 12:54
@ToddGrun ToddGrun requested a review from a team as a code owner March 20, 2024 12:54
@@ -31,6 +32,8 @@ internal sealed partial class SolutionCompilationState
/// </summary>
private static readonly ConditionalWeakTable<ISymbol, ProjectId> s_assemblyOrModuleSymbolToProjectMap = new();

private static readonly ObjectPool<SegmentedDictionary<ProjectId, ICompilationTracker>> s_trackerInfoPool = new(() => new());
Copy link
Member

Choose a reason for hiding this comment

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

❓ Can you run another pass through without this pool? Instead of using an intermediate dictionary, just use _projectIdToTrackerMap.ToBuilder() at the start of the method, and then return builder.ToImmutable() at the end. This will always create a small builder object, but it removes the need for allReused and isModified, and only allocates a new dictionary if/when something changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I can give that a go, results probably won't come out for around 12 hours.

From my debugging, I saw a large number of calls during project open come through where projectIdToTrackerMap was empty, so I don't love that this would allocate, but we'll give it a go.

Copy link
Member

@sharwell sharwell Mar 20, 2024

Choose a reason for hiding this comment

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

From my debugging, I saw a large number of calls during project open come through where projectIdToTrackerMap was empty

Thanks, this is very helpful. I reviewed all 5 call sites based on this, and found:

  • 3 call sites never add new items to the map, and would silently proceed if the map was empty. These call sites can pass skipEmpty: true (name could be changed), allowing the call to return immediately if the map is empty.
  • 1 call site (WithFrozenSourceGeneratedDocuments) will add items to the map in the callback. This call site can pass skipEmpty: false.
  • 1 call site (WithoutFrozenSourceGeneratedDocuments) does not add items to the map, but it has a runtime assertion that the map is not empty in the callback. This call site can also pass skipEmpty: false to preserve the assertion behavior.

When skipEmpty is true, return _projectIdToTrackerMap without any other work if it is empty at the start of the method. Otherwise, proceed with the ToBuilder() and the builder will perform the modification detection.

@ToddGrun
Copy link
Contributor Author

@sharwell -- Went ahead with the SegmentedCollectionsMarshal usage directly at this location.

Copy link
Member

@sharwell sharwell left a comment

Choose a reason for hiding this comment

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

Requesting simplification based on comment unless speedometer shows it is clearly worse.

…tionTracker>.Builder

2) Change the modification callback to an action
3) Pass flag indicating whether callback needs to happen on empty collections
@ToddGrun ToddGrun changed the title WIP: reduce allocations in SolutionCompilationState.CreateCompilationTrackerMap Reduce allocations in SolutionCompilationState.CreateCompilationTrackerMap Mar 20, 2024
ToddGrun and others added 2 commits March 20, 2024 09:30
…ationState.cs

Co-authored-by: Sam Harwell <sam@tunnelvisionlabs.com>
@ToddGrun
Copy link
Contributor Author

@sharwell -- assuming the speedometer tests come back positive, any remaining concerns?

@sharwell
Copy link
Member

No further concerns

@ToddGrun
Copy link
Contributor Author

speedometer run was broken, don't really want to wait another 12 hours for another chance. I'm pretty certain this should have a sizable positive impact, so I'm going to go ahead and insert once tests pass on the ci.

@ToddGrun
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 4 pipeline(s).

@ToddGrun ToddGrun merged commit 92e8fa3 into dotnet:main Mar 21, 2024
27 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Mar 21, 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.

3 participants