-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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 excess allocations in Uri.ReCreateParts #34864
Conversation
Tagging @dotnet/ncl as an area owner. If you would like to be tagged for a label, please notify danmosemsft. |
|
||
static void EnsureCapacity(char[]? dest, int destSize, int requiredSize) |
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.
This looks like a bug in the current implementation as the param is not ref char[]
, therefore the resize isn't visible.
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.
Do we need a test that would catch 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.
The overload accepting a char[] destination was only called from ReCreateParts, after this conservative worst-case capacity calculation. I believe it was thankfully unreachable code, as we never needed a resize.
vsb.TryCopyTo(dest.AsSpan(destPos), out int charsWritten); | ||
destPos += charsWritten; | ||
return dest; | ||
ReadOnlySpan<bool> noEscapeCopy = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(noEscape), UnreservedReservedTable.Length); |
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.
This is a hack to avoid code analysis from preventing us passing the noEscape span as a parameter with
CS8352 Cannot use local 'noEscape' in this context because it may expose referenced variables outside of their declaration scope
and
CS8350 This combination of arguments to 'UriHelper.EscapeStringToBuilder(ReadOnlySpan<char>, ref ValueStringBuilder, ReadOnlySpan<bool>, bool)' is disallowed because it may expose variables referenced by parameter 'noEscape' outside of their declaration scope
Is this still legal from the runtime's perspective? Can it be avoided (without duplicating EscapeToBuilder by inlining it into this method)?
@stephentoub @jkotas
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 ok from the runtime's perspective.
The only way to avoid this today that I think of is to stackalloc the flags in the same method that they are used in.
copy.Append(dest.AsSpan(start, dest.Length - start)); | ||
|
||
dest.Length = start; | ||
ReadOnlySpan<char> copySpan = MemoryMarshal.CreateReadOnlySpan(ref copy.GetPinnableReference(), copy.Length); |
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.
Same as #34864 (comment) for using this instead of copy.AsSpan()
src/libraries/System.Private.Uri/src/System/ValueStringBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
a019979
to
0c543fb
Compare
src/libraries/System.Private.Uri/src/System/ValueStringBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.Uri/src/System/ValueStringBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.Uri/src/System/DomainNameHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.Uri/src/System/DomainNameHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.Uri/src/System/DomainNameHelper.cs
Outdated
Show resolved
Hide resolved
} | ||
} | ||
else if (_syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) && InFact(Flags.BackslashInPath)) | ||
{ | ||
for (int i = pos; i < end; ++i) | ||
Span<char> chars = dest.RawChars.Slice(start, dest.Length - start); | ||
for (int i = 0; i < chars.Length; i++) |
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 this change. Why are we slicing to get chars and iterating over its length, but then using dest
? Assuming that's a bug and dest
should be chars
, seems like we then have a test hole?
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.
Right, this should be chars. I will look into getting a test for this
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.
This block is currently unreachable as we always add the ShouldBeCompressed
flag when we have ConvertPathSlashes
. That looks like I bug - I'll look into cases where the output is changed because of this (could be this is okay and this block can simply be removed).
src/libraries/System.Private.Uri/src/System/ValueStringBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
Looks like all feedback is addressed now. @stephentoub can you please re-review so that we can merge? Thanks! |
@alnikola @wfurt @scalablecory can you please review it as Stephen is OOF? |
@MihaZupan, can you rebase this to address the conflicts? Thanks. |
/azp run runtime |
Azure Pipelines successfully started running 1 pipeline(s). |
Closing as not actionable until reviewed further. |
Team discussion:
|
This PR is being under review now. |
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.
LGTM
@stephentoub Could you PTAL at the changes now that this PR doesn't include changes from #46747 and #46751? |
copy.Append(dest.AsSpan(start, dest.Length - start)); | ||
|
||
dest.Length = start; | ||
ReadOnlySpan<char> copySpan = MemoryMarshal.CreateReadOnlySpan(ref copy.GetPinnableReference(), copy.Length); |
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.
How is this different from copy.AsSpan()
?
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.
Oh, I see your GitHub comment on a similar line below. It'd be worth a comment in the code.
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.
Added comments.
We should be able to Spanify more Uri code now and hopefully remove these hacks in the future.
Fixes #22903
For a sample Uri containing non-ascii chars, allocations for fetching
PathAndQuery
andIdnHost
are reduced from 1608 to 272 bytes (168 after the first access since the Uri is fully parsed at that point).After this PR and #32552, it should be fairly simple to move more unsafe code to use Span.