-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 allocations on all base converters, improve TokenizerHelper #9364
base: main
Are you sure you want to change the base?
Conversation
src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/LengthConverter.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/LengthConverter.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/LengthConverter.cs
Outdated
Show resolved
Hide resolved
4a84838
to
15380c3
Compare
15380c3
to
beced18
Compare
4cb1aca
to
aadcc3b
Compare
Resolved merge conflicts 12, there are better ways to spent an hour and a half. |
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.
Side note: @h3xds1nz when there's a review on a PR I recommend adding new commits and doing merges instead of rebasing. It makes it much easier to track what changed since the last review.
DefaultInterpolatedStringHandler handler = new(3, 4, cultureInfo, stackalloc char[64]); | ||
handler.AppendFormatted(keySpline.ControlPoint1.X); | ||
handler.AppendLiteral(listSeparator); | ||
|
||
handler.AppendFormatted(keySpline.ControlPoint1.Y); | ||
handler.AppendLiteral(listSeparator); | ||
|
||
handler.AppendFormatted(keySpline.ControlPoint2.X); | ||
handler.AppendLiteral(listSeparator); | ||
|
||
handler.AppendFormatted(keySpline.ControlPoint2.Y); | ||
|
||
return handler.ToStringAndClear(); |
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.
Could this be replaced with this:
DefaultInterpolatedStringHandler handler = new(3, 4, cultureInfo, stackalloc char[64]); | |
handler.AppendFormatted(keySpline.ControlPoint1.X); | |
handler.AppendLiteral(listSeparator); | |
handler.AppendFormatted(keySpline.ControlPoint1.Y); | |
handler.AppendLiteral(listSeparator); | |
handler.AppendFormatted(keySpline.ControlPoint2.X); | |
handler.AppendLiteral(listSeparator); | |
handler.AppendFormatted(keySpline.ControlPoint2.Y); | |
return handler.ToStringAndClear(); | |
return string.Create(cultureInfo, stackalloc char[64], $"{keySpline.ControlPoint1.X}{listSeparator}{keySpline.ControlPoint1.Y}{listSeparator}{keySpline.ControlPoint2.X}{listSeparator}{keySpline.ControlPoint2.Y}"); |
I ran a quick benchmark and it seems to be as fast and doesn't require the direct use of DefaultInterpolatedStringHandler (Which isn't its intended use BTW):
Method | Mean | Error | StdDev | Ratio | RatioSD | Code Size | Gen0 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|---|---|
ToString | 149.9 ns | 2.74 ns | 2.57 ns | 1.00 | 0.02 | 4,723 B | 0.0024 | 40 B | 1.00 |
ToStringWithStringFormat | 150.2 ns | 1.93 ns | 1.71 ns | 1.00 | 0.02 | 3,589 B | 0.0024 | 40 B | 1.00 |
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 chose readibility in this case, I know it will end up in the same code generation (so your bench is correct).
It would be wiser to introduce a ValueStringBuilder
variant but that's for another PR; right now I'd prefer to keep it as it is and change it later along with other places its already used.
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.
But the fact remains that DefaultInterpolatedStringHandler
is not supposed to be called directly, it is supposed to be called by the compiler (See the remarks here). We should avoid using it where possible, even if the syntax is less pretty. There are cases which are "fine" IMO like when the format specifier is not a constant or when we want to do custom formatting to the value (Like you did here: #9363).
It would be wiser to introduce a ValueStringBuilder variant but that's for another PR; right now I'd prefer to keep it as it is and change it later along with other places its already used.
If you already plan on changing it in the future I think it would be best to use DefaultInterpolatedStringHandler
how it's intended (By letting the compiler do it) for now and to change it in the future.
|
||
PixelUnit pixelUnit; | ||
if (PixelUnit.TryParsePixel(valueSpan, out pixelUnit) | ||
|| PixelUnit.TryParsePixelPerInch(valueSpan, out pixelUnit) |
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.
Is the ||
or &&
at the start of the line instead of at the end of the line wrong according to the coding style ? Both seems to be used a lot and if one isn't better than the other I don't see a reason to change it.
if (valueSpan.Equals("auto", StringComparison.OrdinalIgnoreCase)) | ||
return Double.NaN; | ||
|
||
PixelUnit pixelUnit; |
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.
When I wrote this I put it on its own line on purpose because I think it's cleaner when the variable is used multiple times. Is there a reason you changed it ? I know there's an IDE0018 suggestion to change it but projects like winforms or runtime keep it as a suggestion and reason might be that sometimes it's cleaner not to do it.
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 will revert the stylistic changes to your code before merge if you think it's cleaner, as it is your code. I just auto formatted here (which does for me a tad more than pure .editorconfig).
using System.ComponentModel.Design.Serialization; | ||
using System.Windows.Media.Animation; |
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.
Could you double check your using changes in this PR ? There's a couple places where the changes don't make sense to me, like this one where you change unsorts the usings (It might be caused by a bad merge conflict resolution).
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 usings are a mess thanks to that other PR, I will have to take a look onto the files once this PR is closer to review. I was hoping it could be before P1 but maybe P2 or P3 😀
Description
This is probably one of the impactful PRs I've posted; optimizes conversion/parsing of all the base-generated types but also
ThicknessConverter
(once more),CornerRadiusConverter
,KeySplineConverter
andVirtualizationCacheLengthConverter
.Completely removes any allocations caused by the original Tokenizer by using
ReadOnlySpan<char>
instead of substrings.I have also introduced a
ref struct
variant that can be used in any code that doesn't compile fornet472
.MilCodeGen has also been adjusted to support this.
Single Thickness parsing (4 units)
Parsing Thickness 10 times (4 units)
Creation of 10x Matrix3D via Parse
Customer Impact
Improved performance by around 20% on base types, zeroed out allocations.
Regression
No.
Testing
Local build, basic testing of the tokenizer with span and some of the converters.
Risk
Low, the scope of the PR is big but with a simple change, hence any mistakes shall be easy to figure out on a DRT run.
Microsoft Reviewers: Open in CodeFlow