Skip to content
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

Vectorized common String.Split() paths #38001

Merged
merged 28 commits into from
Apr 7, 2021
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
52ad5ac
Vectorized String.Split()
bbartels Jun 17, 2020
10417a3
Merge branch 'master' of https://github.com/dotnet/runtime into Vecto…
bbartels Jun 17, 2020
140c2ce
Fixed variable name
bbartels Jun 17, 2020
58918a1
Update src/libraries/System.Private.CoreLib/src/System/String.Manipul…
bbartels Jun 17, 2020
be469ff
Update src/libraries/System.Private.CoreLib/src/System/String.Manipul…
bbartels Jun 17, 2020
1969c67
Merge branch 'master' of https://github.com/dotnet/runtime into Vecto…
bbartels Jun 17, 2020
79b32be
Merge branch 'VectorizedSplit' of https://github.com/bbartels/runtime…
bbartels Jun 17, 2020
44db429
Applied Review Feedback
bbartels Jun 17, 2020
995719b
Update src/libraries/System.Private.CoreLib/src/System/String.Manipul…
bbartels Jun 18, 2020
65cba0c
Applied Review Feedback
bbartels Jun 18, 2020
b437551
Merge branch 'master' of https://github.com/dotnet/runtime into Vecto…
bbartels Jun 18, 2020
0cdcfd7
Merge branch 'VectorizedSplit' of https://github.com/bbartels/runtime…
bbartels Jun 18, 2020
3d2a645
Built branchless version with help of @gfoidl
bbartels Jun 18, 2020
d9a8640
Update src/libraries/System.Private.CoreLib/src/System/String.Manipul…
bbartels Jun 18, 2020
dc8926e
Merge branch 'master' of https://github.com/dotnet/runtime into Vecto…
bbartels Jun 18, 2020
1b73a5d
Merge branch 'VectorizedSplit' of https://github.com/bbartels/runtime…
bbartels Jun 18, 2020
2b0b8f8
Removed nullable separator parameters
bbartels Jun 18, 2020
cc4ae1f
Refactored MakeSeparatorList
bbartels Jun 18, 2020
11c968b
Fixed mistakenly removed comments
bbartels Jun 18, 2020
707871a
Removed dependency on BMI2 PEXT instruction
bbartels Jun 19, 2020
19e57f0
Fixed mistaken use of Vector<ushort>.Count
bbartels Jun 19, 2020
82329c6
Lowered string.Split() vectorization dependency from Avx2 to SSE41
bbartels Jun 24, 2020
7d1fe48
Merge branch 'master' into VectorizedSplit
bbartels Jan 25, 2021
aa7454a
Added Sse.IsSupported check
bbartels Jan 25, 2021
e0f8337
Updated IsSupported check to match highest used ISA
bbartels Mar 22, 2021
14eaf11
Merge branch 'main' of https://github.com/dotnet/runtime into Vectori…
bbartels Mar 26, 2021
10da409
Merge branch 'VectorizedSplit' of https://github.com/bbartels/runtime…
bbartels Mar 26, 2021
d55cf92
Fixed possible cause for failing tests
bbartels Mar 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 115 additions & 54 deletions src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Text;
using Internal.Runtime.CompilerServices;

