Skip to content

Commit

Permalink
Vectorize and optimize formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyMakkison authored and phatboyg committed Jan 7, 2023
1 parent 63a4966 commit 8777158
Show file tree
Hide file tree
Showing 5 changed files with 402 additions and 43 deletions.
79 changes: 73 additions & 6 deletions src/NewId/NewIdFormatters/Base32Formatter.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
namespace MassTransit.NewIdFormatters
{
using System;
using System.Threading;
#if NET6_0_OR_GREATER
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Runtime.CompilerServices;
#endif


public class Base32Formatter :
INewIdFormatter
{
const string LowerCaseChars = "abcdefghijklmnopqrstuvwxyz234567";
const string UpperCaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
static readonly ThreadLocal<char[]> _formatBuffer = new ThreadLocal<char[]>(() => new char[26]);

readonly string _chars;

readonly bool _isCustom;
readonly bool _isUpperCase;
#if NET6_0_OR_GREATER
readonly Vector256<byte> _lower;
readonly Vector256<byte> _upper;
#endif
public Base32Formatter(bool upperCase = false)
{
_chars = upperCase ? UpperCaseChars : LowerCaseChars;
_isUpperCase = upperCase;
}

public Base32Formatter(in string chars)
Expand All @@ -24,11 +34,42 @@ public Base32Formatter(in string chars)
throw new ArgumentException("The character string must be exactly 32 characters", nameof(chars));

_chars = chars;

#if NET6_0_OR_GREATER
if (Avx2.IsSupported && BitConverter.IsLittleEndian)
{
_isCustom = true;
var bytes = MemoryMarshal.Cast<char, byte>(chars);
var lower = MemoryMarshal.Read<Vector256<byte>>(bytes);
var upper = MemoryMarshal.Read<Vector256<byte>>(bytes[32..]);

_lower = IntrinsicsHelper.GetByteLutFromChar(lower);
_upper = IntrinsicsHelper.GetByteLutFromChar(upper);
}
#endif
}

public string Format(in byte[] bytes)
public unsafe string Format(in byte[] bytes)
{
var result = _formatBuffer.Value;
#if NET6_0_OR_GREATER
if (Avx2.IsSupported)
{
if (_isCustom)
{
return string.Create(26, (bytes, _lower, _upper), (span, state) =>
{
var (bytes, lower, upper) = state;
IntrinsicsHelper.EncodeBase32(bytes, span, lower, upper);
});
}
return string.Create(26, (bytes, _isUpperCase), (span, state) =>
{
var (bytes, isUpperCase) = state;
EncodeKnown(bytes, span, isUpperCase);
});
}
#endif
var result = stackalloc char[26];

var offset = 0;
for (var i = 0; i < 3; i++)
Expand All @@ -50,14 +91,40 @@ public string Format(in byte[] bytes)
return new string(result, 0, 26);
}

static void ConvertLongToBase32(in char[] buffer, int offset, long value, int count, string chars)
static unsafe void ConvertLongToBase32(char* buffer, int offset, long value, int count, string chars)
{
for (var i = count - 1; i >= 0; i--)
{
//30, 26, 25, 7, 24, 31, 4, 10, 23, 1, 2, 9, 17, 11, 4, 23, 7, 9, 16, 16, 15, 8, 16, 19, 2, 4,
var index = (int)(value % 32);
buffer[offset + i] = chars[index];
value /= 32;
}
}

#if NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EncodeKnown(ReadOnlySpan<byte> source, Span<char> destination, bool isUpperCase)
{
#region lut
var lowerCaseLow = Vector256.Create((byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o', (byte)'p', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o', (byte)'p');

var lowerCaseHigh = Vector256.Create((byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7');

var upperCaseLow = Vector256.Create((byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P');

var upperCaseHigh = Vector256.Create((byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7');
#endregion

if (isUpperCase)
{
IntrinsicsHelper.EncodeBase32(source, destination, upperCaseLow, upperCaseHigh);
}
else
{
IntrinsicsHelper.EncodeBase32(source, destination, lowerCaseLow, lowerCaseHigh);
}
}
#endif
}
}
111 changes: 89 additions & 22 deletions src/NewId/NewIdFormatters/DashedHexFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
namespace MassTransit.NewIdFormatters
{
using System;
#if NET6_0_OR_GREATER
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif


public class DashedHexFormatter :
INewIdFormatter
{
readonly int _alpha;
readonly uint _alpha;
readonly int _length;
readonly char _prefix;
readonly char _suffix;
const uint LowerCaseUInt = 0x2020U;

public DashedHexFormatter(char prefix = '\0', char suffix = '\0', bool upperCase = false)
{
Expand All @@ -19,54 +29,64 @@ public DashedHexFormatter(char prefix = '\0', char suffix = '\0', bool upperCase
_length = 38;
}

_alpha = upperCase ? 'A' : 'a';
_alpha = upperCase ? 0 : LowerCaseUInt;
}

public string Format(in byte[] bytes)
public unsafe string Format(in byte[] bytes)
{
var result = new char[_length];
#if NET6_0_OR_GREATER
if (Avx2.IsSupported && BitConverter.IsLittleEndian)
{
var isUpperCase = _alpha != LowerCaseUInt;
return string.Create(_length, (bytes, isUpperCase, _prefix, _suffix), (span, state) =>
{
EncodeVector256(span, state);
});
}
#endif
var result = stackalloc char[_length];

var i = 0;
var offset = 0;
if (_prefix != '\0')
result[offset++] = _prefix;
for (; i < 4; i++)
{
int value = bytes[i];
result[offset++] = HexToChar(value >> 4, _alpha);
result[offset++] = HexToChar(value, _alpha);
var value = bytes[i];
HexToChar(value, result, offset, _alpha);
offset+=2;
}

result[offset++] = '-';
for (; i < 6; i++)
{
int value = bytes[i];
result[offset++] = HexToChar(value >> 4, _alpha);
result[offset++] = HexToChar(value, _alpha);
var value = bytes[i];
HexToChar(value, result, offset, _alpha);
offset+=2;
}

result[offset++] = '-';
for (; i < 8; i++)
{
int value = bytes[i];
result[offset++] = HexToChar(value >> 4, _alpha);
result[offset++] = HexToChar(value, _alpha);
var value = bytes[i];
HexToChar(value, result, offset, _alpha);
offset+=2;
}

result[offset++] = '-';
for (; i < 10; i++)
{
int value = bytes[i];
result[offset++] = HexToChar(value >> 4, _alpha);
result[offset++] = HexToChar(value, _alpha);
var value = bytes[i];
HexToChar(value, result, offset, _alpha);
offset+=2;
}

result[offset++] = '-';
for (; i < 16; i++)
{
int value = bytes[i];
result[offset++] = HexToChar(value >> 4, _alpha);
result[offset++] = HexToChar(value, _alpha);
var value = bytes[i];
HexToChar(value, result, offset, _alpha);
offset+=2;
}

if (_suffix != '\0')
Expand All @@ -75,10 +95,57 @@ public string Format(in byte[] bytes)
return new string(result, 0, _length);
}

static char HexToChar(int value, int alpha)
#if NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EncodeVector256(Span<char> span, (byte[] bytes, bool, char _prefix, char _suffix) state)
{
value &= 0xf;
return (char)(value > 9 ? value - 10 + alpha : value + 0x30);
var (bytes, isUpper, prefix, suffix) = state;
var swizzle = Vector256.Create((byte)
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x80, 0x08, 0x09, 0x0a, 0x0b, 0x80, 0x0c, 0x0d,
0x80, 0x80, 0x80, 0x00, 0x01, 0x02, 0x03, 0x80,
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b);

var dash = Vector256.Create((byte)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2d, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00,
0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x2d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);

var inputVec = MemoryMarshal.Read<Vector128<byte>>(bytes);
var hexVec = IntrinsicsHelper.EncodeBytesHex(inputVec, isUpper);

var a1 = Avx2.Shuffle(hexVec, swizzle);
var a2 = Avx2.Or(a1, dash);

if (span.Length == 38)
{
span[0] = prefix;
span[^1] = suffix;
}

var charSpan = span.Length == 38 ? span[1..^1] : span;
var spanBytes = MemoryMarshal.Cast<char, byte>(charSpan);
IntrinsicsHelper.Vector256ToCharUtf16(a2, spanBytes);

spanBytes[32] = hexVec.GetElement(14);
spanBytes[34] = hexVec.GetElement(15);

spanBytes[64] = hexVec.GetElement(28);
spanBytes[66] = hexVec.GetElement(29);
spanBytes[68] = hexVec.GetElement(30);
spanBytes[70] = hexVec.GetElement(31);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static unsafe void HexToChar(byte value, char* buffer, int startingIndex, uint casing)
{
uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;

buffer[startingIndex + 1] = (char)(packedResult & 0xFF);
buffer[startingIndex] = (char)(packedResult >> 8);
}
}
}
54 changes: 42 additions & 12 deletions src/NewId/NewIdFormatters/HexFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,64 @@
namespace MassTransit.NewIdFormatters
{
using System.Diagnostics;
using System.Runtime.CompilerServices;
#if NET6_0_OR_GREATER
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif


public class HexFormatter :
INewIdFormatter
{
readonly int _alpha;
readonly uint _alpha;
const uint LowerCaseUInt = 0x2020U;

public HexFormatter(bool upperCase = false)
{
_alpha = upperCase ? 'A' : 'a';
_alpha = upperCase ? 0 : LowerCaseUInt;
}

public string Format(in byte[] bytes)
public unsafe string Format(in byte[] bytes)
{
var result = new char[32];
Debug.Assert(bytes.Length == 16);

#if NET6_0_OR_GREATER
if (Avx2.IsSupported && BitConverter.IsLittleEndian)
{
var isUpperCase = _alpha != LowerCaseUInt;
return string.Create(32, (bytes, isUpperCase), (span, state) =>
{
var (bytes, isUpper) = state;

var offset = 0;
for (var i = 0; i < 16; i++)
var inputVec = MemoryMarshal.Read<Vector128<byte>>(bytes);
var hexVec = IntrinsicsHelper.EncodeBytesHex(inputVec, isUpper);

var byteSpan = MemoryMarshal.Cast<char, byte>(span);
IntrinsicsHelper.Vector256ToCharUtf16(hexVec, byteSpan);
});
}
#endif
var result = stackalloc char[32];

for (int pos = 0; pos < bytes.Length; pos++)
{
var value = bytes[i];
result[offset++] = HexToChar(value >> 4, _alpha);
result[offset++] = HexToChar(value, _alpha);
HexToChar(bytes[pos], result, pos * 2, _alpha);
}

return new string(result, 0, 32);
}

static char HexToChar(int value, int alpha)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static unsafe void HexToChar(byte value, char* buffer, int startingIndex, uint casing)
{
value &= 0xf;
return (char)(value > 9 ? value - 10 + alpha : value + 0x30);
uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;

buffer[startingIndex + 1] = (char)(packedResult & 0xFF);
buffer[startingIndex] = (char)(packedResult >> 8);
}
}
}
Loading

0 comments on commit 8777158

Please sign in to comment.