-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add MemoryExtensions to CoreLib along with necessary SpanHelpers #16521
Conversation
@@ -635,7 +635,7 @@ internal static bool TryParseExactMultiple(ReadOnlySpan<char> input, string[] fo | |||
/// <summary>Common private Parse method called by both Parse and TryParse.</summary> | |||
private static bool TryParseTimeSpan(ReadOnlySpan<char> input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result) | |||
{ | |||
input = input.Trim(); |
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.
What was wrong with input.Trim()
?
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.
We have added MemoryExtension methods (Trim/etc) and the call was ambiguous. I will clean up/remove StringSpanHelpers APIs in favor of MemoryExtension after this change goes through.
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.
IMHO, it would be better to just delete StringSpanHelpers.Trim in this change instead of going back and forth.
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.
There are other APIs from StringSpanHelpers we need to delete (and update uses). Most of the calls specify the class name and hence weren't ambiguous. I wanted to keep that change together (and isolated from this one).
Cleanup StringSpanHelpers and uses in subsequent PR.
I can do the cleanup change as part of this PR, but it would have to wait till tomorrow. Hope that's OK.
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.
Bulk of the cleanup can wait for other PR. I just meant that we can do a little bit of the cleanup here to avoid useless changes.
@@ -487,7 +489,10 @@ | |||
<Compile Include="$(MSBuildThisFileDirectory)System\Span.cs" /> | |||
<Compile Include="$(MSBuildThisFileDirectory)System\Span.Fast.cs" /> | |||
<Compile Include="$(MSBuildThisFileDirectory)System\SpanDebugView.cs" /> | |||
<Compile Include="$(MSBuildThisFileDirectory)System\Span.NonGeneric.cs" /> |
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 you will need to keep the non-generic Span class temporary (with forwarders to the new implementations), so that things are not on the floor.
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.
Even after this change #16520 propagates completely (which has these temporary APIs https://github.com/dotnet/coreclr/pull/16520/files#diff-34f82797d649dc710c6ff9268fa79c9bR691)?
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 so.
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.
@jkotas, I have added back Span.NonGeneric but only kept a small subset of the APIs (specifically the ones that were recently renamed). Is that sufficient or do I need to add back all old APIs with forwarders to the impl in MemoryExtensions?
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.
You just need to add enough to make CoreCLR CI green. If the CoreCLR CI is green with what you go, then it is fine.
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 looks like there are several tests that end up calling Span.AsBytes and are failing.
I can't keep both Span.AsBytes and MemoryExtensions.AsBytes since they are extension methods, and would conflict, so should I rename AsBytes in MemoryExtensions for now? Or should I try to temporarily disable the subset of tests that end up calling AsBytes?
- Rename AsBytes, to something like AsBytesExtension, in MemoryExtensions
- Update use of AsBytes in corefx to AsBytesExtension
- Remove Span.AsBytes and add MemoryExtensions.AsBytes
- Update use of AsBytesExtension in corefx to AsBytes
- Remove AsBytesExtension.
Is there a simpler approach?
Uses of AsBytes in corefx:
https://github.com/dotnet/corefx/blob/master/src/System.Private.Xml/src/System/Xml/NameTable.cs - ComputeHash32
https://github.com/dotnet/corefx/blob/master/src/System.Net.Primitives/src/System/Net/IPAddress.cs - GetHashCode
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.
You should keep Span.AsBytes
forwarder but make it non-extension method:
public static class Span
{
public static Span<byte> AsBytes<T>(Span<T> source) => source.AsBytes();
}
@@ -181,7 +181,7 @@ public int CompareTo(Boolean value) | |||
public static Boolean Parse(String value) | |||
{ | |||
if (value == null) throw new ArgumentNullException(nameof(value)); | |||
return Parse(value.AsReadOnlySpan()); | |||
return Parse(value.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.
@KrzysztofCwalina, FWIW, I prefer the old naming. It's possible I'm just used to it, but when I see the code in this PR, with AsSpan being passed to methods that accept a ReadOnlySpan, I immediately think "this is going through a cast and it's going to cost me something" (I've not looked to see if it does cost anything or not once the JIT is done with it). It also makes me think I'm getting back a mutable span even if I'm not, which makes reviewing code harder. And it'll lead to inconsistencies if we ever decide that we do want both AsSpan and As ReadOnlySpan exposed somewhere. I realize I may be in the minority, but I prefer the unabbreviated AsTargetType naming.
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 concur. When I saw this I started digging back to find where the api-approved
issue was so I could point out that we had said that it should be AsReadOnlySpan
and we had explictly had issue with AsSpan
. Then I saw that this was based off of a review meeting that I must not have been present for, and was marked as approved... and I sighed.
I expect AsFoo
to give me a Foo
, not a type sort of like Foo
but with fewer members. And especially with variable names like input
and people who really like var
, it's misleading to a reviewer, since input.AsSpan()
may or may not be "more access" than the code needs.
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, there are pros and cons here. Naming is hard :-)
public static ReadOnlyMemory<T> AsReadOnlyMemory<T>(this Memory<T> memory) => memory; | ||
|
||
/// <summary> | ||
/// |
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.
Missing comment?
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 was going to wait for Ati to add the API in corefx and copy over the xml comment.
/// </summary> | ||
public static int IndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType) | ||
{ | ||
StringSpanHelpers.CheckStringComparison(comparisonType); |
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 approach seems to differ in some of these methods. Here the check is done at the very beginning. In https://github.com/dotnet/coreclr/pull/16521/files#diff-a42ac5dcb8522689822880cf2e02161dR304, the check is done as part of the switch in a default cast, and then it's also done in the fast-path return branches up front, presumably to avoid doing a double-validation for the common case. Why the different approaches?
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 followed the approach from here (to be consistent): #16434
/// <summary> | ||
/// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T). | ||
/// </summary> | ||
public static int SequenceCompareTo<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second) |
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.
Pretty much every other method on this type has AggressiveInlining set. Is there a reason this one is different?
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 see a reason why. This was copy/pasted. I will fix the inconsistency.
|
||
// PERF: This depends on the fact that the String objects are always zero terminated | ||
// and that the terminating zero is not included in the length. For even string sizes | ||
// this compare can include the zero terminator. Bitwise OR avoids a branch. |
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 comment is incorrect for spans. Is this check actually relying on that? If so, it's buggy.
if (pointerSizeLength == 0) | ||
return; | ||
|
||
// TODO: Perhaps do switch casing to improve small size perf |
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 there an issue for this TODO?
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 file was essentially a rename of the existing Span.NonGenerics.
There is an issue here: https://github.com/dotnet/corefx/issues/26604.
I will add additional context to the issue.
Unsafe.InitBlockUnaligned(ref b, 0, (uint)byteLength); | ||
return; | ||
#else | ||
// TODO: Optimize other platforms to be on par with AMD64 CoreCLR |
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 there an issue for this TODO?
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.
Any other feedback before this gets merged and mirrored? |
@@ -928,6 +928,7 @@ static void TestArrayClear<T>(T[] array, int length, int iterationCount) | |||
#endregion | |||
|
|||
#region TestSpanAsBytes<T> | |||
#if false |
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.
You should not need this change now.
@ahsonkhan I was about to re-enable those parts of span bench. I'll let you take over. |
This PR isn't touching the SpanBench. Please continue your PR related to SpanBench. I can take over separately, if you prefer. |
No, I'll take care of it. |
Part of https://github.com/dotnet/corefx/issues/25182
Related PR: dotnet/corefx#27489
TODO before merging:
Figure out the mirroring for the relevant, duplicate source files between corefx/coreclr.TODO outside this PR:
cc @jkotas, @KrzysztofCwalina, @stephentoub, @atsushikan, @dotnet/corefxlab-contrib