|
using System; |
|
using System.Runtime.CompilerServices; |
|
using System.Runtime.InteropServices; |
|
using System.Text; |
|
using BenchmarkDotNet.Attributes; |
|
using BenchmarkDotNet.Running; |
|
|
|
namespace Benchmark; |
|
|
|
public class Bench |
|
{ |
|
[Params(10, 100, 500, 1000, 10000, 1000000)] |
|
public int N; |
|
|
|
#region PrecomputedMappings |
|
|
|
private const string HexAlphabetString = "0123456789ABCDEF"; |
|
|
|
private static readonly char[] HexAlphabetArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; |
|
|
|
private static ReadOnlySpan<byte> HexAlphabetSpan => new[] |
|
{ |
|
(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', |
|
}; |
|
|
|
// { "00", "01", ..., "0E", "0F", "10", "11", ..., "FE", "FF" } |
|
private static readonly string[] HexStringTable = |
|
HexAlphabetString.SelectMany(n1 => HexAlphabetString.Select(n2 => new string(new[] { n1, n2 }))).ToArray(); |
|
|
|
private static readonly uint[] Lookup32 = Enumerable.Range(0, 256).Select(i => |
|
{ |
|
string s = i.ToString("X2"); |
|
return s[0] + ((uint)s[1] << 16); |
|
}).ToArray(); |
|
|
|
private static readonly unsafe uint* Lookup32UnsafeP = (uint*)GCHandle.Alloc(Lookup32, GCHandleType.Pinned).AddrOfPinnedObject(); |
|
|
|
private static ReadOnlySpan<byte> Lookup32Span => new byte[] |
|
{ |
|
0x0, 0x30, 0x0, 0x30, |
|
0x0, 0x31, 0x0, 0x30, |
|
0x0, 0x32, 0x0, 0x30, |
|
0x0, 0x33, 0x0, 0x30, |
|
0x0, 0x34, 0x0, 0x30, |
|
0x0, 0x35, 0x0, 0x30, |
|
0x0, 0x36, 0x0, 0x30, |
|
0x0, 0x37, 0x0, 0x30, |
|
0x0, 0x38, 0x0, 0x30, |
|
0x0, 0x39, 0x0, 0x30, |
|
0x0, 0x61, 0x0, 0x30, |
|
0x0, 0x62, 0x0, 0x30, |
|
0x0, 0x63, 0x0, 0x30, |
|
0x0, 0x64, 0x0, 0x30, |
|
0x0, 0x65, 0x0, 0x30, |
|
0x0, 0x66, 0x0, 0x30, |
|
0x0, 0x30, 0x0, 0x31, |
|
0x0, 0x31, 0x0, 0x31, |
|
0x0, 0x32, 0x0, 0x31, |
|
0x0, 0x33, 0x0, 0x31, |
|
0x0, 0x34, 0x0, 0x31, |
|
0x0, 0x35, 0x0, 0x31, |
|
0x0, 0x36, 0x0, 0x31, |
|
0x0, 0x37, 0x0, 0x31, |
|
0x0, 0x38, 0x0, 0x31, |
|
0x0, 0x39, 0x0, 0x31, |
|
0x0, 0x61, 0x0, 0x31, |
|
0x0, 0x62, 0x0, 0x31, |
|
0x0, 0x63, 0x0, 0x31, |
|
0x0, 0x64, 0x0, 0x31, |
|
0x0, 0x65, 0x0, 0x31, |
|
0x0, 0x66, 0x0, 0x31, |
|
0x0, 0x30, 0x0, 0x32, |
|
0x0, 0x31, 0x0, 0x32, |
|
0x0, 0x32, 0x0, 0x32, |
|
0x0, 0x33, 0x0, 0x32, |
|
0x0, 0x34, 0x0, 0x32, |
|
0x0, 0x35, 0x0, 0x32, |
|
0x0, 0x36, 0x0, 0x32, |
|
0x0, 0x37, 0x0, 0x32, |
|
0x0, 0x38, 0x0, 0x32, |
|
0x0, 0x39, 0x0, 0x32, |
|
0x0, 0x61, 0x0, 0x32, |
|
0x0, 0x62, 0x0, 0x32, |
|
0x0, 0x63, 0x0, 0x32, |
|
0x0, 0x64, 0x0, 0x32, |
|
0x0, 0x65, 0x0, 0x32, |
|
0x0, 0x66, 0x0, 0x32, |
|
0x0, 0x30, 0x0, 0x33, |
|
0x0, 0x31, 0x0, 0x33, |
|
0x0, 0x32, 0x0, 0x33, |
|
0x0, 0x33, 0x0, 0x33, |
|
0x0, 0x34, 0x0, 0x33, |
|
0x0, 0x35, 0x0, 0x33, |
|
0x0, 0x36, 0x0, 0x33, |
|
0x0, 0x37, 0x0, 0x33, |
|
0x0, 0x38, 0x0, 0x33, |
|
0x0, 0x39, 0x0, 0x33, |
|
0x0, 0x61, 0x0, 0x33, |
|
0x0, 0x62, 0x0, 0x33, |
|
0x0, 0x63, 0x0, 0x33, |
|
0x0, 0x64, 0x0, 0x33, |
|
0x0, 0x65, 0x0, 0x33, |
|
0x0, 0x66, 0x0, 0x33, |
|
0x0, 0x30, 0x0, 0x34, |
|
0x0, 0x31, 0x0, 0x34, |
|
0x0, 0x32, 0x0, 0x34, |
|
0x0, 0x33, 0x0, 0x34, |
|
0x0, 0x34, 0x0, 0x34, |
|
0x0, 0x35, 0x0, 0x34, |
|
0x0, 0x36, 0x0, 0x34, |
|
0x0, 0x37, 0x0, 0x34, |
|
0x0, 0x38, 0x0, 0x34, |
|
0x0, 0x39, 0x0, 0x34, |
|
0x0, 0x61, 0x0, 0x34, |
|
0x0, 0x62, 0x0, 0x34, |
|
0x0, 0x63, 0x0, 0x34, |
|
0x0, 0x64, 0x0, 0x34, |
|
0x0, 0x65, 0x0, 0x34, |
|
0x0, 0x66, 0x0, 0x34, |
|
0x0, 0x30, 0x0, 0x35, |
|
0x0, 0x31, 0x0, 0x35, |
|
0x0, 0x32, 0x0, 0x35, |
|
0x0, 0x33, 0x0, 0x35, |
|
0x0, 0x34, 0x0, 0x35, |
|
0x0, 0x35, 0x0, 0x35, |
|
0x0, 0x36, 0x0, 0x35, |
|
0x0, 0x37, 0x0, 0x35, |
|
0x0, 0x38, 0x0, 0x35, |
|
0x0, 0x39, 0x0, 0x35, |
|
0x0, 0x61, 0x0, 0x35, |
|
0x0, 0x62, 0x0, 0x35, |
|
0x0, 0x63, 0x0, 0x35, |
|
0x0, 0x64, 0x0, 0x35, |
|
0x0, 0x65, 0x0, 0x35, |
|
0x0, 0x66, 0x0, 0x35, |
|
0x0, 0x30, 0x0, 0x36, |
|
0x0, 0x31, 0x0, 0x36, |
|
0x0, 0x32, 0x0, 0x36, |
|
0x0, 0x33, 0x0, 0x36, |
|
0x0, 0x34, 0x0, 0x36, |
|
0x0, 0x35, 0x0, 0x36, |
|
0x0, 0x36, 0x0, 0x36, |
|
0x0, 0x37, 0x0, 0x36, |
|
0x0, 0x38, 0x0, 0x36, |
|
0x0, 0x39, 0x0, 0x36, |
|
0x0, 0x61, 0x0, 0x36, |
|
0x0, 0x62, 0x0, 0x36, |
|
0x0, 0x63, 0x0, 0x36, |
|
0x0, 0x64, 0x0, 0x36, |
|
0x0, 0x65, 0x0, 0x36, |
|
0x0, 0x66, 0x0, 0x36, |
|
0x0, 0x30, 0x0, 0x37, |
|
0x0, 0x31, 0x0, 0x37, |
|
0x0, 0x32, 0x0, 0x37, |
|
0x0, 0x33, 0x0, 0x37, |
|
0x0, 0x34, 0x0, 0x37, |
|
0x0, 0x35, 0x0, 0x37, |
|
0x0, 0x36, 0x0, 0x37, |
|
0x0, 0x37, 0x0, 0x37, |
|
0x0, 0x38, 0x0, 0x37, |
|
0x0, 0x39, 0x0, 0x37, |
|
0x0, 0x61, 0x0, 0x37, |
|
0x0, 0x62, 0x0, 0x37, |
|
0x0, 0x63, 0x0, 0x37, |
|
0x0, 0x64, 0x0, 0x37, |
|
0x0, 0x65, 0x0, 0x37, |
|
0x0, 0x66, 0x0, 0x37, |
|
0x0, 0x30, 0x0, 0x38, |
|
0x0, 0x31, 0x0, 0x38, |
|
0x0, 0x32, 0x0, 0x38, |
|
0x0, 0x33, 0x0, 0x38, |
|
0x0, 0x34, 0x0, 0x38, |
|
0x0, 0x35, 0x0, 0x38, |
|
0x0, 0x36, 0x0, 0x38, |
|
0x0, 0x37, 0x0, 0x38, |
|
0x0, 0x38, 0x0, 0x38, |
|
0x0, 0x39, 0x0, 0x38, |
|
0x0, 0x61, 0x0, 0x38, |
|
0x0, 0x62, 0x0, 0x38, |
|
0x0, 0x63, 0x0, 0x38, |
|
0x0, 0x64, 0x0, 0x38, |
|
0x0, 0x65, 0x0, 0x38, |
|
0x0, 0x66, 0x0, 0x38, |
|
0x0, 0x30, 0x0, 0x39, |
|
0x0, 0x31, 0x0, 0x39, |
|
0x0, 0x32, 0x0, 0x39, |
|
0x0, 0x33, 0x0, 0x39, |
|
0x0, 0x34, 0x0, 0x39, |
|
0x0, 0x35, 0x0, 0x39, |
|
0x0, 0x36, 0x0, 0x39, |
|
0x0, 0x37, 0x0, 0x39, |
|
0x0, 0x38, 0x0, 0x39, |
|
0x0, 0x39, 0x0, 0x39, |
|
0x0, 0x61, 0x0, 0x39, |
|
0x0, 0x62, 0x0, 0x39, |
|
0x0, 0x63, 0x0, 0x39, |
|
0x0, 0x64, 0x0, 0x39, |
|
0x0, 0x65, 0x0, 0x39, |
|
0x0, 0x66, 0x0, 0x39, |
|
0x0, 0x30, 0x0, 0x61, |
|
0x0, 0x31, 0x0, 0x61, |
|
0x0, 0x32, 0x0, 0x61, |
|
0x0, 0x33, 0x0, 0x61, |
|
0x0, 0x34, 0x0, 0x61, |
|
0x0, 0x35, 0x0, 0x61, |
|
0x0, 0x36, 0x0, 0x61, |
|
0x0, 0x37, 0x0, 0x61, |
|
0x0, 0x38, 0x0, 0x61, |
|
0x0, 0x39, 0x0, 0x61, |
|
0x0, 0x61, 0x0, 0x61, |
|
0x0, 0x62, 0x0, 0x61, |
|
0x0, 0x63, 0x0, 0x61, |
|
0x0, 0x64, 0x0, 0x61, |
|
0x0, 0x65, 0x0, 0x61, |
|
0x0, 0x66, 0x0, 0x61, |
|
0x0, 0x30, 0x0, 0x62, |
|
0x0, 0x31, 0x0, 0x62, |
|
0x0, 0x32, 0x0, 0x62, |
|
0x0, 0x33, 0x0, 0x62, |
|
0x0, 0x34, 0x0, 0x62, |
|
0x0, 0x35, 0x0, 0x62, |
|
0x0, 0x36, 0x0, 0x62, |
|
0x0, 0x37, 0x0, 0x62, |
|
0x0, 0x38, 0x0, 0x62, |
|
0x0, 0x39, 0x0, 0x62, |
|
0x0, 0x61, 0x0, 0x62, |
|
0x0, 0x62, 0x0, 0x62, |
|
0x0, 0x63, 0x0, 0x62, |
|
0x0, 0x64, 0x0, 0x62, |
|
0x0, 0x65, 0x0, 0x62, |
|
0x0, 0x66, 0x0, 0x62, |
|
0x0, 0x30, 0x0, 0x63, |
|
0x0, 0x31, 0x0, 0x63, |
|
0x0, 0x32, 0x0, 0x63, |
|
0x0, 0x33, 0x0, 0x63, |
|
0x0, 0x34, 0x0, 0x63, |
|
0x0, 0x35, 0x0, 0x63, |
|
0x0, 0x36, 0x0, 0x63, |
|
0x0, 0x37, 0x0, 0x63, |
|
0x0, 0x38, 0x0, 0x63, |
|
0x0, 0x39, 0x0, 0x63, |
|
0x0, 0x61, 0x0, 0x63, |
|
0x0, 0x62, 0x0, 0x63, |
|
0x0, 0x63, 0x0, 0x63, |
|
0x0, 0x64, 0x0, 0x63, |
|
0x0, 0x65, 0x0, 0x63, |
|
0x0, 0x66, 0x0, 0x63, |
|
0x0, 0x30, 0x0, 0x64, |
|
0x0, 0x31, 0x0, 0x64, |
|
0x0, 0x32, 0x0, 0x64, |
|
0x0, 0x33, 0x0, 0x64, |
|
0x0, 0x34, 0x0, 0x64, |
|
0x0, 0x35, 0x0, 0x64, |
|
0x0, 0x36, 0x0, 0x64, |
|
0x0, 0x37, 0x0, 0x64, |
|
0x0, 0x38, 0x0, 0x64, |
|
0x0, 0x39, 0x0, 0x64, |
|
0x0, 0x61, 0x0, 0x64, |
|
0x0, 0x62, 0x0, 0x64, |
|
0x0, 0x63, 0x0, 0x64, |
|
0x0, 0x64, 0x0, 0x64, |
|
0x0, 0x65, 0x0, 0x64, |
|
0x0, 0x66, 0x0, 0x64, |
|
0x0, 0x30, 0x0, 0x65, |
|
0x0, 0x31, 0x0, 0x65, |
|
0x0, 0x32, 0x0, 0x65, |
|
0x0, 0x33, 0x0, 0x65, |
|
0x0, 0x34, 0x0, 0x65, |
|
0x0, 0x35, 0x0, 0x65, |
|
0x0, 0x36, 0x0, 0x65, |
|
0x0, 0x37, 0x0, 0x65, |
|
0x0, 0x38, 0x0, 0x65, |
|
0x0, 0x39, 0x0, 0x65, |
|
0x0, 0x61, 0x0, 0x65, |
|
0x0, 0x62, 0x0, 0x65, |
|
0x0, 0x63, 0x0, 0x65, |
|
0x0, 0x64, 0x0, 0x65, |
|
0x0, 0x65, 0x0, 0x65, |
|
0x0, 0x66, 0x0, 0x65, |
|
0x0, 0x30, 0x0, 0x66, |
|
0x0, 0x31, 0x0, 0x66, |
|
0x0, 0x32, 0x0, 0x66, |
|
0x0, 0x33, 0x0, 0x66, |
|
0x0, 0x34, 0x0, 0x66, |
|
0x0, 0x35, 0x0, 0x66, |
|
0x0, 0x36, 0x0, 0x66, |
|
0x0, 0x37, 0x0, 0x66, |
|
0x0, 0x38, 0x0, 0x66, |
|
0x0, 0x39, 0x0, 0x66, |
|
0x0, 0x61, 0x0, 0x66, |
|
0x0, 0x62, 0x0, 0x66, |
|
0x0, 0x63, 0x0, 0x66, |
|
0x0, 0x64, 0x0, 0x66, |
|
0x0, 0x65, 0x0, 0x66, |
|
0x0, 0x66, 0x0, 0x66 |
|
}; |
|
|
|
#endregion |
|
|
|
#region Init |
|
|
|
private byte[] _bytes; |
|
|
|
public Bench() { |
|
_bytes = new byte[] {0}; |
|
} |
|
|
|
[GlobalSetup] |
|
public void GlobalSetup() |
|
{ |
|
_bytes = new byte[N]; |
|
new Random(42).NextBytes(_bytes); |
|
} |
|
|
|
#endregion |
|
|
|
/// <summary> |
|
/// https://github.com/fit-ctu-discord/honza-botner/blob/45d37346576d254a5acb82dedafd7d6712c1c619/src/HonzaBotner.Services/Sha256HashService.cs |
|
/// by https://github.com/ostorc |
|
/// </summary> |
|
[Benchmark(Baseline = true)] |
|
public string StringBuilderForEachByte() |
|
{ |
|
StringBuilder strB = new(); |
|
foreach (byte b in _bytes) |
|
strB.Append(b.ToString("X2")); |
|
|
|
return strB.ToString(); |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/3824807/3161322 |
|
/// by https://stackoverflow.com/users/64084 |
|
/// </summary> |
|
[Benchmark] |
|
public string StringBuilderForEachBytePreAllocated() |
|
{ |
|
StringBuilder hex = new StringBuilder(_bytes.Length * 2); |
|
foreach (byte b in _bytes) |
|
hex.Append(b.ToString("X2")); |
|
return hex.ToString(); |
|
} |
|
|
|
/// <summary> |
|
/// unknown, probably modified |
|
/// https://stackoverflow.com/a/3824807/3161322 |
|
/// by https://stackoverflow.com/users/64084 |
|
/// </summary> |
|
[Benchmark] |
|
public string StringBuilderForEachAppendFormat() |
|
{ |
|
StringBuilder hex = new StringBuilder(_bytes.Length * 2); |
|
foreach (byte b in _bytes) |
|
hex.AppendFormat("{0:X2}", b); |
|
return hex.ToString(); |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/311179/3161322 |
|
/// by https://stackoverflow.com/users/18771/tomalak |
|
/// </summary> |
|
[Benchmark] |
|
public string BitConverterReplace() |
|
{ |
|
string hex = BitConverter.ToString(_bytes); |
|
return hex.Replace("-", ""); |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/311382/3161322 |
|
/// by https://stackoverflow.com/users/987/will-dean |
|
/// </summary> |
|
[Benchmark] |
|
public string StringJoinArrayConvertAll() |
|
=> string.Join(string.Empty, Array.ConvertAll(_bytes, b => b.ToString("X2"))); |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/2345722/3161322 |
|
/// by |
|
/// </summary> |
|
[Benchmark] |
|
public string StringJoinSelect() |
|
=> string.Join(string.Empty, _bytes.Select(bin => bin.ToString("X2")).ToArray()); |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/311382/3161322 |
|
/// by https://stackoverflow.com/users/987/will-dean |
|
/// </summary> |
|
[Benchmark] |
|
public string StringConcatArrayConvertAll() |
|
=> string.Concat(Array.ConvertAll(_bytes, b => b.ToString("X2"))); |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/311382/3161322 |
|
/// by https://stackoverflow.com/users/149265/allon-guralnek |
|
/// </summary> |
|
[Benchmark] |
|
public string StringConcatSelect() |
|
=> string.Concat(_bytes.Select(b => b.ToString("X2"))); |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/3824807/3161322 |
|
/// by https://stackoverflow.com/users/64084 |
|
/// </summary> |
|
[Benchmark] |
|
public string StringBuilderAggregateBytesAppend() |
|
=> _bytes.Aggregate(new StringBuilder(_bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString(); |
|
|
|
/// <summary> |
|
/// unknown, probably modified |
|
/// https://stackoverflow.com/a/3824807/3161322 |
|
/// by https://stackoverflow.com/users/64084 |
|
/// </summary> |
|
[Benchmark] |
|
public string StringBuilderAggregateBytesAppendFormat() |
|
=> _bytes.Aggregate(new StringBuilder(_bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString(); |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/632920/3161322 |
|
/// by https://stackoverflow.com/users/676066 |
|
/// </summary> |
|
[Benchmark] |
|
public string ByteManipulationHexMultiply() |
|
{ |
|
char[] c = new char[_bytes.Length * 2]; |
|
byte b; |
|
for (int i = 0; i < _bytes.Length; i++) |
|
{ |
|
b = ((byte)(_bytes[i] >> 4)); |
|
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30); |
|
b = ((byte)(_bytes[i] & 0xF)); |
|
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30); |
|
} |
|
|
|
return new string(c); |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/3974535/3161322 |
|
/// by https://stackoverflow.com/users/21784/kgriffs |
|
/// </summary> |
|
[Benchmark] |
|
public string ByteManipulationHexIncrement() |
|
{ |
|
char[] c = new char[_bytes.Length * 2]; |
|
byte b; |
|
for (int bx = 0, cx = 0; bx < _bytes.Length; ++bx, ++cx) |
|
{ |
|
b = ((byte)(_bytes[bx] >> 4)); |
|
c[cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30); |
|
b = ((byte)(_bytes[bx] & 0x0F)); |
|
c[++cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30); |
|
} |
|
|
|
return new string(c); |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/14333437/3161322 |
|
/// by https://stackoverflow.com/users/445517 |
|
/// </summary> |
|
[Benchmark] |
|
public string ByteManipulationDecimal() |
|
{ |
|
char[] c = new char[_bytes.Length * 2]; |
|
int b; |
|
for (int i = 0; i < _bytes.Length; i++) |
|
{ |
|
b = _bytes[i] >> 4; |
|
c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7)); |
|
b = _bytes[i] & 0xF; |
|
c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7)); |
|
} |
|
|
|
return new string(c); |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/22158486/3161322 |
|
/// by https://stackoverflow.com/users/278889/patrick |
|
/// </summary> |
|
[Benchmark] |
|
public string WhileLocalLookup() |
|
{ |
|
char[] lookup = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; |
|
int i = -1, p = 0, l = _bytes.Length; |
|
char[] c = new char[l * 2]; |
|
byte d; |
|
--l; |
|
--p; |
|
while (i < l) |
|
{ |
|
d = _bytes[++i]; |
|
c[++p] = lookup[d >> 4]; |
|
c[++p] = lookup[d & 0xF]; |
|
} |
|
|
|
return new string(c, 0, c.Length); |
|
} |
|
|
|
/// <summary> |
|
/// based on WhileLocalLookup |
|
/// by https://github.com/antoninkriz |
|
/// </summary> |
|
[Benchmark] |
|
public string WhilePropertyLookup() |
|
{ |
|
int i = -1, p = 0, l = _bytes.Length; |
|
char[] c = new char[l * 2]; |
|
byte d; |
|
--l; |
|
--p; |
|
while (i < l) |
|
{ |
|
d = _bytes[++i]; |
|
c[++p] = HexAlphabetArray[d >> 4]; |
|
c[++p] = HexAlphabetArray[d & 0xF]; |
|
} |
|
|
|
return new string(c, 0, c.Length); |
|
} |
|
|
|
/// <summary> |
|
/// https://docs.microsoft.com/en-us/archive/blogs/blambert/blambertcodesnip-fast-byte-array-to-hex-string-conversion |
|
/// by Brian Lambert |
|
/// </summary> |
|
[Benchmark] |
|
public string LookupPerNibble() |
|
{ |
|
StringBuilder result = new StringBuilder(_bytes.Length * 2); |
|
foreach (byte b in _bytes) |
|
{ |
|
result.Append(HexStringTable[b]); |
|
} |
|
|
|
return result.ToString(); |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/5919521/3161322 |
|
/// by https://stackoverflow.com/users/610692/nathan-moinvaziri |
|
/// </summary> |
|
[Benchmark] |
|
public string LookupAndShift() |
|
{ |
|
StringBuilder result = new StringBuilder(_bytes.Length * 2); |
|
foreach (byte b in _bytes) |
|
{ |
|
result.Append(HexAlphabetString[b >> 4]); |
|
result.Append(HexAlphabetString[b & 0xF]); |
|
} |
|
|
|
return result.ToString(); |
|
} |
|
|
|
/// <summary> |
|
/// - |
|
/// by https://github.com/antoninkriz |
|
/// </summary> |
|
[Benchmark] |
|
public string LookupAndShiftAlphabetArray() |
|
{ |
|
var res = _bytes.Length * 2 <= 1024 ? stackalloc char[_bytes.Length * 2] : new char[_bytes.Length * 2]; |
|
|
|
for (int i = 0, j = 0; i < _bytes.Length; ++i, ++j) |
|
{ |
|
res[j] = HexAlphabetArray[_bytes[i] >> 4]; |
|
res[++j] = HexAlphabetArray[_bytes[i] & 0xF]; |
|
} |
|
|
|
return new string(res); |
|
} |
|
|
|
/// <summary> |
|
/// - |
|
/// by https://github.com/antoninkriz |
|
/// </summary> |
|
[Benchmark] |
|
public string LookupAndShiftAlphabetSpan() |
|
{ |
|
var res = _bytes.Length * 2 <= 1024 ? stackalloc char[_bytes.Length * 2] : new char[_bytes.Length * 2]; |
|
|
|
for (int i = 0, j = 0; i < _bytes.Length; ++i, ++j) |
|
{ |
|
res[j] = (char)HexAlphabetSpan[_bytes[i] >> 4]; |
|
res[++j] = (char)HexAlphabetSpan[_bytes[i] & 0xF]; |
|
} |
|
|
|
return new string(res); |
|
} |
|
|
|
/// <summary> |
|
/// - |
|
/// by https://github.com/antoninkriz |
|
/// </summary> |
|
[Benchmark] |
|
public string LookupAndShiftAlphabetSpanMultiply() |
|
{ |
|
var res = _bytes.Length * 2 <= 1024 ? stackalloc char[_bytes.Length * 2] : new char[_bytes.Length * 2]; |
|
|
|
for (var i = 0; i < _bytes.Length; ++i) |
|
{ |
|
var j = i * 2; |
|
res[j] = (char)HexAlphabetSpan[_bytes[i] >> 4]; |
|
res[j + 1] = (char)HexAlphabetSpan[_bytes[i] & 0xF]; |
|
} |
|
|
|
return new string(res); |
|
} |
|
|
|
/// <summary> |
|
/// http://stackoverflow.com/a/24343727/48700 |
|
/// by https://stackoverflow.com/users/445517 |
|
/// </summary> |
|
[Benchmark] |
|
public string LookupPerByte() |
|
{ |
|
var result = new char[_bytes.Length * 2]; |
|
for (int i = 0; i < _bytes.Length; i++) |
|
{ |
|
var val = Lookup32[_bytes[i]]; |
|
result[2 * i] = (char)val; |
|
result[2 * i + 1] = (char)(val >> 16); |
|
} |
|
|
|
return new string(result); |
|
} |
|
|
|
/// <summary> |
|
/// based on LookupPerByte |
|
/// by https://github.com/antoninkriz |
|
/// </summary> |
|
[Benchmark] |
|
public string LookupPerByteSpan() |
|
{ |
|
var result = _bytes.Length * 2 <= 1024 ? stackalloc char[_bytes.Length * 2] : new char[_bytes.Length * 2]; |
|
for (var i = 0; i < _bytes.Length; i++) |
|
{ |
|
var val = Lookup32[_bytes[i]]; |
|
result[2 * i] = (char)val; |
|
result[2 * i + 1] = (char)(val >> 16); |
|
} |
|
|
|
return new string(result); |
|
} |
|
|
|
/// <summary> |
|
/// based on LookupPerByte |
|
/// by https://github.com/antoninkriz |
|
/// </summary> |
|
[Benchmark] |
|
public string LookupSpanPerByteSpan() |
|
{ |
|
unchecked { |
|
var result = _bytes.Length * 2 <= 1024 ? stackalloc char[_bytes.Length * 2] : new char[_bytes.Length * 2]; |
|
for (var i = 0; i < _bytes.Length; i++) |
|
{ |
|
var val = Unsafe.ReadUnaligned<uint>(ref MemoryMarshal.GetReference(Lookup32Span.Slice(_bytes[i] * 4, 4))); |
|
result[2 * i] = (char) val; |
|
result[2 * i + 1] = (char)(val >> 16); |
|
} |
|
|
|
return new string(result); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/24343727/3161322 |
|
/// by https://stackoverflow.com/users/445517 |
|
/// </summary> |
|
[Benchmark] |
|
public unsafe string Lookup32UnsafeDirect() |
|
{ |
|
var lookupP = Lookup32UnsafeP; |
|
var result = new string((char)0, _bytes.Length * 2); |
|
fixed (byte* bytesP = _bytes) |
|
fixed (char* resultP = result) |
|
{ |
|
uint* resultP2 = (uint*)resultP; |
|
for (int i = 0; i < _bytes.Length; i++) |
|
{ |
|
resultP2[i] = lookupP[bytesP[i]]; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/24343727/3161322 |
|
/// by https://stackoverflow.com/users/445517 |
|
/// </summary> |
|
[Benchmark] |
|
public unsafe string Lookup32SpanUnsafeDirect() |
|
{ |
|
var result = new string((char)0, _bytes.Length * 2); |
|
fixed (byte* lookupP = Lookup32Span) |
|
fixed (byte* bytesP = _bytes) |
|
fixed (char* resultP = result) |
|
{ |
|
uint* resultP2 = (uint*)resultP; |
|
uint* lookupP2 = (uint*)lookupP; |
|
for (int i = 0; i < _bytes.Length; i++) |
|
{ |
|
resultP2[i] = lookupP2[bytesP[i]]; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// https://stackoverflow.com/a/68596484/3161322 |
|
/// by https://stackoverflow.com/posts/68596484/revisions |
|
/// </summary> |
|
[Benchmark] |
|
public string ConvertToHexString() |
|
=> Convert.ToHexString(_bytes); |
|
} |
|
|
|
internal static class Program |
|
{ |
|
public static void Main() |
|
{ |
|
var summary = BenchmarkRunner.Run<Bench>(); |
|
Console.WriteLine(summary); |
|
} |
|
} |
I found that a slight modification to LookupAndShift to use an array instead of a string builder is giving even better results thanLookupPerByteSpan here. (Still not as good as the Unsafe or the .NET 5 Convert.ToHex but could be good if you can't use either).
Benchmarks for 8-256: