Skip to content

Commit 432397d

Browse files
authored
Faster IndexOfAny for IgnoreCase Ascii letters (#96588)
* Add packed IgnoreCase IndexOfAny variants * Emit SearchValues for ASCII sets of 4/5 values in RegexCompliler * Remove Any3CharPackedIgnoreCase * Update asserts * Attribute order * Fix build * More comments * Tweak ContainsCore
1 parent 483fd0c commit 432397d

22 files changed

+600
-368
lines changed

src/libraries/System.Memory/tests/Span/SearchValues.cs

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public static IEnumerable<object[]> Values_MemberData()
4444
"aaa",
4545
"aaaa",
4646
"aaaaa",
47+
"Aa",
48+
"AaBb",
49+
"AaBbCc",
50+
"[]{}",
4751
"\uFFF0",
4852
"\uFFF0\uFFF2",
4953
"\uFFF0\uFFF2\uFFF4",

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

+8-6
Original file line numberDiff line numberDiff line change
@@ -423,14 +423,16 @@
423423
<Compile Include="$(MSBuildThisFileDirectory)System\IFormatProvider.cs" />
424424
<Compile Include="$(MSBuildThisFileDirectory)System\IFormattable.cs" />
425425
<Compile Include="$(MSBuildThisFileDirectory)System\Index.cs" />
426+
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any1CharPackedSearchValues.cs" />
427+
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any1CharPackedIgnoreCaseSearchValues.cs" />
428+
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any2CharPackedIgnoreCaseSearchValues.cs" />
429+
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any3CharPackedSearchValues.cs" />
430+
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any2CharPackedSearchValues.cs" />
431+
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any1SearchValues.cs" />
432+
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any2SearchValues.cs" />
433+
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any3SearchValues.cs" />
426434
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\BitVector256.cs" />
427435
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\ProbabilisticWithAsciiCharSearchValues.cs" />
428-
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\SingleCharSearchValues.cs" />
429-
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\SingleByteSearchValues.cs" />
430-
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any2ByteSearchValues.cs" />
431-
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any2CharSearchValues.cs" />
432-
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any3ByteSearchValues.cs" />
433-
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any3CharSearchValues.cs" />
434436
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any4SearchValues.cs" />
435437
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any5SearchValues.cs" />
436438
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\AsciiByteSearchValues.cs" />

src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ internal static int IndexOfOrdinalIgnoreCase(ReadOnlySpan<char> source, ReadOnly
397397
// Do a quick search for the first element of "value".
398398
int relativeIndex = isLetter ?
399399
PackedSpanHelpers.PackedIndexOfIsSupported
400-
? PackedSpanHelpers.IndexOfAny(ref Unsafe.Add(ref searchSpace, offset), valueCharU, valueCharL, searchSpaceMinusValueTailLength)
400+
? PackedSpanHelpers.IndexOfAnyIgnoreCase(ref Unsafe.Add(ref searchSpace, offset), valueCharL, searchSpaceMinusValueTailLength)
401401
: SpanHelpers.IndexOfAnyChar(ref Unsafe.Add(ref searchSpace, offset), valueCharU, valueCharL, searchSpaceMinusValueTailLength) :
402402
SpanHelpers.IndexOfChar(ref Unsafe.Add(ref searchSpace, offset), valueChar, searchSpaceMinusValueTailLength);
403403
if (relativeIndex < 0)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
using System.Runtime.Intrinsics.X86;
8+
9+
namespace System.Buffers
10+
{
11+
internal sealed class Any1CharPackedIgnoreCaseSearchValues : SearchValues<char>
12+
{
13+
// While this most commonly applies to ASCII letters, it also works for other values that differ by 0x20 (e.g. "[{" => "{").
14+
// _lowerCase is therefore not necessarily a lower case ASCII letter, but just the higher value (the one with the 0x20 bit set).
15+
private readonly char _lowerCase, _upperCase;
16+
private readonly uint _lowerCaseUint;
17+
18+
public Any1CharPackedIgnoreCaseSearchValues(char value)
19+
{
20+
Debug.Assert((value | 0x20) == value);
21+
22+
_lowerCase = value;
23+
_upperCase = (char)(value & ~0x20);
24+
_lowerCaseUint = value;
25+
}
26+
27+
internal override char[] GetValues() =>
28+
[_upperCase, _lowerCase];
29+
30+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
31+
internal override bool ContainsCore(char value) =>
32+
(uint)(value | 0x20) == _lowerCaseUint;
33+
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
[CompExactlyDependsOn(typeof(Sse2))]
36+
internal override int IndexOfAny(ReadOnlySpan<char> span) =>
37+
PackedSpanHelpers.IndexOfAnyIgnoreCase(ref MemoryMarshal.GetReference(span), _lowerCase, span.Length);
38+
39+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
40+
[CompExactlyDependsOn(typeof(Sse2))]
41+
internal override int IndexOfAnyExcept(ReadOnlySpan<char> span) =>
42+
PackedSpanHelpers.IndexOfAnyExceptIgnoreCase(ref MemoryMarshal.GetReference(span), _lowerCase, span.Length);
43+
44+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
45+
internal override int LastIndexOfAny(ReadOnlySpan<char> span) =>
46+
span.LastIndexOfAny(_lowerCase, _upperCase);
47+
48+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
49+
internal override int LastIndexOfAnyExcept(ReadOnlySpan<char> span) =>
50+
span.LastIndexOfAnyExcept(_lowerCase, _upperCase);
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
6+
using System.Runtime.Intrinsics.X86;
7+
8+
namespace System.Buffers
9+
{
10+
internal sealed class Any1CharPackedSearchValues : SearchValues<char>
11+
{
12+
private readonly char _e0;
13+
14+
public Any1CharPackedSearchValues(char value) =>
15+
_e0 = value;
16+
17+
internal override char[] GetValues() =>
18+
[_e0];
19+
20+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
21+
internal override bool ContainsCore(char value) =>
22+
value == _e0;
23+
24+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
25+
[CompExactlyDependsOn(typeof(Sse2))]
26+
internal override int IndexOfAny(ReadOnlySpan<char> span) =>
27+
PackedSpanHelpers.IndexOf(ref MemoryMarshal.GetReference(span), _e0, span.Length);
28+
29+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
30+
[CompExactlyDependsOn(typeof(Sse2))]
31+
internal override int IndexOfAnyExcept(ReadOnlySpan<char> span) =>
32+
PackedSpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), _e0, span.Length);
33+
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
internal override int LastIndexOfAny(ReadOnlySpan<char> span) =>
36+
span.LastIndexOf(_e0);
37+
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
internal override int LastIndexOfAnyExcept(ReadOnlySpan<char> span) =>
40+
span.LastIndexOfAnyExcept(_e0);
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Numerics;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
8+
9+
#pragma warning disable 8500 // address of managed types
10+
11+
namespace System.Buffers
12+
{
13+
internal sealed class Any1SearchValues<T, TImpl> : SearchValues<T>
14+
where T : struct, IEquatable<T>
15+
where TImpl : struct, INumber<TImpl>
16+
{
17+
private readonly TImpl _e0;
18+
19+
public Any1SearchValues(ReadOnlySpan<TImpl> values)
20+
{
21+
Debug.Assert(Unsafe.SizeOf<T>() == Unsafe.SizeOf<TImpl>());
22+
Debug.Assert(values.Length == 1);
23+
_e0 = values[0];
24+
}
25+
26+
internal override unsafe T[] GetValues()
27+
{
28+
TImpl e0 = _e0;
29+
return new[] { *(T*)&e0 };
30+
}
31+
32+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
33+
internal override unsafe bool ContainsCore(T value) =>
34+
*(TImpl*)&value == _e0;
35+
36+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
37+
internal override int IndexOfAny(ReadOnlySpan<T> span) =>
38+
SpanHelpers.NonPackedIndexOfValueType<TImpl, SpanHelpers.DontNegate<TImpl>>(ref Unsafe.As<T, TImpl>(ref MemoryMarshal.GetReference(span)), _e0, span.Length);
39+
40+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
41+
internal override int IndexOfAnyExcept(ReadOnlySpan<T> span) =>
42+
SpanHelpers.NonPackedIndexOfValueType<TImpl, SpanHelpers.Negate<TImpl>>(ref Unsafe.As<T, TImpl>(ref MemoryMarshal.GetReference(span)), _e0, span.Length);
43+
44+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
45+
internal override int LastIndexOfAny(ReadOnlySpan<T> span) =>
46+
SpanHelpers.LastIndexOfValueType(ref Unsafe.As<T, TImpl>(ref MemoryMarshal.GetReference(span)), _e0, span.Length);
47+
48+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
49+
internal override int LastIndexOfAnyExcept(ReadOnlySpan<T> span) =>
50+
SpanHelpers.LastIndexOfAnyExceptValueType(ref Unsafe.As<T, TImpl>(ref MemoryMarshal.GetReference(span)), _e0, span.Length);
51+
}
52+
}

src/libraries/System.Private.CoreLib/src/System/SearchValues/Any2ByteSearchValues.cs

-41
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
using System.Runtime.Intrinsics.Arm;
8+
using System.Runtime.Intrinsics.Wasm;
9+
using System.Runtime.Intrinsics.X86;
10+
11+
namespace System.Buffers
12+
{
13+
internal sealed class Any2CharPackedIgnoreCaseSearchValues : SearchValues<char>
14+
{
15+
// While this most commonly applies to ASCII letters, it also works for other values that differ by 0x20 (e.g. "[]{}" => "{}").
16+
// _e0 and _e1 are therefore not necessarily lower case ASCII letters, but just the higher values (the ones with the 0x20 bit set).
17+
private readonly char _e0, _e1;
18+
private readonly uint _uint0, _uint1;
19+
private IndexOfAnyAsciiSearcher.AsciiState _state;
20+
21+
public Any2CharPackedIgnoreCaseSearchValues(char value0, char value1)
22+
{
23+
Debug.Assert((value0 | 0x20) == value0 && char.IsAscii(value0));
24+
Debug.Assert((value1 | 0x20) == value1 && char.IsAscii(value1));
25+
26+
(_e0, _e1) = (value0, value1);
27+
(_uint0, _uint1) = (value0, value1);
28+
IndexOfAnyAsciiSearcher.ComputeAsciiState([(char)(_e0 & ~0x20), _e0, (char)(_e1 & ~0x20), _e1], out _state);
29+
}
30+
31+
internal override char[] GetValues() =>
32+
[(char)(_e0 & ~0x20), _e0, (char)(_e1 & ~0x20), _e1];
33+
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
internal override bool ContainsCore(char value)
36+
{
37+
uint lowerCase = (uint)(value | 0x20);
38+
return lowerCase == _uint0 || lowerCase == _uint1;
39+
}
40+
41+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
42+
[CompExactlyDependsOn(typeof(Sse2))]
43+
internal override int IndexOfAny(ReadOnlySpan<char> span) =>
44+
PackedSpanHelpers.IndexOfAnyIgnoreCase(ref MemoryMarshal.GetReference(span), _e0, _e1, span.Length);
45+
46+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
47+
[CompExactlyDependsOn(typeof(Sse2))]
48+
internal override int IndexOfAnyExcept(ReadOnlySpan<char> span) =>
49+
PackedSpanHelpers.IndexOfAnyExceptIgnoreCase(ref MemoryMarshal.GetReference(span), _e0, _e1, span.Length);
50+
51+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
52+
[CompExactlyDependsOn(typeof(Ssse3))]
53+
[CompExactlyDependsOn(typeof(AdvSimd))]
54+
[CompExactlyDependsOn(typeof(PackedSimd))]
55+
internal override int LastIndexOfAny(ReadOnlySpan<char> span) =>
56+
IndexOfAnyAsciiSearcher.LastIndexOfAny<IndexOfAnyAsciiSearcher.DontNegate, IndexOfAnyAsciiSearcher.Default>(
57+
ref Unsafe.As<char, short>(ref MemoryMarshal.GetReference(span)), span.Length, ref _state);
58+
59+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
60+
[CompExactlyDependsOn(typeof(Ssse3))]
61+
[CompExactlyDependsOn(typeof(AdvSimd))]
62+
[CompExactlyDependsOn(typeof(PackedSimd))]
63+
internal override int LastIndexOfAnyExcept(ReadOnlySpan<char> span) =>
64+
IndexOfAnyAsciiSearcher.LastIndexOfAny<IndexOfAnyAsciiSearcher.Negate, IndexOfAnyAsciiSearcher.Default>(
65+
ref Unsafe.As<char, short>(ref MemoryMarshal.GetReference(span)), span.Length, ref _state);
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
6+
using System.Runtime.Intrinsics.X86;
7+
8+
namespace System.Buffers
9+
{
10+
internal sealed class Any2CharPackedSearchValues : SearchValues<char>
11+
{
12+
private readonly char _e0, _e1;
13+
14+
public Any2CharPackedSearchValues(char value0, char value1) =>
15+
(_e0, _e1) = (value0, value1);
16+
17+
internal override char[] GetValues() =>
18+
[_e0, _e1];
19+
20+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
21+
internal override bool ContainsCore(char value) =>
22+
value == _e0 || value == _e1;
23+
24+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
25+
[CompExactlyDependsOn(typeof(Sse2))]
26+
internal override int IndexOfAny(ReadOnlySpan<char> span) =>
27+
PackedSpanHelpers.IndexOfAny(ref MemoryMarshal.GetReference(span), _e0, _e1, span.Length);
28+
29+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
30+
[CompExactlyDependsOn(typeof(Sse2))]
31+
internal override int IndexOfAnyExcept(ReadOnlySpan<char> span) =>
32+
PackedSpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), _e0, _e1, span.Length);
33+
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
internal override int LastIndexOfAny(ReadOnlySpan<char> span) =>
36+
span.LastIndexOfAny(_e0, _e1);
37+
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
internal override int LastIndexOfAnyExcept(ReadOnlySpan<char> span) =>
40+
span.LastIndexOfAnyExcept(_e0, _e1);
41+
}
42+
}

src/libraries/System.Private.CoreLib/src/System/SearchValues/Any2CharSearchValues.cs

-51
This file was deleted.

0 commit comments

Comments
 (0)