-
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
Vectorize SpanHelpers<T>.IndexOf #60974
Changes from 7 commits
d31edd6
2f814ca
b200ddb
4f333bc
7b49da9
a749275
818de41
62affca
04253e1
d6642e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
using System.Diagnostics; | ||
using System.Numerics; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
using System.Runtime.Intrinsics; | ||
using Internal.Runtime.CompilerServices; | ||
|
||
|
@@ -225,6 +226,22 @@ public static unsafe bool Contains<T>(ref T searchSpace, T value, int length) wh | |
{ | ||
Debug.Assert(length >= 0); | ||
|
||
if (typeof(T).IsValueType && RuntimeHelpers.IsBitwiseEquatable<T>()) | ||
{ | ||
// bool and char will already have been checked before, just do checks for types | ||
// that are equal to sizeof(int) or sizeof(long) | ||
if (Unsafe.SizeOf<T>() == sizeof(int)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this break for float and double? This is why we have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I'll add a check. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need tests for float and double somewhere -- did anything fail before you fixed this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, nothing failed for me when testing locally using: .\build.cmd clr+libs+libs.tests -c Checked -test
.\build.cmd clr+libs+libs.tests -c Release -test But some new tests for |
||
{ | ||
int result = IndexOfValueType(ref Unsafe.As<T, int>(ref searchSpace), Unsafe.As<T, int>(ref value), length); | ||
return result != -1; | ||
} | ||
else if (Unsafe.SizeOf<T>() == sizeof(long)) | ||
{ | ||
int result = IndexOfValueType(ref Unsafe.As<T, long>(ref searchSpace), Unsafe.As<T, long>(ref value), length); | ||
return result != -1; | ||
} | ||
} | ||
|
||
nint index = 0; // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations | ||
|
||
if (default(T) != null || (object)value != null) | ||
|
@@ -291,6 +308,115 @@ public static unsafe bool Contains<T>(ref T searchSpace, T value, int length) wh | |
return true; | ||
} | ||
|
||
internal static unsafe int IndexOfValueType<T>(ref T searchSpace, T value, int length) where T : struct, IEquatable<T> | ||
{ | ||
Debug.Assert(length >= 0); | ||
|
||
nint index = 0; // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations | ||
if (Vector.IsHardwareAccelerated && Vector<T>.IsTypeSupported && (Vector<T>.Count * 2) <= length) | ||
{ | ||
Vector<T> valueVector = new Vector<T>(value); | ||
Vector<T> compareVector = default; | ||
Vector<T> matchVector = default; | ||
if ((uint)length % (uint)Vector<T>.Count != 0) | ||
{ | ||
// Number of elements is not a multiple of Vector<T>.Count, so do one | ||
// check and shift only enough for the remaining set to be a multiple | ||
// of Vecotr<T>.Count. | ||
alexcovington marked this conversation as resolved.
Show resolved
Hide resolved
|
||
compareVector = Unsafe.As<T, Vector<T>>(ref Unsafe.Add(ref searchSpace, index)); | ||
matchVector = Vector.Equals(valueVector, compareVector); | ||
if (matchVector != Vector<T>.Zero) | ||
{ | ||
goto VectorMatch; | ||
} | ||
index += length % Vector<T>.Count; | ||
length -= length % Vector<T>.Count; | ||
} | ||
while (length > 0) | ||
{ | ||
compareVector = Unsafe.As<T, Vector<T>>(ref Unsafe.Add(ref searchSpace, index)); | ||
matchVector = Vector.Equals(valueVector, compareVector); | ||
if (matchVector != Vector<T>.Zero) | ||
{ | ||
goto VectorMatch; | ||
} | ||
index += Vector<T>.Count; | ||
length -= Vector<T>.Count; | ||
} | ||
goto NotFound; | ||
VectorMatch: | ||
for (int i = 0; i < Vector<T>.Count; i++) | ||
if (compareVector[i].Equals(value)) | ||
return (int)(index + i); | ||
} | ||
|
||
while (length >= 8) | ||
{ | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index))) | ||
goto Found; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 1))) | ||
goto Found1; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 2))) | ||
goto Found2; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 3))) | ||
goto Found3; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 4))) | ||
goto Found4; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 5))) | ||
goto Found5; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 6))) | ||
goto Found6; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 7))) | ||
goto Found7; | ||
|
||
length -= 8; | ||
index += 8; | ||
} | ||
|
||
while (length >= 4) | ||
{ | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index))) | ||
goto Found; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 1))) | ||
goto Found1; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 2))) | ||
goto Found2; | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index + 3))) | ||
goto Found3; | ||
|
||
length -= 4; | ||
index += 4; | ||
} | ||
|
||
while (length > 0) | ||
{ | ||
if (value.Equals(Unsafe.Add(ref searchSpace, index))) | ||
goto Found; | ||
|
||
index += 1; | ||
length--; | ||
} | ||
NotFound: | ||
return -1; | ||
|
||
Found: // Workaround for https://github.com/dotnet/runtime/issues/8795 | ||
return (int)index; | ||
Found1: | ||
return (int)(index + 1); | ||
Found2: | ||
return (int)(index + 2); | ||
Found3: | ||
return (int)(index + 3); | ||
Found4: | ||
return (int)(index + 4); | ||
Found5: | ||
return (int)(index + 5); | ||
Found6: | ||
return (int)(index + 6); | ||
Found7: | ||
return (int)(index + 7); | ||
} | ||
|
||
public static unsafe int IndexOf<T>(ref T searchSpace, T value, int length) where T : IEquatable<T> | ||
{ | ||
Debug.Assert(length >= 0); | ||
|
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.
Indeed I see byte/bool and char are checked here
https://github.com/danmoseley/runtime/blob/f3ca6f91ba9c758bb246be8ba26bd356d3f9dda6/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs#L298
... why are 1 and 2 byte sizes treated specially there, and 4 and 8 byte sizes treated specially here? why not all in the same place?
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 think I was referencing a different check for those types, but putting them all near the section you linked would be cleaner and more intuitive. I'll move the checks there instead.