From a245f5a0d463e132bcbb3c5871465bdb8bbec0b7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 19 Oct 2019 00:22:08 +0200 Subject: Rewrite hex encoder/decoder --- MediaBrowser.Common/Hex.cs | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 MediaBrowser.Common/Hex.cs (limited to 'MediaBrowser.Common/Hex.cs') diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs new file mode 100644 index 0000000000..e19a9a1f40 --- /dev/null +++ b/MediaBrowser.Common/Hex.cs @@ -0,0 +1,57 @@ +using System; +using System.Globalization; + +namespace MediaBrowser.Common +{ + /// + /// Encoding and decoding hex strings. + /// + public static class Hex + { + internal const string HexCharsLower = "0123456789abcdef"; + internal const string HexCharsUpper = "0123456789ABCDEF"; + + /// + /// Encodes bytes as a hex string. + /// + /// + /// + /// bytes as a hex string. + public static string Encode(ReadOnlySpan bytes, bool lowercase = true) + { + var hexChars = lowercase ? HexCharsLower : HexCharsUpper; + + // TODO: use string.Create when it's supports spans + // Ref: https://github.com/dotnet/corefx/issues/29120 + char[] s = new char[bytes.Length * 2]; + int j = 0; + for (int i = 0; i < bytes.Length; i++) + { + s[j++] = hexChars[bytes[i] >> 4]; + s[j++] = hexChars[bytes[i] & 0x0f]; + } + + return new string(s); + } + + /// + /// Decodes a hex string into bytes. + /// + /// The . + /// The decoded bytes. + public static byte[] Decode(ReadOnlySpan str) + { + byte[] bytes = new byte[str.Length / 2]; + int j = 0; + for (int i = 0; i < str.Length; i += 2) + { + bytes[j++] = byte.Parse( + str.Slice(i, 2), + NumberStyles.HexNumber, + CultureInfo.InvariantCulture); + } + + return bytes; + } + } +} -- cgit v1.2.3 From b6627af65f690754e14902144237e2f8866ca193 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 20 Oct 2019 00:05:04 +0200 Subject: Make decode even faster --- MediaBrowser.Common/Hex.cs | 53 ++++++++++++++++++---- .../Jellyfin.Common.Benches/HexDecodeBenches.cs | 9 ++-- 2 files changed, 51 insertions(+), 11 deletions(-) (limited to 'MediaBrowser.Common/Hex.cs') diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs index e19a9a1f40..6c86e4e6d0 100644 --- a/MediaBrowser.Common/Hex.cs +++ b/MediaBrowser.Common/Hex.cs @@ -1,5 +1,5 @@ using System; -using System.Globalization; +using System.Diagnostics.CodeAnalysis; namespace MediaBrowser.Common { @@ -11,6 +11,23 @@ namespace MediaBrowser.Common internal const string HexCharsLower = "0123456789abcdef"; internal const string HexCharsUpper = "0123456789ABCDEF"; + internal const int LastHexSymbol = 0x66; // 102: f + + /// + /// Map from an ASCII char to its hex value shifted, + /// e.g. b -> 11. 0xFF means it's not a hex symbol. + /// + /// + internal static ReadOnlySpan HexLookup => new byte[] { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + /// /// Encodes bytes as a hex string. /// @@ -41,17 +58,37 @@ namespace MediaBrowser.Common /// The decoded bytes. public static byte[] Decode(ReadOnlySpan str) { - byte[] bytes = new byte[str.Length / 2]; - int j = 0; - for (int i = 0; i < str.Length; i += 2) + if (str.Length == 0) { - bytes[j++] = byte.Parse( - str.Slice(i, 2), - NumberStyles.HexNumber, - CultureInfo.InvariantCulture); + return Array.Empty(); + } + + var unHex = HexLookup; + + int byteLen = str.Length / 2; + byte[] bytes = new byte[byteLen]; + int i = 0; + for (int j = 0; j < byteLen; j++) + { + byte a; + byte b; + if (str[i] > LastHexSymbol + || (a = unHex[str[i++]]) == 0xFF + || str[i] > LastHexSymbol + || (b = unHex[str[i++]]) == 0xFF) + { + ThrowArgumentException(nameof(str)); + break; // Unreachable + } + + bytes[j] = (byte)((a << 4) | b); } return bytes; } + + [DoesNotReturn] + private static void ThrowArgumentException(string paramName) + => throw new ArgumentException("Character is not a hex symbol.", paramName); } } diff --git a/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs index 2812755978..2efe5273f7 100644 --- a/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs +++ b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs @@ -9,10 +9,13 @@ namespace Jellyfin.Common.Benches [MemoryDiagnoser] public class HexDecodeBenches { - private const int N = 1000000; - private readonly string data; + [Params(0, 10, 100, 1000, 10000, 1000000)] + public int N { get; set; } - public HexDecodeBenches() + private string data; + + [GlobalSetup] + public void GlobalSetup() { var tmp = new byte[N]; new Random(42).NextBytes(tmp); -- cgit v1.2.3 From 593107e190856b7059f9e32f7dcb1343ca057db7 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 20 Oct 2019 12:31:59 +0200 Subject: Multiplication is faster than bit shifting --- MediaBrowser.Common/Hex.cs | 2 +- benches/Jellyfin.Common.Benches/HexDecodeBenches.cs | 5 ++++- benches/Jellyfin.Common.Benches/Program.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Common/Hex.cs') diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs index 6c86e4e6d0..b2d9aea3a7 100644 --- a/MediaBrowser.Common/Hex.cs +++ b/MediaBrowser.Common/Hex.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Common break; // Unreachable } - bytes[j] = (byte)((a << 4) | b); + bytes[j] = (byte)((a * 16) | b); } return bytes; diff --git a/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs index 2efe5273f7..5cd47202c9 100644 --- a/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs +++ b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs @@ -9,7 +9,7 @@ namespace Jellyfin.Common.Benches [MemoryDiagnoser] public class HexDecodeBenches { - [Params(0, 10, 100, 1000, 10000, 1000000)] + [Params(/*0,*/ 10, 100, 1000, 10000, 1000000)] public int N { get; set; } private string data; @@ -40,6 +40,9 @@ namespace Jellyfin.Common.Benches public byte[] Decode() => Hex.Decode(data); [Benchmark] + public byte[] Decode2() => Hex.Decode2(data); + + //[Benchmark] public byte[] DecodeSubString() => DecodeSubString(data); } } diff --git a/benches/Jellyfin.Common.Benches/Program.cs b/benches/Jellyfin.Common.Benches/Program.cs index b218b0dc10..e7d51c1b54 100644 --- a/benches/Jellyfin.Common.Benches/Program.cs +++ b/benches/Jellyfin.Common.Benches/Program.cs @@ -7,7 +7,7 @@ namespace Jellyfin.Common.Benches { public static void Main(string[] args) { - _ = BenchmarkRunner.Run(); + //_ = BenchmarkRunner.Run(); _ = BenchmarkRunner.Run(); } } -- cgit v1.2.3