Expand Down Expand Up @@ -1491,78 +1493,137 @@ private string[] SplitWithPostProcessing(ReadOnlySpan<int> sepList, ReadOnlySpan
/// <param name="sepListBuilder"><see cref="ValueListBuilder{T}"/> to store indexes</param>
private void MakeSeparatorList(ReadOnlySpan<char> separators, ref ValueListBuilder<int> sepListBuilder)
{
char sep0, sep1, sep2;

switch (separators.Length)
// Special-case no separators to mean any whitespace is a separator.
if (separators.Length == 0)
{
// Special-case no separators to mean any whitespace is a separator.
case 0:
for (int i = 0; i < Length; i++)
for (int i = 0; i < Length; i++)
{
if (char.IsWhiteSpace(this[i]))
{
if (char.IsWhiteSpace(this[i]))
{
sepListBuilder.Append(i);
}
sepListBuilder.Append(i);
}
break;
}
}

// Special-case the common cases of 1, 2, and 3 separators, with manual comparisons against each separator.
case 1:
sep0 = separators[0];
for (int i = 0; i < Length; i++)
// Special-case the common cases of 1, 2, and 3 separators, with manual comparisons against each separator.
else if (separators.Length <= 3)
{
char sep0, sep1, sep2;
sep0 = separators[0];
sep1 = separators.Length > 1 ? separators[1] : sep0;
sep2 = separators.Length > 2 ? separators[2] : sep1;

if (Length >= 16 && Sse41.IsSupported)
{
MakeSeparatorListVectorized(ref sepListBuilder, sep0, sep1, sep2);
return;
}

for (int i = 0; i < Length; i++)
{
char c = this[i];
if (c == sep0 || c == sep1 || c == sep2)
{
if (this[i] == sep0)
{
sepListBuilder.Append(i);
}
sepListBuilder.Append(i);
}
break;
case 2:
sep0 = separators[0];
sep1 = separators[1];
}
}

// Handle > 3 separators with a probabilistic map, ala IndexOfAny.
// This optimizes for chars being unlikely to match a separator.
else
{
unsafe
{
ProbabilisticMap map = default;
uint* charMap = (uint*)&map;
InitializeProbabilisticMap(charMap, separators);

for (int i = 0; i < Length; i++)
{
char c = this[i];
if (c == sep0 || c == sep1)
if (IsCharBitSet(charMap, (byte)c) && IsCharBitSet(charMap, (byte)(c >> 8)) &&
separators.Contains(c))
{
sepListBuilder.Append(i);
}
}
break;
case 3:
sep0 = separators[0];
sep1 = separators[1];
sep2 = separators[2];
for (int i = 0; i < Length; i++)
}
}
}

private void MakeSeparatorListVectorized(ref ValueListBuilder<int> sepListBuilder, char c, char c2, char c3)
{
// Redundant test so we won't prejit remainder of this method
// on platforms without SSE.
if (!Sse41.IsSupported)
{
throw new PlatformNotSupportedException();
}

// Constant that allows for the truncation of 16-bit (FFFF/0000) values within a register to 4-bit (F/0)
Vector128<byte> shuffleConstant = Vector128.Create(0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
bbartels marked this conversation as resolved.
Show resolved Hide resolved

Vector128<ushort> v1 = Vector128.Create(c);
Vector128<ushort> v2 = Vector128.Create(c2);
Vector128<ushort> v3 = Vector128.Create(c3);

ref char c0 = ref MemoryMarshal.GetReference(this.AsSpan());
int cond = Length & -Vector128<ushort>.Count;
int i = 0;

for (; i < cond; i += Vector128<ushort>.Count)
{
Vector128<ushort> charVector = ReadVector(ref c0, i);
Vector128<ushort> cmp = Sse2.CompareEqual(charVector, v1);

cmp = Sse2.Or(Sse2.CompareEqual(charVector, v2), cmp);
cmp = Sse2.Or(Sse2.CompareEqual(charVector, v3), cmp);

if (Sse41.TestZ(cmp, cmp)) { continue; }

Vector128<byte> mask = Sse2.ShiftRightLogical(cmp.AsUInt64(), 4).AsByte();
bbartels marked this conversation as resolved.
Show resolved Hide resolved
mask = Ssse3.Shuffle(mask, shuffleConstant);

uint lowBits = Sse2.ConvertToUInt32(mask.AsUInt32());
mask = Sse2.ShiftRightLogical(mask.AsUInt64(), 32).AsByte();
uint highBits = Sse2.ConvertToUInt32(mask.AsUInt32());

for (int idx = i; lowBits != 0; idx++)
bbartels marked this conversation as resolved.
Show resolved Hide resolved
{
if ((lowBits & 0xF) != 0)
{
char c = this[i];
if (c == sep0 || c == sep1 || c == sep2)
{
sepListBuilder.Append(i);
}
sepListBuilder.Append(idx);
}
break;

// Handle > 3 separators with a probabilistic map, ala IndexOfAny.
// This optimizes for chars being unlikely to match a separator.
default:
unsafe
{
ProbabilisticMap map = default;
uint* charMap = (uint*)&map;
InitializeProbabilisticMap(charMap, separators);
lowBits >>= 8;
}

for (int i = 0; i < Length; i++)
{
char c = this[i];
if (IsCharBitSet(charMap, (byte)c) && IsCharBitSet(charMap, (byte)(c >> 8)) &&
separators.Contains(c))
{
sepListBuilder.Append(i);
}
}
for (int idx = i + 4; highBits != 0; idx++)
{
if ((highBits & 0xF) != 0)
{
sepListBuilder.Append(idx);
}
break;

highBits >>= 8;
}
}

for (; i < Length; i++)
{
char curr = Unsafe.Add(ref c0, (IntPtr)(uint)i);
if (curr == c || curr == c2 || curr == c3)
{
sepListBuilder.Append(i);
}
}

static Vector128<ushort> ReadVector(ref char c0, int offset)
{
ref char ci = ref Unsafe.Add(ref c0, (IntPtr)(uint)offset);
ref byte b = ref Unsafe.As<char, byte>(ref ci);
return Unsafe.ReadUnaligned<Vector128<ushort>>(ref b);
}
}

Expand Down