From 87771585d10764f87be5004f4434195428fb1ddc Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Sat, 7 Jan 2023 13:21:13 +0000 Subject: [PATCH] Vectorize and optimize formatters --- src/NewId/NewIdFormatters/Base32Formatter.cs | 79 +++++++++++- .../NewIdFormatters/DashedHexFormatter.cs | 111 +++++++++++++---- src/NewId/NewIdFormatters/HexFormatter.cs | 54 +++++++-- src/NewId/NewIdFormatters/IntrinsicsHelper.cs | 114 ++++++++++++++++++ src/NewId/NewIdFormatters/ZBase32Formatter.cs | 87 ++++++++++++- 5 files changed, 402 insertions(+), 43 deletions(-) create mode 100644 src/NewId/NewIdFormatters/IntrinsicsHelper.cs diff --git a/src/NewId/NewIdFormatters/Base32Formatter.cs b/src/NewId/NewIdFormatters/Base32Formatter.cs index 3b303a4..e82c29d 100644 --- a/src/NewId/NewIdFormatters/Base32Formatter.cs +++ b/src/NewId/NewIdFormatters/Base32Formatter.cs @@ -1,7 +1,12 @@ 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 : @@ -9,13 +14,18 @@ public class Base32Formatter : { const string LowerCaseChars = "abcdefghijklmnopqrstuvwxyz234567"; const string UpperCaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - static readonly ThreadLocal _formatBuffer = new ThreadLocal(() => new char[26]); readonly string _chars; - + readonly bool _isCustom; + readonly bool _isUpperCase; +#if NET6_0_OR_GREATER + readonly Vector256 _lower; + readonly Vector256 _upper; +#endif public Base32Formatter(bool upperCase = false) { _chars = upperCase ? UpperCaseChars : LowerCaseChars; + _isUpperCase = upperCase; } public Base32Formatter(in string chars) @@ -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(chars); + var lower = MemoryMarshal.Read>(bytes); + var upper = MemoryMarshal.Read>(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++) @@ -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 source, Span 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 } } diff --git a/src/NewId/NewIdFormatters/DashedHexFormatter.cs b/src/NewId/NewIdFormatters/DashedHexFormatter.cs index 4829b2f..f9cd862 100644 --- a/src/NewId/NewIdFormatters/DashedHexFormatter.cs +++ b/src/NewId/NewIdFormatters/DashedHexFormatter.cs @@ -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) { @@ -19,12 +29,22 @@ 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; @@ -32,41 +52,41 @@ public string Format(in byte[] bytes) 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') @@ -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 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>(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(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); } } } diff --git a/src/NewId/NewIdFormatters/HexFormatter.cs b/src/NewId/NewIdFormatters/HexFormatter.cs index fe2bebd..1275db0 100644 --- a/src/NewId/NewIdFormatters/HexFormatter.cs +++ b/src/NewId/NewIdFormatters/HexFormatter.cs @@ -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>(bytes); + var hexVec = IntrinsicsHelper.EncodeBytesHex(inputVec, isUpper); + + var byteSpan = MemoryMarshal.Cast(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); } } } diff --git a/src/NewId/NewIdFormatters/IntrinsicsHelper.cs b/src/NewId/NewIdFormatters/IntrinsicsHelper.cs new file mode 100644 index 0000000..f501c95 --- /dev/null +++ b/src/NewId/NewIdFormatters/IntrinsicsHelper.cs @@ -0,0 +1,114 @@ +#if NET6_0_OR_GREATER +namespace MassTransit.NewIdFormatters +{ + using System.Diagnostics; + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.Intrinsics; + using System.Runtime.Intrinsics.X86; + + + public static class IntrinsicsHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Vector128ToCharUtf16(Vector128 value, Span destination) + { + var widened = Avx2.ConvertToVector256Int16(value).AsByte(); + MemoryMarshal.Write(destination, ref widened); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Vector256ToCharUtf16(Vector256 vec, Span destination) + { + Vector256 zero = Vector256.Zero; + Vector256 c0 = Avx2.UnpackLow(vec, zero); + Vector256 c1 = Avx2.UnpackHigh(vec, zero); + + Vector256 t0 = Avx2.Permute2x128(c0, c1, 0b_10_00_00); + Vector256 t1 = Avx2.Permute2x128(c0, c1, 0b_11_00_01); + + MemoryMarshal.Write(destination, ref t0); + MemoryMarshal.Write(destination[Vector256.Count..], ref t1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 GetByteLutFromChar(Vector256 value) + { + var shuffled = Avx2.Shuffle(value, Vector256.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 2, 4, 6, 8, 10, 12, 14)); + return Avx2.Permute4x64(shuffled.AsDouble(), 0b_11_00_11_00).AsByte(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 EncodeBytesHex(Vector128 vec, bool isUpper) + { + var lowerCharSet = Vector256.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + + var upperCharSet = Vector256.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F'); + + var x = Avx2.ConvertToVector256Int16(vec); + var highNibble = Avx2.ShiftLeftLogical(x, 8); + var lowNibble = Avx2.ShiftRightLogical(x, 4); + var values = Avx2.And(Avx2.Or(highNibble, lowNibble).AsByte(), Vector256.Create((byte)0x0F)); + return Avx2.Shuffle(isUpper ? upperCharSet : lowerCharSet, values); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EncodeBase32(ReadOnlySpan span, Span output, Vector256 lowLut, Vector256 upperLut) + { + Debug.Assert(span.Length >= 16); + Debug.Assert(output.Length >= 26); + + Span buffer = stackalloc byte[64]; + span.CopyTo(buffer[6..]); + + var inputVector = MemoryMarshal.Read>(buffer); + var splitVector = Split130Bits5x26(inputVector); + var encodedVector = EncodeValuesBase32(splitVector, lowLut, upperLut); + + Vector256ToCharUtf16(encodedVector, buffer); + + var byteSpan = MemoryMarshal.Cast(output); + buffer[..52].CopyTo(byteSpan); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Split130Bits5x26(Vector256 input) + { + var splitShuffle = Vector256.Create((byte) + 0x07, 0x06, 0x08, 0x07, 0x09, 0x08, 0x0A, 0x09, + 0x0C, 0x0B, 0x0D, 0x0C, 0x0E, 0x0D, 0x0F, 0x0E, + 0x01, 0x00, 0x02, 0x01, 0x03, 0x02, 0x04, 0x03, + 0x05, 0x06, + 0x07, 0x06, 0x08, 0x07, 0x09, 0x08); + + var splitM1 = Vector256.Create((ulong)0x0800_0200_0080_0020, 0x0800_0200_0080_0020, 0x0800_0200_0080_0020, 0x0800).AsUInt16(); + var splitM2 = Vector256.Create((ulong)0x0100_0040_0010_0004, 0x0100_0040_0010_0004, 0x0100_0040_0010_0004, 0x0100).AsInt16(); + + var maskM1 = Vector256.Create((ushort)0x00_1F); + var maskM2 = Vector256.Create((ushort)0x1F_00); + + var x1 = Avx2.Shuffle(input, splitShuffle).AsUInt16(); + var x2 = Avx2.MultiplyHigh(x1, splitM1); + var x3 = Avx2.MultiplyLow(x1.AsInt16(), splitM2).AsUInt16(); + var x4 = Avx2.And(x2, maskM1); + var x5 = Avx2.And(x3, maskM2); + + var x6 = Avx2.Or(x4, x5).AsByte(); + return x6; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 EncodeValuesBase32(Vector256 x, Vector256 lower, Vector256 upper) + { + var mask16 = Vector256.Create((sbyte)0x10); + var x1 = Avx2.Shuffle(lower, x); + var x2 = Avx2.Shuffle(upper, x); + var x3 = Avx2.CompareGreaterThan(mask16, x.AsSByte()).AsByte(); + + return Avx2.BlendVariable(x2, x1, x3); + } + } +} +#endif diff --git a/src/NewId/NewIdFormatters/ZBase32Formatter.cs b/src/NewId/NewIdFormatters/ZBase32Formatter.cs index e07e5f3..a45b35a 100644 --- a/src/NewId/NewIdFormatters/ZBase32Formatter.cs +++ b/src/NewId/NewIdFormatters/ZBase32Formatter.cs @@ -1,15 +1,96 @@ namespace MassTransit.NewIdFormatters { - public class ZBase32Formatter : - Base32Formatter + using System.Runtime.CompilerServices; + using System; +#if NET6_0_OR_GREATER + using System.Runtime.Intrinsics.X86; + using System.Runtime.Intrinsics; +#endif + + + public class ZBase32Formatter : INewIdFormatter { // taken from analysis done at http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt const string LowerCaseChars = "ybndrfg8ejkmcpqxot1uwisza345h769"; const string UpperCaseChars = "YBNDRFG8EJKMCPQXOT1UWISZA345H769"; + readonly string _chars; + readonly bool _isUpper; + public ZBase32Formatter(bool upperCase = false) - : base(upperCase ? UpperCaseChars : LowerCaseChars) { + _chars = upperCase ? UpperCaseChars : LowerCaseChars; + _isUpper = upperCase; + } + + public unsafe string Format(in byte[] bytes) + { +#if NET6_0_OR_GREATER + if (Avx2.IsSupported) + { + return string.Create(26, (bytes, _isUpper), (span, state) => + { + var (bytes, isUpperCase) = state; + + EncodeKnownCase(bytes, span, isUpperCase); + }); + } +#endif + var result = stackalloc char[26]; + + var offset = 0; + for (var i = 0; i < 3; i++) + { + var indexed = i * 5; + long number = (bytes[indexed] << 12) | (bytes[indexed + 1] << 4) | (bytes[indexed + 2] >> 4); + ConvertLongToBase32(result, offset, number, 4, _chars); + + offset += 4; + + number = ((bytes[indexed + 2] & 0xf) << 16) | (bytes[indexed + 3] << 8) | bytes[indexed + 4]; + ConvertLongToBase32(result, offset, number, 4, _chars); + offset += 4; + } + + ConvertLongToBase32(result, offset, bytes[15], 2, _chars); + + return new string(result, 0, 26); + } + + 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 EncodeKnownCase(ReadOnlySpan source, Span destination, bool isUpperCase) + { + #region lut + var upperCaseLow = Vector256.Create((byte)'Y', (byte)'B', (byte)'N', (byte)'D', (byte)'R', (byte)'F', (byte)'G', (byte)'8', (byte)'E', (byte)'J', (byte)'K', (byte)'M', (byte)'C', (byte)'P', (byte)'Q', (byte)'X', (byte)'Y', (byte)'B', (byte)'N', (byte)'D', (byte)'R', (byte)'F', (byte)'G', (byte)'8', (byte)'E', (byte)'J', (byte)'K', (byte)'M', (byte)'C', (byte)'P', (byte)'Q', (byte)'X'); + + var upperCaseHigh = Vector256.Create((byte)'O', (byte)'T', (byte)'1', (byte)'U', (byte)'W', (byte)'I', (byte)'S', (byte)'Z', (byte)'A', (byte)'3', (byte)'4', (byte)'5', (byte)'H', (byte)'7', (byte)'6', (byte)'9', (byte)'O', (byte)'T', (byte)'1', (byte)'U', (byte)'W', (byte)'I', (byte)'S', (byte)'Z', (byte)'A', (byte)'3', (byte)'4', (byte)'5', (byte)'H', (byte)'7', (byte)'6', (byte)'9'); + + var lowerCaseLow = Vector256.Create((byte)'y', (byte)'b', (byte)'n', (byte)'d', (byte)'r', (byte)'f', (byte)'g', (byte)'8', (byte)'e', (byte)'j', (byte)'k', (byte)'m', (byte)'c', (byte)'p', (byte)'q', (byte)'x', (byte)'y', (byte)'b', (byte)'n', (byte)'d', (byte)'r', (byte)'f', (byte)'g', (byte)'8', (byte)'e', (byte)'j', (byte)'k', (byte)'m', (byte)'c', (byte)'p', (byte)'q', (byte)'x'); + + var lowerCaseHigh = Vector256.Create((byte)'o', (byte)'t', (byte)'1', (byte)'u', (byte)'w', (byte)'i', (byte)'s', (byte)'z', (byte)'a', (byte)'3', (byte)'4', (byte)'5', (byte)'h', (byte)'7', (byte)'6', (byte)'9', (byte)'o', (byte)'t', (byte)'1', (byte)'u', (byte)'w', (byte)'i', (byte)'s', (byte)'z', (byte)'a', (byte)'3', (byte)'4', (byte)'5', (byte)'h', (byte)'7', (byte)'6', (byte)'9'); + #endregion + + if (isUpperCase) + { + IntrinsicsHelper.EncodeBase32(source, destination, upperCaseLow, upperCaseHigh); + } + else + { + IntrinsicsHelper.EncodeBase32(source, destination, lowerCaseLow, lowerCaseHigh); + } } +#endif } }