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 RuntimeDescription and RuntimeDependencySet #5248

Merged
merged 2 commits into from
Jun 29, 2023

Conversation

drewnoakes
Copy link
Contributor

@drewnoakes drewnoakes commented Jun 16, 2023

Bug

Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1837525
Fixes: NuGet/Home#12714

Regression? Last working version:

Description

The RuntimeDescription and RuntimeDependencySet classes follow similar construction and clone patterns, and the changes to each class here are similar.

Previously each expected to receive non-null enumerables with input values, which would then be enumerated to materialise copies.

In cases where these collections were empty, the allocations are avoidable.

This changes the constructors to receive nullable values, allowing singleton collections to be used in such cases.

Similarly, when cloning, if the collections are empty, nulls are passed so that these singletons are applied. Also when cloning, the known collection type is cast to to enable allocation-free enumeration of the source when constructing the clone object.

PR Checklist

  • PR has a meaningful title

  • PR has a linked issue.

  • Described changes

  • Tests

    • Automated tests added
    • OR
    • Test exception
    • OR
    • N/A
  • Documentation

    • Documentation PR or issue filled
    • OR
    • N/A

These two classes follow a similar construction and clone pattern, and the changes to each are similar.

Previously each expected to receive non-null enumerables with input values, which would then be enumerated to materialise copies.

In cases where these collections were empty, the allocations are avoidable.

This changes the constructors to receive nullable values, allowing singleton collections to be used in such cases.

Similarly, when cloning, if the collections are empty, nulls are passed so that these singletons are applied. Also when cloning, the known collection type is cast to to enable allocation-free enumeration of the source when constructing the clone object.
@drewnoakes drewnoakes requested a review from a team as a code owner June 16, 2023 13:10
nkolev92
nkolev92 previously approved these changes Jun 21, 2023
@nkolev92
Copy link
Member

After a few of the other PRs were merged, there's conflicts @drewnoakes

@drewnoakes
Copy link
Contributor Author

Merged in dev and resolved a few basic conflicts (we no longer have to clone runtimes due to #5249).

@zivkan zivkan merged commit 4543b34 into dev Jun 29, 2023
@zivkan zivkan deleted the dev-drnoakes-1837525-runtime-class-allocations branch June 29, 2023 06:51
left.RuntimeIdentifier,
// If collections are empty, pass null to avoid allocations.
inheritedRuntimes.Count == 0 ? null : inheritedRuntimes,
newSets.Count == 0 ? null : newSets);
Copy link

Choose a reason for hiding this comment

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

newSets passed to the new overload will no longer be case-insensitive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed! Fixed in #5321

drewnoakes added a commit to drewnoakes/NuGet.Client that referenced this pull request Jul 20, 2023
Fixes #12757

PR NuGet#5248 reworked how construction of `RuntimeDescription` instances happens, partly to reduce the number of redundant collection copies that occurred.

As an example of that innefficiency, the prior `RuntimeDesciption.Merge` method would build a dictionary of merged `RuntimeDependencySet` instances, then call a `RuntimeDescription` constructor that would copy those into a new dictionary. The PR added a new private constructor that allowed the copy to be avoided.

That change introduced a bug however. It turns out that during the redundant copy a case-insensitive key comparer was specified, while the `Merge` method's dictionary was case-sensitive. So if two different `RuntimeDependencySet` identifiers differed only by case, then the collection created internally within the `Merge` method would contain both instances, then the constructor call would merge them during the subsequent `ToDictionary` call.

The fix is to ensure that the `Merge` method uses the correct `StringComparer` for `RuntimeDependencySet` identifiers, to preserve the prior behaviour.

Several unit tests have been added to validate the behaviour of `RuntimeDescription.Merge`.
drewnoakes added a commit to drewnoakes/NuGet.Client that referenced this pull request Jul 20, 2023
Fixes #12757

PR NuGet#5248 reworked how construction of `RuntimeDescription` instances happens, partly to reduce the number of redundant collection copies that occurred.

As an example of that innefficiency, the prior `RuntimeDesciption.Merge` method would build a dictionary of merged `RuntimeDependencySet` instances, then call a `RuntimeDescription` constructor that would copy those into a new dictionary. The PR added a new private constructor that allowed the copy to be avoided.

That change introduced a bug however. It turns out that during the redundant copy a case-insensitive key comparer was specified, while the `Merge` method's dictionary was case-sensitive. So if two different `RuntimeDependencySet` identifiers differed only by case, then the collection created internally within the `Merge` method would contain both instances, then the constructor call would merge them during the subsequent `ToDictionary` call.

The fix is to ensure that the `Merge` method uses the correct `StringComparer` for `RuntimeDependencySet` identifiers, to preserve the prior behaviour.

Several unit tests have been added to validate the behaviour of `RuntimeDescription.Merge`.
@aortiz-msft aortiz-msft added the Community PRs created by someone not in the NuGet team label Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community PRs created by someone not in the NuGet team
Projects
None yet
5 participants