Skip to content

Instantly share code, notes, and snippets.

@ufcpp
Last active December 11, 2024 12:48
Show Gist options
  • Save ufcpp/bad4dd447cf061a73cace83238d60113 to your computer and use it in GitHub Desktop.
Save ufcpp/bad4dd447cf061a73cace83238d60113 to your computer and use it in GitHub Desktop.
byte 列の16進ダンプ文字列化
Method Mean Error StdDev Gen0 Gen1 Allocated
A1 88.28 us 1.739 us 2.953 us 31.1279 4.3945 399.68 KB
A2 84.18 us 1.644 us 2.137 us 30.5176 4.0283 390.72 KB
A3 120.11 us 0.894 us 0.836 us 5.9814 0.7324 78.22 KB
B1 124.21 us 0.311 us 0.243 us 2.9297 - 39.09 KB
B2 122.88 us 0.612 us 0.543 us 2.9297 - 39.09 KB
B3 123.91 us 0.480 us 0.426 us 2.9297 - 39.09 KB
C1 109.96 us 0.325 us 0.272 us 2.9297 - 39.09 KB
A4 10.72 us 0.208 us 0.195 us 6.8207 1.1292 87.18 KB
B4 10.88 us 0.126 us 0.105 us 3.0365 - 39.09 KB
C2 10.31 us 0.197 us 0.193 us 3.0365 - 39.09 KB
using BenchmarkDotNet.Attributes;
using System.Runtime.CompilerServices;
using System.Text;
using C = System.Globalization.CultureInfo;
#if DEBUG
var data = ToHexStringBenchmark.StaticData;
var s = data.A1();
Console.WriteLine(data.A1() == s);
Console.WriteLine(data.A2() == s);
Console.WriteLine(data.A3() == s);
Console.WriteLine(data.B1() == s);
Console.WriteLine(data.B2() == s);
Console.WriteLine(data.B3() == s);
Console.WriteLine(data.C1() == s);
Console.WriteLine(data.A4() == s);
Console.WriteLine(data.B4() == s);
Console.WriteLine(data.C2() == s);
#else
BenchmarkDotNet.Running.BenchmarkRunner.Run<ToHexStringBenchmark>();
#endif
public static class ToHexExtensions
{
public static string A1(this ReadOnlySpan<byte> data)
{
var x = new StringBuilder();
foreach (var b in data)
{
x.Append(b.ToString("X2", C.InvariantCulture));
}
return x.ToString();
}
public static string A2(this ReadOnlySpan<byte> data)
{
var x = new StringBuilder(data.Length * 2); // capacity 追加
foreach (var b in data)
{
x.Append(b.ToString("X2", C.InvariantCulture));
}
return x.ToString();
}
public static string A3(this ReadOnlySpan<byte> data)
{
var x = new StringBuilder(data.Length * 2);
foreach (var b in data)
{
x.Append(C.InvariantCulture, $"{b:X2}"); // interpolation に
}
return x.ToString();
}
public static string B1(this ReadOnlySpan<byte> data)
{
var x = new DefaultInterpolatedStringHandler(0, 0, C.InvariantCulture);
foreach (var b in data)
{
x.AppendFormatted(b, 0, "X2");
}
return x.ToStringAndClear();
}
public static string B2(this ReadOnlySpan<byte> data)
{
var x = new DefaultInterpolatedStringHandler(data.Length * 2, 0, C.InvariantCulture); // literalLength に文字数追加
foreach (var b in data)
{
x.AppendFormatted(b, 0, "X2");
}
return x.ToStringAndClear();
}
[SkipLocalsInit]
public static string B3(this ReadOnlySpan<byte> data)
{
var x = new DefaultInterpolatedStringHandler(0, 0, C.InvariantCulture, stackalloc char[data.Length * 2]); // initialBufer 追加
foreach (var b in data)
{
x.AppendFormatted(b, 0, "X2");
}
return x.ToStringAndClear();
}
// .NET 9 必須(allows ref struct が必須)。
// でないと、SpanAction デリゲートに ReadOnlySpan を渡せない。
public static string C1(this ReadOnlySpan<byte> data)
=> string.Create(data.Length * 2, data, static (buffer, data) =>
{
foreach (var b in data)
{
b.TryFormat(buffer, out _, "X2", C.InvariantCulture);
buffer = buffer[2..];
}
});
private const string _hexChars = "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF";
public static string A4(this ReadOnlySpan<byte> data)
{
var x = new StringBuilder();
foreach (var b in data)
{
x.Append(_hexChars.AsSpan(2 * b, 2));
}
return x.ToString();
}
public static string B4(this ReadOnlySpan<byte> data)
{
var x = new DefaultInterpolatedStringHandler(data.Length * 2, 0, C.InvariantCulture, stackalloc char[data.Length * 2]); // initialBufer 追加
foreach (var b in data)
{
x.AppendFormatted(_hexChars.AsSpan(2 * b, 2));
}
return x.ToStringAndClear();
}
// .NET 9 必須(allows ref struct が必須)。
public static string C2(this ReadOnlySpan<byte> data)
=> string.Create(data.Length * 2, data, static (buffer, data) =>
{
foreach (var b in data)
{
_hexChars.AsSpan(2 * b, 2).CopyTo(buffer);
buffer = buffer[2..];
}
});
}
[MemoryDiagnoser]
public class ToHexStringBenchmark
{
public static ReadOnlySpan<byte> StaticData => [
0,1,2,3,4,5,6,7,8,9,
10,11,12,13,14,15,16,17,18,19,
20,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,36,37,38,39,
40,41,42,43,44,45,46,47,48,49,
50,51,52,53,54,55,56,57,58,59,
60,61,62,63,64,65,66,67,68,69,
70,71,72,73,74,75,76,77,78,79,
80,81,82,83,84,85,86,87,88,89,
90,91,92,93,94,95,96,97,98,99,
];
private ReadOnlySpan<byte> Data => _data ??= Enumerable.Range(0, 10000).Select(x => (byte)x).ToArray();
private byte[]? _data;
[GlobalSetup]
public void Setup()
{
_ = Data;
}
[Benchmark] public string A1() => Data.A1();
[Benchmark] public string A2() => Data.A2();
[Benchmark] public string A3() => Data.A3();
[Benchmark] public string B1() => Data.B1();
[Benchmark] public string B2() => Data.B2();
[Benchmark] public string B3() => Data.B3();
[Benchmark] public string C1() => Data.C1();
[Benchmark] public string A4() => Data.A4();
[Benchmark] public string B4() => Data.B4();
[Benchmark] public string C2() => Data.C2();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment