-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Conversation
@KooKiz, It will cover your contributions to all .NET Foundation-managed open source projects. |
@@ -2001,6 +2043,22 @@ private static unsafe void ThreadSafeCopy(char* sourcePtr, char[] destination, i | |||
} | |||
} | |||
|
|||
private static unsafe void ThreadSafeCopy(char* sourcePtr, Span<char> destination, int destinationIndex, int count) |
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'm tentative about those overloads, as they significantly increase code duplication. Still, I have yet to come with an elegant way to factorize them (short of making all codepaths going through the Span version, with a performance penalty). Suggestions are much appreciated
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 you know for sure that there is a performance penalty? If so, and Span
is the culprit, we'd love to see examples.
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.
My take was that there is necessarily some overhead, since we would add the conversion from array to span on top of the existing code. But it's true that this overhead is probably not significant in this context. I'll run some benchmark to make sure.
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.
After spending more time on it, I realized that the ThreadSafeCopy overload was only used by CopyTo, so I don't actually have to impact the other codepaths
@dotnet-bot test this please |
The char[] based one was called only by CopyTo so it wasn't needed anymore. Replaced by a Span based version
{ | ||
if (count > 0) | ||
{ | ||
if ((uint)sourceIndex <= (uint)source.Length && (sourceIndex + count) <= source.Length) | ||
if ((uint)sourceIndex > (uint)source.Length || (sourceIndex + count) > source.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.
int
overflow risk
(sourceIndex + count) > source.Length
=> count > (source.Length - sourceIndex)
} | ||
else | ||
|
||
if ((uint)destinationIndex > (uint)destination.Length || (destinationIndex + count) > destination.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.
int
overflow risk?
(destinationIndex + count) > destination.Length)
=> count > (destination.Length - destinationIndex)
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.
FWIW, the same condition exists on the other ThreadSafeCopy overload (that I didn't touch), line 2034.
Still, I agree on the risk. Gonna do some further testing.
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.
source.Length - destinationIndex
@benaadams, copy and paste error?
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.
yeah :)
{ | ||
unsafe | ||
{ | ||
fixed (char* valueChars = &value.DangerousGetPinnableReference()) |
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.
If possible, I'd prefer to see this flipped -- that is it would be preferable to have the pointer overload create a span and pass that throughout the class. That realizes part of the purpose of spans -- avoiding memory corruption while still being fast. If you're not comfortable doing that in this change, I do understand.
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.
Calls string.wstrcpy
though which wants pointers; would need to use equivalent span function in string to that?
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.
Since there's not currently interop support for Span (@yizhang82, any plans?), I agree there are places where pointers are still necessary. However, if we minimize those places, it mitigates cases like the int overflows you pointed out.
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 add an internal
wstrcpy
that takes Span and call that instead?
i.e. have the pointers in string rather than elsewhere
/cc @stephentoub
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.
Let's keep such things separate. There are two distinct tracks for span in coreclr/corefx:
- Using it internally to clean up duplicated code, to make existing unsafe code safer, to make it easier to stack allocate in places we're currently heap allocating, etc.
- Adding new APIs that accept span
I'd like to largely keep those separate. If it's easy to achieve the former while doing the latter, great, or if the best way to implement the API is by changing existing implementation, ok, but doing so also makes it more likely we'll incur performance regressions, especially at this early stage where we haven't shaken out all of the possible perf issues with span. Any such changes need to be measured and validated rigorously.
if (count < 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(count), SR.Arg_NegativeArgCount); | ||
} |
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.
Technically this a breaking change, as if multiple invalid inputs were provided, it'll change the order in which we check and thus will change what exceptions are thrown. That said, I personally think that's an acceptable level of break, but others should weigh in... @weshaggard?
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 think the change is acceptable as well.
Thanks, @kookiz. Have you run the corefx tests against this change? Assuming yes, LGTM. If not, instructions for doing so are at https://github.com/dotnet/corefx/blob/7059f8b316723f5db64e4b1d28e2b1e8b137ab1e/Documentation/project-docs/developer-guide.md#testing-with-private-coreclr-bits. |
@stephentoub I did, but it's interesting that you're asking. Isn't the continuous delivery supposed to automatically run the tests when I push a commit? On an unrelated note, it looks like the cla-required tag wasn't removed even though I signed the agreement (as visible in dotnet/corefx#22852) |
@kookiz, thanks for signing the contribution license agreement. We will now validate the agreement and then the pull request. |
Is there something else I need to do for this PR to be merged? |
@dotnet-bot test Windows_NT x64 corefx_baseline |
* Add StringBuilder Span-based APIs * Factorized ThreadSafeCopy methods The char[] based one was called only by CopyTo so it wasn't needed anymore. Replaced by a Span based version * Fix possible overflow Signed-off-by: dotnet-bot <[email protected]>
* Add StringBuilder Span-based APIs * Factorized ThreadSafeCopy methods The char[] based one was called only by CopyTo so it wasn't needed anymore. Replaced by a Span based version * Fix possible overflow Signed-off-by: dotnet-bot <[email protected]>
Contributes to https://github.com/dotnet/corefx/issues/22430