From 5956d49d49a8edc1a0c821cdfba536045d9b83cd Mon Sep 17 00:00:00 2001 From: sewer56 Date: Thu, 16 Nov 2023 22:26:58 +0000 Subject: [PATCH] Fixed: Performance Regression with <16 Byte Strings in ToLower/ToUpper --- .../Extensions/StringExtensions.cs | 2 +- .../Algorithms/UnstableStringHashLower.cs | 36 +--- .../System/Globalization/TextInfo.cs | 192 +++++++++++++++--- .../System/Text/Unicode/UnicodeUtility.cs | 8 + .../System/Text/Unicode/Utf16Utility.cs | 44 +++- .../System/Text/Unicode/Utf8Utility.cs | 113 +++++++++++ 6 files changed, 341 insertions(+), 54 deletions(-) create mode 100644 src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/UnicodeUtility.cs create mode 100644 src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/Utf8Utility.cs diff --git a/src/Reloaded.Memory/Extensions/StringExtensions.cs b/src/Reloaded.Memory/Extensions/StringExtensions.cs index ba739c9..60d8e72 100644 --- a/src/Reloaded.Memory/Extensions/StringExtensions.cs +++ b/src/Reloaded.Memory/Extensions/StringExtensions.cs @@ -168,7 +168,7 @@ public static int Count(this string text, char c) /// [SuppressMessage("ReSharper", "InconsistentNaming")] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void ToLowerInvariantFast(this ReadOnlySpan text, Span target) => TextInfo.ChangeCase(text, target); + public static void ToLowerInvariantFast(this ReadOnlySpan text, Span target) => TextInfo.ChangeCase(text, target); /// /// Converts the given string to upper case (invariant casing), using the fastest possible implementation. diff --git a/src/Reloaded.Memory/Internals/Algorithms/UnstableStringHashLower.cs b/src/Reloaded.Memory/Internals/Algorithms/UnstableStringHashLower.cs index c9734ab..3960042 100644 --- a/src/Reloaded.Memory/Internals/Algorithms/UnstableStringHashLower.cs +++ b/src/Reloaded.Memory/Internals/Algorithms/UnstableStringHashLower.cs @@ -1,9 +1,9 @@ using System.Diagnostics.CodeAnalysis; using Reloaded.Memory.Exceptions; using Reloaded.Memory.Utilities; +using static Reloaded.Memory.Internals.Backports.System.Text.Unicode.Utf16Utility; #if NET7_0_OR_GREATER using Reloaded.Memory.Extensions; -using Reloaded.Memory.Internals.Backports.System.Text.Unicode; using Reloaded.Memory.Internals.Backports.System.Globalization; using System.Numerics; using System.Runtime.Intrinsics; @@ -131,28 +131,28 @@ internal static unsafe UIntPtr UnstableHashVec128Lower(this ReadOnlySpan t length -= (sizeof(Vector128) / sizeof(char)) * 4; Vector128 v0 = Vector128.Load((ulong*)ptr); - if (!Utf16Utility.AllCharsInVector128AreAscii(v0)) + if (!AllCharsInVector128AreAscii(v0)) goto NotAscii; hash1_128 = Vector128.Xor(hash1_128, Vector128.BitwiseOr(v0, toLower)); hash1_128 = Vector128.Multiply(hash1_128.AsUInt32(), prime.AsUInt32()).AsUInt64(); v0 = Vector128.Load((ulong*)ptr + 2); - if (!Utf16Utility.AllCharsInVector128AreAscii(v0)) + if (!AllCharsInVector128AreAscii(v0)) goto NotAscii; hash2_128 = Vector128.Xor(hash2_128, Vector128.BitwiseOr(v0, toLower)); hash2_128 = Vector128.Multiply(hash2_128.AsUInt32(), prime.AsUInt32()).AsUInt64(); v0 = Vector128.Load((ulong*)ptr + 4); - if (!Utf16Utility.AllCharsInVector128AreAscii(v0)) + if (!AllCharsInVector128AreAscii(v0)) goto NotAscii; hash1_128 = Vector128.Xor(hash1_128, Vector128.BitwiseOr(v0, toLower)); hash1_128 = Vector128.Multiply(hash1_128.AsUInt32(), prime.AsUInt32()).AsUInt64(); v0 = Vector128.Load((ulong*)ptr + 6); - if (!Utf16Utility.AllCharsInVector128AreAscii(v0)) + if (!AllCharsInVector128AreAscii(v0)) goto NotAscii; hash2_128 = Vector128.Xor(hash2_128, Vector128.BitwiseOr(v0, toLower)); @@ -165,7 +165,7 @@ internal static unsafe UIntPtr UnstableHashVec128Lower(this ReadOnlySpan t length -= sizeof(Vector128) / sizeof(char); Vector128 v0 = Vector128.Load((ulong*)ptr); - if (!Utf16Utility.AllCharsInVector128AreAscii(v0)) + if (!AllCharsInVector128AreAscii(v0)) goto NotAscii; hash1_128 = Vector128.Xor(hash1_128, Vector128.BitwiseOr(v0, toLower)); @@ -251,28 +251,28 @@ internal static unsafe UIntPtr UnstableHashAvx2Lower(this ReadOnlySpan tex length -= (sizeof(Vector256) / sizeof(char)) * 4; Vector256 v0 = Vector256.Load((ulong*)ptr); - if (!Utf16Utility.AllCharsInVector256AreAscii(v0)) + if (!AllCharsInVector256AreAscii(v0)) goto NotAscii; hash1Avx = Avx2.Xor(hash1Avx, Avx2.Or(v0, toLower)); hash1Avx = Avx2.Multiply(hash1Avx.AsUInt32(), prime.AsUInt32()); v0 = Vector256.Load((ulong*)ptr + 4); - if (!Utf16Utility.AllCharsInVector256AreAscii(v0)) + if (!AllCharsInVector256AreAscii(v0)) goto NotAscii; hash2Avx = Avx2.Xor(hash2Avx, Avx2.Or(v0, toLower)); hash2Avx = Avx2.Multiply(hash2Avx.AsUInt32(), prime.AsUInt32()); v0 = Vector256.Load((ulong*)ptr + 8); - if (!Utf16Utility.AllCharsInVector256AreAscii(v0)) + if (!AllCharsInVector256AreAscii(v0)) goto NotAscii; hash1Avx = Avx2.Xor(hash1Avx, Avx2.Or(v0, toLower)); hash1Avx = Avx2.Multiply(hash1Avx.AsUInt32(), prime.AsUInt32()); v0 = Vector256.Load((ulong*)ptr + 12); - if (!Utf16Utility.AllCharsInVector256AreAscii(v0)) + if (!AllCharsInVector256AreAscii(v0)) goto NotAscii; hash2Avx = Avx2.Xor(hash2Avx, Avx2.Or(v0, toLower)); @@ -285,7 +285,7 @@ internal static unsafe UIntPtr UnstableHashAvx2Lower(this ReadOnlySpan tex length -= sizeof(Vector256) / sizeof(char); Vector256 v0 = Vector256.Load((ulong*)ptr); - if (!Utf16Utility.AllCharsInVector256AreAscii(v0)) + if (!AllCharsInVector256AreAscii(v0)) goto NotAscii; hash1Avx = Avx2.Xor(hash1Avx, Avx2.Or(v0, toLower)); @@ -573,20 +573,6 @@ internal static unsafe UIntPtr UnstableHashNonVectorLower64(this ReadOnlySpan - /// Returns true iff the 64-bit nuint represents all ASCII UTF-16 characters in machine endianness. - /// - /// The value to assert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe bool AllCharsInULongAreAscii(ulong value) => (value & ~0x007F_007F_007F_007Fu) == 0; - - /// - /// Returns true iff the 32-bit nuint represents all ASCII UTF-16 characters in machine endianness. - /// - /// The value to assert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe bool AllCharsInUIntAreAscii(uint value) => (value & ~0x007F_007F) == 0; - #if (NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER) && !NET7_0_OR_GREATER private unsafe struct ChangeCaseParams(char* first, int length) { diff --git a/src/Reloaded.Memory/Internals/Backports/System/Globalization/TextInfo.cs b/src/Reloaded.Memory/Internals/Backports/System/Globalization/TextInfo.cs index e5302f7..a0b5a01 100644 --- a/src/Reloaded.Memory/Internals/Backports/System/Globalization/TextInfo.cs +++ b/src/Reloaded.Memory/Internals/Backports/System/Globalization/TextInfo.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.Intrinsics; +using Reloaded.Memory.Exceptions; using Reloaded.Memory.Internals.Backports.System.Text.Unicode; // ReSharper disable UnusedType.Global @@ -53,29 +54,7 @@ public static void ChangeCase(ReadOnlySpan source, Span else if (Vector128.IsHardwareAccelerated && source.Length >= Vector128.Count) ChangeCase_Vector128(ref MemoryMarshal.GetReference(source), ref MemoryMarshal.GetReference(destination), source.Length); else - { - var toUpper = typeof(TConversion) == typeof(ToUpperConversion); - ChangeCase_Fallback(source, destination, toUpper); - } - } - - private static void ChangeCase_Fallback(ReadOnlySpan source, Span destination, bool toUpper) - { - try - { - if (toUpper) - source.ToUpperInvariant(destination); - else - source.ToLowerInvariant(destination); - } - catch (InvalidOperationException) - { - // Overlapping buffers - if (toUpper) - source.ToString().AsSpan().ToUpperInvariant(destination); - else - source.ToString().AsSpan().ToLowerInvariant(destination); - } + ChangeCase_Under16B(source, destination); } /// @@ -134,7 +113,7 @@ public static void ChangeCase_Vector256(ref char source, ref char d var length = charCount - (int)i; var srcSpan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref source, i), length); var dstSpan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref destination, i), length); - ChangeCase_Fallback(srcSpan, dstSpan, toUpper); + ChangeCase_Fallback(srcSpan, dstSpan); } /// @@ -193,7 +172,7 @@ public static void ChangeCase_Vector128(ref char source, ref char d var length = charCount - (int)i; var srcSpan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref source, i), length); var dstSpan = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref destination, i), length); - ChangeCase_Fallback(srcSpan, dstSpan, toUpper); + ChangeCase_Fallback(srcSpan, dstSpan); } // A dummy struct that is used for 'ToUpper' in generic parameters @@ -207,5 +186,168 @@ private unsafe struct ChangeCaseParams(char* first, int length) public readonly char* First = first; public readonly int Length = length; }; + + /// + /// An implementation of Change Case for inputs up to 16 bytes. + /// Custom, not taken from runtime. + /// + /// Source span. + /// Destination span. + public static unsafe void ChangeCase_Under16B(ReadOnlySpan source, Span destination) where TConversion : struct + { + var length = source.Length; + + // JIT will treat this as a constant in release builds + var toUpper = typeof(TConversion) == typeof(ToUpperConversion); + + // 32 bit implementation + if (sizeof(nuint) == 4) + { + ref uint srcNuintPtr = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dstNuintPtr = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + + // 32 bit implementation + // range: 0-7 chars (0-14 bytes) + // keep converting 4 bytes at once until we are left with 0-2 + while (length >= 2) + { + length -= 2; + if (!Utf16Utility.AllCharsInUIntAreAscii(srcNuintPtr)) + goto NotAscii; + + dstNuintPtr = toUpper + ? Utf8Utility.ConvertAllAsciiBytesInUInt32ToUppercase(srcNuintPtr) + : Utf8Utility.ConvertAllAsciiBytesInUInt32ToLowercase(srcNuintPtr); + + srcNuintPtr = ref Unsafe.Add(ref srcNuintPtr, 1); + dstNuintPtr = ref Unsafe.Add(ref dstNuintPtr, 1); + } + + ref char srcCharPtr = ref Unsafe.As(ref srcNuintPtr); + ref char dstCharPtr = ref Unsafe.As(ref dstNuintPtr); + if (length > 0) + { + if (toUpper) + { + if (UnicodeUtility.IsInRangeInclusive(srcCharPtr, 'a', 'z')) + { + dstCharPtr = (char)(srcCharPtr - (char)0x20u); + return; + } + + goto NotAscii; + } + else + { + if (UnicodeUtility.IsInRangeInclusive(srcCharPtr, 'A', 'Z')) + { + dstCharPtr = (char)(srcCharPtr + (char)0x20u); + return; + } + + goto NotAscii; + } + } + + return; + } + + // 64 bit implementation + if (sizeof(nuint) == 8) + { + ref nuint srcNuintPtr = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref nuint dstNuintPtr = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + + // range: 0-7 chars (0-14 bytes) + // -4 chars + if (length >= 4) + { + length -= sizeof(nuint) / sizeof(char); + if (!Utf16Utility.AllCharsInNuintAreAscii(srcNuintPtr)) + goto NotAscii; + + dstNuintPtr = toUpper + ? (nuint)Utf8Utility.ConvertAllAsciiBytesInUInt64ToUppercase(srcNuintPtr) + : (nuint)Utf8Utility.ConvertAllAsciiBytesInUInt64ToLowercase(srcNuintPtr); + + srcNuintPtr = ref Unsafe.Add(ref srcNuintPtr, 1); + dstNuintPtr = ref Unsafe.Add(ref dstNuintPtr, 1); + } + + // -2 chars + ref uint srcUIntPtr = ref Unsafe.As(ref srcNuintPtr); + ref uint dstUIntPtr = ref Unsafe.As(ref dstNuintPtr); + if (length >= 2) + { + length -= 2; + if (!Utf16Utility.AllCharsInUIntAreAscii(srcUIntPtr)) + goto NotAscii; + + dstUIntPtr = toUpper + ? Utf8Utility.ConvertAllAsciiBytesInUInt32ToUppercase(srcUIntPtr) + : Utf8Utility.ConvertAllAsciiBytesInUInt32ToLowercase(srcUIntPtr); + + srcUIntPtr = ref Unsafe.Add(ref srcUIntPtr, 1); + dstUIntPtr = ref Unsafe.Add(ref dstUIntPtr, 1); + } + + // -1 char + ref char srcCharPtr = ref Unsafe.As(ref srcUIntPtr); + ref char dstCharPtr = ref Unsafe.As(ref dstUIntPtr); + if (length >= 1) + { + if (toUpper) + { + if (UnicodeUtility.IsInRangeInclusive(srcCharPtr, 'a', 'z')) + { + dstCharPtr = (char)(srcCharPtr - (char)0x20u); + return; + } + + goto NotAscii; + } + else + { + if (UnicodeUtility.IsInRangeInclusive(srcCharPtr, 'A', 'Z')) + { + dstCharPtr = (char)(srcCharPtr + (char)0x20u); + return; + } + + goto NotAscii; + } + } + + return; + } + + ThrowHelpers.ThrowArchitectureNotSupportedException(); + return; + + NotAscii: + ChangeCase_Fallback(source, destination); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ChangeCase_Fallback(ReadOnlySpan source, Span destination) + { + // JIT will treat this as a constant in release builds + var toUpper = typeof(TConversion) == typeof(ToUpperConversion); + try + { + if (toUpper) + source.ToUpperInvariant(destination); + else + source.ToLowerInvariant(destination); + } + catch (InvalidOperationException) + { + // Overlapping buffers + if (toUpper) + source.ToString().AsSpan().ToUpperInvariant(destination); + else + source.ToString().AsSpan().ToLowerInvariant(destination); + } + } } #endif diff --git a/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/UnicodeUtility.cs b/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/UnicodeUtility.cs new file mode 100644 index 0000000..8ab0765 --- /dev/null +++ b/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/UnicodeUtility.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +[ExcludeFromCodeCoverage] // Taken from Runtime +internal static class UnicodeUtility +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsInRangeInclusive(uint value, uint lowerBound, uint upperBound) => (value - lowerBound) <= (upperBound - lowerBound); +} diff --git a/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/Utf16Utility.cs b/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/Utf16Utility.cs index 7a6fb2e..93d77c8 100644 --- a/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/Utf16Utility.cs +++ b/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/Utf16Utility.cs @@ -2,17 +2,55 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; +using Reloaded.Memory.Exceptions; + #if NET7_0_OR_GREATER using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.Intrinsics; +#endif namespace Reloaded.Memory.Internals.Backports.System.Text.Unicode; #pragma warning disable RCS1141 // Add parameter to documentation comment. -[ExcludeFromCodeCoverage(Justification = "Taken from .NET Runtime")] +[ExcludeFromCodeCoverage] // "Taken from .NET Runtime" internal static class Utf16Utility { + /// + /// Returns true iff the 64-bit nuint represents all ASCII UTF-16 characters in machine endianness. + /// + /// The value to assert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe bool AllCharsInNuintAreAscii(nuint value) + { + switch (sizeof(nuint)) + { + // Replaced with concrete implementation by JIT. + case 4: + return (value & ~0x007F_007Fu) == 0; + case 8: + return (value & ~0x007F_007F_007F_007Fu) == 0; + default: + ThrowHelpers.ThrowArchitectureNotSupportedException(); + return false; + } + } + + /// + /// Returns true iff the 64-bit nuint represents all ASCII UTF-16 characters in machine endianness. + /// + /// The value to assert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool AllCharsInULongAreAscii(ulong value) => (value & ~0x007F_007F_007F_007Fu) == 0; + + /// + /// Returns true iff the 32-bit nuint represents all ASCII UTF-16 characters in machine endianness. + /// + /// The value to assert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool AllCharsInUIntAreAscii(uint value) => (value & ~0x007F_007F) == 0; + +#if NET7_0_OR_GREATER /// /// Returns true iff the Vector128 represents 8 ASCII UTF-16 characters in machine endianness. /// @@ -114,5 +152,5 @@ internal static Vector256 Vector256AsciiToUppercase(Vector256 ve // Drop the lowercase indicator (0x20 bit) from all a-z letters return vec - Vector256.AndNot(Vector256.Create((sbyte)0x20), combIndicator1).AsUInt16(); } -} #endif +} diff --git a/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/Utf8Utility.cs b/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/Utf8Utility.cs new file mode 100644 index 0000000..e5d3f9c --- /dev/null +++ b/src/Reloaded.Memory/Internals/Backports/System/Text/Unicode/Utf8Utility.cs @@ -0,0 +1,113 @@ +using System.Diagnostics.CodeAnalysis; + +[ExcludeFromCodeCoverage] // Taken from Runtime +internal static class Utf8Utility +{ + /// + /// Given a UInt32 that represents four ASCII UTF-8 characters, returns the invariant + /// lowercase representation of those characters. Requires the input value to contain + /// four ASCII UTF-8 characters in machine endianness. + /// + /// The value to convert. + /// + /// This is a branchless implementation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint ConvertAllAsciiBytesInUInt32ToLowercase(uint value) + { + // the 0x80 bit of each byte of 'lowerIndicator' will be set iff the word has value >= 'A' + var lowerIndicator = value + 0x8080_8080u - 0x4141_4141u; + + // the 0x80 bit of each byte of 'upperIndicator' will be set iff the word has value > 'Z' + var upperIndicator = value + 0x8080_8080u - 0x5B5B_5B5Bu; + + // the 0x80 bit of each byte of 'combinedIndicator' will be set iff the word has value >= 'A' and <= 'Z' + var combinedIndicator = lowerIndicator ^ upperIndicator; + + // the 0x20 bit of each byte of 'mask' will be set iff the word has value >= 'A' and <= 'Z' + var mask = (combinedIndicator & 0x8080_8080u) >> 2; + + return value ^ mask; // bit flip uppercase letters [A-Z] => [a-z] + } + + /// + /// Given a UInt32 that represents four ASCII UTF-8 characters, returns the invariant + /// uppercase representation of those characters. Requires the input value to contain + /// four ASCII UTF-8 characters in machine endianness. + /// + /// The value to convert. + /// + /// This is a branchless implementation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint ConvertAllAsciiBytesInUInt32ToUppercase(uint value) + { + // the 0x80 bit of each byte of 'lowerIndicator' will be set iff the word has value >= 'a' + var lowerIndicator = value + 0x8080_8080u - 0x6161_6161u; + + // the 0x80 bit of each byte of 'upperIndicator' will be set iff the word has value > 'z' + var upperIndicator = value + 0x8080_8080u - 0x7B7B_7B7Bu; + + // the 0x80 bit of each byte of 'combinedIndicator' will be set iff the word has value >= 'a' and <= 'z' + var combinedIndicator = lowerIndicator ^ upperIndicator; + + // the 0x20 bit of each byte of 'mask' will be set iff the word has value >= 'a' and <= 'z' + var mask = (combinedIndicator & 0x8080_8080u) >> 2; + + return value ^ mask; // bit flip lowercase letters [a-z] => [A-Z] + } + + /// + /// Given a UInt64 that represents eight ASCII UTF-8 characters, returns the invariant + /// uppercase representation of those characters. Requires the input value to contain + /// eight ASCII UTF-8 characters in machine endianness. + /// + /// The value to convert. + /// + /// This is a branchless implementation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ulong ConvertAllAsciiBytesInUInt64ToUppercase(ulong value) + { + // the 0x80 bit of each byte of 'lowerIndicator' will be set iff the word has value >= 'a' + var lowerIndicator = value + 0x8080_8080_8080_8080ul - 0x6161_6161_6161_6161ul; + + // the 0x80 bit of each byte of 'upperIndicator' will be set iff the word has value > 'z' + var upperIndicator = value + 0x8080_8080_8080_8080ul - 0x7B7B_7B7B_7B7B_7B7Bul; + + // the 0x80 bit of each byte of 'combinedIndicator' will be set iff the word has value >= 'a' and <= 'z' + var combinedIndicator = lowerIndicator ^ upperIndicator; + + // the 0x20 bit of each byte of 'mask' will be set iff the word has value >= 'a' and <= 'z' + var mask = (combinedIndicator & 0x8080_8080_8080_8080ul) >> 2; + + return value ^ mask; // bit flip lowercase letters [a-z] => [A-Z] + } + + /// + /// Given a UInt64 that represents eight ASCII UTF-8 characters, returns the invariant + /// uppercase representation of those characters. Requires the input value to contain + /// eight ASCII UTF-8 characters in machine endianness. + /// + /// The value to convert. + /// + /// This is a branchless implementation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ulong ConvertAllAsciiBytesInUInt64ToLowercase(ulong value) + { + // the 0x80 bit of each byte of 'lowerIndicator' will be set iff the word has value >= 'A' + var lowerIndicator = value + 0x8080_8080_8080_8080ul - 0x4141_4141_4141_4141ul; + + // the 0x80 bit of each byte of 'upperIndicator' will be set iff the word has value > 'Z' + var upperIndicator = value + 0x8080_8080_8080_8080ul - 0x5B5B_5B5B_5B5B_5B5Bul; + + // the 0x80 bit of each byte of 'combinedIndicator' will be set iff the word has value >= 'a' and <= 'z' + var combinedIndicator = lowerIndicator ^ upperIndicator; + + // the 0x20 bit of each byte of 'mask' will be set iff the word has value >= 'a' and <= 'z' + var mask = (combinedIndicator & 0x8080_8080_8080_8080ul) >> 2; + + return value ^ mask; // bit flip uppercase letters [A-Z] => [a-z] + } +}