-
Notifications
You must be signed in to change notification settings - Fork 693
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
Remove PackageDependencyGroupConverter #5872
Remove PackageDependencyGroupConverter #5872
Conversation
This is my first time doing a perf issue, what should do to confirm this is an improvement? Also, I tried doing something similar like Parse the assets file with System.Text.Json by jgonz120 · Pull Request #5627 · NuGet/NuGet.Client (github.com), but unfortunately if we want to stop using Newtonsoft.json it will require more work in |
src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupConverter.cs
Outdated
Show resolved
Hide resolved
src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupConverter.cs
Outdated
Show resolved
Hide resolved
src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupConverter.cs
Outdated
Show resolved
Hide resolved
@martinrrm A PerfView trace before and after the fix would help to understand the performance improvement resulting from the changes proposed in this PR. |
@martinrrm did you take that from PerfView's CPU Stacks view, or GC Heap Alloc view? In any case, I think a BenchmarkDotNet app would be a simpler comparison, as you can microbenchmark just the converter, and the memory diagnoser will tell you exactly how much memory is allocated. |
src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupConverter.cs
Outdated
Show resolved
Hide resolved
src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupConverter.cs
Outdated
Show resolved
Hide resolved
src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupConverter.cs
Outdated
Show resolved
Hide resolved
src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupConverter.cs
Outdated
Show resolved
Hide resolved
Hi! I'm reopening the issue, I was finally able to work on it and run some benchmarks. Sorry for delay 😞 Benchmarks
Looking at the numbers by removing the usage of LoadJson and avoid using NJ.LINQ we can improve allocations by 31.91% Branch with the benchmarks: dev-martinrrm-benchmarks |
src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupConverter.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please
- Update the PR title and description to something more accurate for what this PR has become (especially since it's isolated to one specific type. it doesn't fix all usage of
LoadJson
across the entirety of NuGet.Client repo)- Be careful when merging the PR too. The default commit message is set by the PR's title at the time the PR was opened, not when the merge button is clicked.
- Provide fresh benchmark results for deserializing some registration blob
thanks, this PR makes me happy! less custom code.
[JsonConstructor] | ||
private PackageDependencyGroup(NuGetFramework targetFramework) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how the JSON deserialization sets the packages list, if this is the json constructor. But there are tests that pass? 😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's weird, since there is already a constructor that takes targetFramework
and dependencies and null checks them I didn't want to change the behavior of it.
Previous converter would initialize an empty list for dependencies when it was null (no dependencies property in the json) thats why I added a constructor that takes only the TargetFramework
that helps me create the empty list, this constructor is called only when deserialization happens. If we add a [JsonConstructor] it first attempts to use it and then populate the rest of the properties that have [JsonProperty]. JsonProperty
doesn't needs a constructor to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The properties are get only, and they're not even auto-props. If Newtonsoft.Json is using reflection to set the value directly on the backing store, I don't understand how it can determine what the backing field name is. 🤷
src/NuGet.Core/NuGet.Protocol/Converters/NuGetFrameworkConverter.cs
Outdated
Show resolved
Hide resolved
src/NuGet.Core/NuGet.Protocol/Converters/NuGetFrameworkConverter.cs
Outdated
Show resolved
Hide resolved
{ | ||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | ||
{ | ||
serializer.Serialize(writer, value.ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: this is... complicated 😆
The TFM value most people are used to reading, is not the value returned by .ToString()
, but the value returned by .GetShortFoldername()
. And for some reason, .nuspec
files use a 3rd format, which is what we'll probably see from package sources when deserializing dependency groups.
But since this is a generic NuGetFramework
converter, it's hard to say what's "correct" to use. However, I don't know of any scenario where NuGet will serialize this, so this method will probably never be used.
I wonder if we can use this overload of JsonConverterAttribute
to pass data to the converter about which format type is preferred for the specific file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like your comment but since this is targeted for 17.2 P3 I believe I don't have enough time for this at this time, do you want me to investigate this further for next release?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I think my preference would be to throw on write, like the old converter did. If anything needs to convert a NuGetFramework to a string in the future, then we can either investigate that "parameterized" converter, or create one converter per string format.
But I can also live with using ToString
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we throw a not implemented exception here if there are going to be no callers for this method? We can address Andy's feedback in a follow up PR.
The existing code throw not implemented exception for the WriteJson
method.
New benchmarks, I moved some things, so object initialization is not considered in the benchmarks. With these new changes looks like performance is better 🥳
|
Please consider updating the PR description to reflect the current state of the proposed code changes. |
// Act | ||
using var stringReader = new StringReader(PackageRegistrationDependencyGroupsJson_NoTargetFramework); | ||
using var jsonReader = new JsonTextReader(stringReader); | ||
jsonReader.Read(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please help me understand why we have to invoke the Read method in the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is because the first token is null and doing the Read()
moves the iterator to the json object, I'm not 100% sure why this happens but looks like we are doing it in other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just small nitpicks, or nice to haves. No important comments.
[JsonConstructor] | ||
private PackageDependencyGroup(NuGetFramework targetFramework) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The properties are get only, and they're not even auto-props. If Newtonsoft.Json is using reflection to set the value directly on the backing store, I don't understand how it can determine what the backing field name is. 🤷
{ | ||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | ||
{ | ||
serializer.Serialize(writer, value.ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I think my preference would be to throw on write, like the old converter did. If anything needs to convert a NuGetFramework to a string in the future, then we can either investigate that "parameterized" converter, or create one converter per string format.
But I can also live with using ToString
.
Bug
Fixes: NuGet/Home#13445
Regression? Last working version:
Description
The goal of this PR is to avoid using
JsonUtilities.LoadJson
, for that we are removing the converter and let Newtonsoft.Json handle the deserialization of PackageDependencyGroupThis converter is used in the PM UI when we are deserializing the registration API, specifically when we select a package and we retrieve all the packages versions. https://learn.microsoft.com/en-us/nuget/api/registration-base-url-resource#package-dependency
PR Checklist
PR has a meaningful title
PR has a linked issue.
Described changes
Tests
Documentation