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

Restore: improve DependencyGraphSpec.Save(...) performance #3215

Merged
merged 1 commit into from
Feb 20, 2020

Conversation

dtivel
Copy link
Contributor

@dtivel dtivel commented Jan 29, 2020

Bug

Fixes: NuGet/Home#9031
Regression: No

Fix

Details: DependencyGraphSpec.Save(…) saves a dependency graph spec (DG spec) to disk. The previous implementation serialized the JObject representation of the DG spec into a string and then wrote the string to disk. This intermediate step of serializing to a string is both unnecessary and a performance issue. As project and solution DG specs grow beyond 80 KB, these strings will be allocated on the large object heap (LOH). This has terrible performance implications given that NuGet restore is triggered automatically in many scenarios (i.e.: build, rebuild, debug, code analysis, etc.).

Testing/Validation

Tests Added: Yes
Validation: Using the NuGet.Client solution as a case study, I profiled performance 5 times before and 5 times after this change. More iterations than 5 would have been great, but it's time consuming.

Deleting only *.nuget.dgspec.json files before each test (to force calls to DependencyGraphSpec.Save(…)), I observed the following "no-op-ish" restore performance changes in Visual Studio. I say "no-op-ish" because there's no real scenario where only these files are deleted; however, the scenario is no-op in all other respects: a full restore was performed previous to testing. It's also important to note that these numbers are based on _all) activity in VS during a restore operation not just NuGet activity; there are many variables which I cannot control within VS.

All numbers below are averages.

Metric Before After Delta %
"no-op-ish" restore time 2,069.96 ms 1,925.08 ms -144.88 ms -7 %
heap allocations 331.61 MB 308.55 MB -23.06 MB -6.95%
LOH allocations 3.80 MB 0 MB -3.80 MB -100%
total GC pause 1,324.60 ms 1,172.94 ms -151.66 ms -11.45%

A real-life no-op restore --- that is one without me deleting *.nuget.dgspec.json files outside of Visual Studio --- allocated 776,496 bytes on the LOH. With this change it's 0.

/// Every call to WriteObjectStart must be balanced by a corresponding call to WriteObjectEnd.
/// </summary>
/// <exception cref="ObjectDisposedException">Thrown if this object is disposed.</exception>
void WriteObjectStart();
Copy link
Member

Choose a reason for hiding this comment

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

Last time, someone complained about this breaking change: NuGet/Home#8080.

Since then they have fixed their dependency https://github.com/dotnet/arcade/pull/2664/files, but we need to be aware that people do take dependencies.

We can say, the client tooling comes first though :)

Copy link
Contributor

Choose a reason for hiding this comment

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

We just observed this breaking change in dotnet/runtime#48462 after upgrading the SDK to 6.0. You could argue that our custom packaging code should be replaced by NuGet's pack task, but we aren't there yet.

https://github.com/dotnet/arcade/blob/1ca4a4637bf32494c8ba579c32e4512309cae751/src/Microsoft.DotNet.Build.Tasks.Packaging/src/NuGetUtility.cs#L94

cc @Anipik @ericstj

Copy link
Contributor

Choose a reason for hiding this comment

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

What is interesting about this is, even though the change was merged in Feb 2020, it didn't make it into 5.0.100. Was that intentional? The fix for us in dotnet/arcade is just to reference newer nuget packages to update what we bind against.

Copy link
Member

Choose a reason for hiding this comment

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

This change is in 5.0.100, https://github.com/NuGet/NuGet.Client/blob/release-5.8.x/src/NuGet.Core/NuGet.Packaging/RuntimeModel/IObjectWriter.cs

Or at least should be according to the branch and tags.

Can you help me understand the problem you are seeing and why you think it's not in 5.0.100.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think @nkolev92 was commenting that this is breaking because they added a member to an interface. We aren't broken by that addition. I think the break we're currently seeing is something different.

Copy link
Member

@zivkan zivkan left a comment

Choose a reason for hiding this comment

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

In addition to what Nikolche wrote, there are a few other breaking API changes.

In IObjectWriter, WriteObjectInArrayStart was removed, as well as in the classes that implement the interface, so anyone currently using it will break.

JsonObjectWriter's default constructor was removed, and a new constructor with a parameter was added, and the GetJson, GetJObject, WriteTo and GetPreviousContainer methods were removed.

The only possible solution I can think of is to create new classes for the improved functionality,. But that doesn't feel like a good solution, so maybe living with the breaking change is sufficient. It's a shame we have this leaky abstraction.

Copy link
Member

@nkolev92 nkolev92 left a comment

Choose a reason for hiding this comment

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

Let's get clarity on the breaking changes in general, but I don't think this will be one that's actualyl gonna break anyone (considering the previously linked dependency has been fixed)

@dtivel dtivel merged commit 657aa2d into dev Feb 20, 2020
@dtivel dtivel deleted the dev-dtivel-improve-dgspec-save-perf branch February 20, 2020 19:07
ViktorHofer added a commit to dotnet/arcade that referenced this pull request Feb 20, 2021
Had to update the NuGet package dependencies because of a breaking change in their API: NuGet/NuGet.Client#3215 (comment).
ViktorHofer added a commit to dotnet/arcade that referenced this pull request Feb 20, 2021
Had to update the NuGet package dependencies because of a breaking change in their API: NuGet/NuGet.Client#3215 (comment).
akoeplinger pushed a commit to akoeplinger/arcade that referenced this pull request Apr 12, 2021
Had to update the NuGet package dependencies because of a breaking change in their API: NuGet/NuGet.Client#3215 (comment).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Restore: large strings created on large object heap (LOH)
5 participants