Skip to content

Commit 2f1def8

Browse files
EgorBoam11
andauthored
Vectorize HexConverter.EncodeToUtf16 using SSSE3 (#44111)
Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com>
1 parent 796848a commit 2f1def8

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

src/libraries/Common/src/System/HexConverter.cs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
#nullable disable
55
using System.Diagnostics;
66
using System.Runtime.CompilerServices;
7+
#if SYSTEM_PRIVATE_CORELIB
8+
using System.Runtime.InteropServices;
9+
using System.Runtime.Intrinsics;
10+
using System.Runtime.Intrinsics.X86;
11+
using Internal.Runtime.CompilerServices;
12+
#endif
713

814
namespace System
915
{
@@ -84,11 +90,75 @@ public static void ToCharsBuffer(byte value, Span<char> buffer, int startingInde
8490
buffer[startingIndex] = (char)(packedResult >> 8);
8591
}
8692

93+
#if SYSTEM_PRIVATE_CORELIB
94+
private static void EncodeToUtf16_Ssse3(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing)
95+
{
96+
Debug.Assert(bytes.Length >= 4);
97+
nint pos = 0;
98+
99+
Vector128<byte> shuffleMask = Vector128.Create(
100+
0xFF, 0xFF, 0, 0xFF, 0xFF, 0xFF, 1, 0xFF,
101+
0xFF, 0xFF, 2, 0xFF, 0xFF, 0xFF, 3, 0xFF);
102+
103+
Vector128<byte> asciiTable = (casing == Casing.Upper) ?
104+
Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3',
105+
(byte)'4', (byte)'5', (byte)'6', (byte)'7',
106+
(byte)'8', (byte)'9', (byte)'A', (byte)'B',
107+
(byte)'C', (byte)'D', (byte)'E', (byte)'F') :
108+
Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3',
109+
(byte)'4', (byte)'5', (byte)'6', (byte)'7',
110+
(byte)'8', (byte)'9', (byte)'a', (byte)'b',
111+
(byte)'c', (byte)'d', (byte)'e', (byte)'f');
112+
113+
do
114+
{
115+
// Read 32bits from "bytes" span at "pos" offset
116+
uint block = Unsafe.ReadUnaligned<uint>(
117+
ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos));
118+
119+
// Calculate nibbles
120+
Vector128<byte> lowNibbles = Ssse3.Shuffle(
121+
Vector128.CreateScalarUnsafe(block).AsByte(), shuffleMask);
122+
Vector128<byte> highNibbles = Sse2.ShiftRightLogical(
123+
Sse2.ShiftRightLogical128BitLane(lowNibbles, 2).AsInt32(), 4).AsByte();
124+
125+
// Lookup the hex values at the positions of the indices
126+
Vector128<byte> indices = Sse2.And(
127+
Sse2.Or(lowNibbles, highNibbles), Vector128.Create((byte)0xF));
128+
Vector128<byte> hex = Ssse3.Shuffle(asciiTable, indices);
129+
130+
// The high bytes (0x00) of the chars have also been converted
131+
// to ascii hex '0', so clear them out.
132+
hex = Sse2.And(hex, Vector128.Create((ushort)0xFF).AsByte());
133+
134+
// Save to "chars" at pos*2 offset
135+
Unsafe.WriteUnaligned(
136+
ref Unsafe.As<char, byte>(
137+
ref Unsafe.Add(ref MemoryMarshal.GetReference(chars), pos * 2)), hex);
138+
139+
pos += 4;
140+
} while (pos < bytes.Length - 3);
141+
142+
// Process trailing elements (bytes.Length % 4)
143+
for (; pos < bytes.Length; pos++)
144+
{
145+
ToCharsBuffer(Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos), chars, (int)pos * 2, casing);
146+
}
147+
}
148+
#endif
149+
87150
public static void EncodeToUtf16(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing = Casing.Upper)
88151
{
89152
Debug.Assert(chars.Length >= bytes.Length * 2);
90153

91-
for (int pos = 0; pos < bytes.Length; ++pos)
154+
#if SYSTEM_PRIVATE_CORELIB
155+
if (Ssse3.IsSupported && bytes.Length >= 4)
156+
{
157+
EncodeToUtf16_Ssse3(bytes, chars, casing);
158+
return;
159+
}
160+
#endif
161+
for (int pos = 0; pos < bytes.Length; pos++)
92162
{
93163
ToCharsBuffer(bytes[pos], chars, pos * 2, casing);
94164
}

src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
using System.Text;
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+
24
using Xunit;
5+
using System.Text;
6+
using System.Collections.Generic;
37

48
namespace System.Tests
59
{
@@ -62,5 +66,45 @@ public static unsafe void InputTooLarge()
6266
{
6367
AssertExtensions.Throws<ArgumentOutOfRangeException>("bytes", () => Convert.ToHexString(new ReadOnlySpan<byte>((void*)0, Int32.MaxValue)));
6468
}
69+
70+
public static IEnumerable<object[]> ToHexStringTestData()
71+
{
72+
yield return new object[] { new byte[0], "" };
73+
yield return new object[] { new byte[] { 0x00 }, "00" };
74+
yield return new object[] { new byte[] { 0x01 }, "01" };
75+
yield return new object[] { new byte[] { 0xFF }, "FF" };
76+
yield return new object[] { new byte[] { 0x00, 0x00 }, "0000" };
77+
yield return new object[] { new byte[] { 0xAB, 0xCD }, "ABCD" };
78+
yield return new object[] { new byte[] { 0xFF, 0xFF }, "FFFF" };
79+
yield return new object[] { new byte[] { 0x00, 0x00, 0x00 }, "000000" };
80+
yield return new object[] { new byte[] { 0x01, 0x02, 0x03 }, "010203" };
81+
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF }, "FFFFFF" };
82+
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00 }, "00000000" };
83+
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12 }, "ABCDEF12" };
84+
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFF" };
85+
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00 }, "0000000000" };
86+
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34 }, "ABCDEF1234" };
87+
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFF" };
88+
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "000000000000" };
89+
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56 }, "ABCDEF123456" };
90+
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFF" };
91+
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "00000000000000" };
92+
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78 }, "ABCDEF12345678" };
93+
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFF" };
94+
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "0000000000000000" };
95+
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90 }, "ABCDEF1234567890" };
96+
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFFFF" };
97+
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "000000000000000000" };
98+
yield return new object[] { new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }, "010203040506070809" };
99+
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFFFFFF" };
100+
}
101+
102+
[Theory]
103+
[MemberData(nameof(ToHexStringTestData))]
104+
public static unsafe void ToHexString(byte[] input, string expected)
105+
{
106+
string actual = Convert.ToHexString(input);
107+
Assert.Equal(expected, actual);
108+
}
65109
}
66110
}

0 commit comments

Comments
 (0)