Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 1ad8ed9

Browse files
dotnet-botahsonkhan
authored andcommitted
Use ROSpan.IndexOf as the workhorse for string.IndexOf (#17284) (#28635)
* Use ROSpan.IndexOf as the workhorse for string.IndexOf * Make changes to Span.IndexOf to follow what string.IndexOf did. * Address PR feedback. * Use Unsafe.Read instead of Unsafe.ReadUnaligned. * Remove special casing for count == 0 * Fix up debug assert to use vector count instead of intptr.size * Use size of Vector<ushort> instead of Vector<byte>.Count Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
1 parent d1d5135 commit 1ad8ed9

File tree

2 files changed

+73
-171
lines changed

2 files changed

+73
-171
lines changed

src/Common/src/CoreLib/System/SpanHelpers.Char.cs

Lines changed: 68 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -84,79 +84,87 @@ public static unsafe int IndexOf(ref char searchSpace, char value, int length)
8484
{
8585
Debug.Assert(length >= 0);
8686

87-
uint uValue = value; // Use uint for comparisons to avoid unnecessary 8->32 extensions
88-
IntPtr index = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
89-
IntPtr nLength = (IntPtr)length;
90-
#if !netstandard11
91-
if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
87+
fixed (char* pChars = &searchSpace)
9288
{
93-
const int elementsPerByte = sizeof(ushort) / sizeof(byte);
94-
int unaligned = ((int)Unsafe.AsPointer(ref searchSpace) & (Vector<byte>.Count - 1)) / elementsPerByte;
95-
nLength = (IntPtr)((Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1));
96-
}
97-
SequentialScan:
89+
char* pCh = pChars;
90+
char* pEndCh = pCh + length;
91+
92+
#if !netstandard11
93+
if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
94+
{
95+
const int elementsPerByte = sizeof(ushort) / sizeof(byte);
96+
int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
97+
length = ((Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1));
98+
}
99+
SequentialScan:
98100
#endif
99-
while ((byte*)nLength >= (byte*)4)
100-
{
101-
nLength -= 4;
102-
103-
if (uValue == Unsafe.Add(ref searchSpace, index))
104-
goto Found;
105-
if (uValue == Unsafe.Add(ref searchSpace, index + 1))
106-
goto Found1;
107-
if (uValue == Unsafe.Add(ref searchSpace, index + 2))
108-
goto Found2;
109-
if (uValue == Unsafe.Add(ref searchSpace, index + 3))
110-
goto Found3;
111-
112-
index += 4;
113-
}
101+
while (length >= 4)
102+
{
103+
length -= 4;
104+
105+
if (*pCh == value)
106+
goto Found;
107+
if (*(pCh + 1) == value)
108+
goto Found1;
109+
if (*(pCh + 2) == value)
110+
goto Found2;
111+
if (*(pCh + 3) == value)
112+
goto Found3;
113+
114+
pCh += 4;
115+
}
114116

115-
while ((byte*)nLength > (byte*)0)
116-
{
117-
nLength -= 1;
117+
while (length > 0)
118+
{
119+
length -= 1;
118120

119-
if (uValue == Unsafe.Add(ref searchSpace, index))
120-
goto Found;
121+
if (*pCh == value)
122+
goto Found;
121123

122-
index += 1;
123-
}
124+
pCh += 1;
125+
}
124126
#if !netstandard11
125-
if (Vector.IsHardwareAccelerated && ((int)(byte*)index < length))
126-
{
127-
nLength = (IntPtr)((length - (int)(byte*)index) & ~(Vector<ushort>.Count - 1));
127+
// We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
128+
// the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
129+
if (Vector.IsHardwareAccelerated && pCh < pEndCh)
130+
{
131+
length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
128132

129-
// Get comparison Vector
130-
Vector<ushort> vComparison = new Vector<ushort>(value);
133+
// Get comparison Vector
134+
Vector<ushort> vComparison = new Vector<ushort>(value);
131135

132-
while ((byte*)nLength > (byte*)index)
133-
{
134-
var vMatches = Vector.Equals(vComparison, Unsafe.ReadUnaligned<Vector<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref searchSpace, index))));
135-
if (Vector<ushort>.Zero.Equals(vMatches))
136+
while (length > 0)
136137
{
137-
index += Vector<ushort>.Count;
138-
continue;
138+
// Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
139+
Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
140+
Vector<ushort> vMatches = Vector.Equals(vComparison, Unsafe.Read<Vector<ushort>>(pCh));
141+
if (Vector<ushort>.Zero.Equals(vMatches))
142+
{
143+
pCh += Vector<ushort>.Count;
144+
length -= Vector<ushort>.Count;
145+
continue;
146+
}
147+
// Find offset of first match
148+
return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
139149
}
140-
// Find offset of first match
141-
return (int)(byte*)index + LocateFirstFoundChar(vMatches);
142-
}
143150

144-
if ((int)(byte*)index < length)
145-
{
146-
nLength = (IntPtr)(length - (int)(byte*)index);
147-
goto SequentialScan;
151+
if (pCh < pEndCh)
152+
{
153+
length = (int)(pEndCh - pCh);
154+
goto SequentialScan;
155+
}
148156
}
149-
}
150157
#endif
151-
return -1;
152-
Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
153-
return (int)(byte*)index;
154-
Found1:
155-
return (int)(byte*)(index + 1);
156-
Found2:
157-
return (int)(byte*)(index + 2);
158-
Found3:
159-
return (int)(byte*)(index + 3);
158+
return -1;
159+
Found3:
160+
pCh++;
161+
Found2:
162+
pCh++;
163+
Found1:
164+
pCh++;
165+
Found:
166+
return (int)(pCh - pChars);
167+
}
160168
}
161169

162170
#if !netstandard11

src/Common/src/CoreLib/System/String.Searching.cs

Lines changed: 5 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,7 @@ public bool Contains(char value, StringComparison comparisonType)
3535
// Returns the index of the first occurrence of a specified character in the current instance.
3636
// The search starts at startIndex and runs thorough the next count characters.
3737
//
38-
public int IndexOf(char value)
39-
{
40-
return IndexOf(value, 0, this.Length);
41-
}
38+
public int IndexOf(char value) => SpanHelpers.IndexOf(ref _firstChar, value, Length);
4239

4340
public int IndexOf(char value, int startIndex)
4441
{
@@ -74,122 +71,19 @@ public int IndexOf(char value, StringComparison comparisonType)
7471

7572
public unsafe int IndexOf(char value, int startIndex, int count)
7673
{
74+
// These bounds checks are redundant since AsSpan does them already, but are required
75+
// so that the correct parameter name is thrown as part of the exception.
7776
if ((uint)startIndex > (uint)Length)
7877
throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
7978

8079
if ((uint)count > (uint)(Length - startIndex))
8180
throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
8281

83-
fixed (char* pChars = &_firstChar)
84-
{
85-
char* pCh = pChars + startIndex;
86-
char* pEndCh = pCh + count;
87-
88-
if (Vector.IsHardwareAccelerated && count >= Vector<ushort>.Count * 2)
89-
{
90-
unchecked
91-
{
92-
const int elementsPerByte = sizeof(ushort) / sizeof(byte);
93-
int unaligned = ((int)pCh & (Vector<byte>.Count - 1)) / elementsPerByte;
94-
count = ((Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1));
95-
}
96-
}
97-
SequentialScan:
98-
while (count >= 4)
99-
{
100-
if (*pCh == value) goto ReturnIndex;
101-
if (*(pCh + 1) == value) goto ReturnIndex1;
102-
if (*(pCh + 2) == value) goto ReturnIndex2;
103-
if (*(pCh + 3) == value) goto ReturnIndex3;
104-
105-
count -= 4;
106-
pCh += 4;
107-
}
108-
109-
while (count > 0)
110-
{
111-
if (*pCh == value)
112-
goto ReturnIndex;
113-
114-
count--;
115-
pCh++;
116-
}
117-
118-
if (pCh < pEndCh)
119-
{
120-
count = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
121-
// Get comparison Vector
122-
Vector<ushort> vComparison = new Vector<ushort>(value);
123-
while (count > 0)
124-
{
125-
var vMatches = Vector.Equals(vComparison, Unsafe.ReadUnaligned<Vector<ushort>>(pCh));
126-
if (Vector<ushort>.Zero.Equals(vMatches))
127-
{
128-
pCh += Vector<ushort>.Count;
129-
count -= Vector<ushort>.Count;
130-
continue;
131-
}
132-
// Find offset of first match
133-
return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
134-
}
135-
136-
if (pCh < pEndCh)
137-
{
138-
unchecked
139-
{
140-
count = (int)(pEndCh - pCh);
141-
}
142-
goto SequentialScan;
143-
}
144-
}
145-
146-
return -1;
82+
int result = SpanHelpers.IndexOf(ref Unsafe.Add(ref _firstChar, startIndex), value, count);
14783

148-
ReturnIndex3: pCh++;
149-
ReturnIndex2: pCh++;
150-
ReturnIndex1: pCh++;
151-
ReturnIndex:
152-
return (int)(pCh - pChars);
153-
}
84+
return result == -1 ? result : result + startIndex;
15485
}
15586

156-
// Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
157-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
158-
private static int LocateFirstFoundChar(Vector<ushort> match)
159-
{
160-
var vector64 = Vector.AsVectorUInt64(match);
161-
ulong candidate = 0;
162-
int i = 0;
163-
// Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
164-
for (; i < Vector<ulong>.Count; i++)
165-
{
166-
candidate = vector64[i];
167-
if (candidate != 0)
168-
{
169-
break;
170-
}
171-
}
172-
173-
// Single LEA instruction with jitted const (using function result)
174-
return i * 4 + LocateFirstFoundChar(candidate);
175-
}
176-
177-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
178-
private static int LocateFirstFoundChar(ulong match)
179-
{
180-
unchecked
181-
{
182-
// Flag least significant power of two bit
183-
var powerOfTwoFlag = match ^ (match - 1);
184-
// Shift all powers of two into the high byte and extract
185-
return (int)((powerOfTwoFlag * XorPowerOfTwoToHighChar) >> 49);
186-
}
187-
}
188-
189-
private const ulong XorPowerOfTwoToHighChar = (0x03ul |
190-
0x02ul << 16 |
191-
0x01ul << 32) + 1;
192-
19387
// Returns the index of the first occurrence of any specified character in the current instance.
19488
// The search starts at startIndex and runs to startIndex + count - 1.
19589
//

0 commit comments

Comments
 (0)