gpt4 book ai didi

C# 将 byte[] 解析为十六进制字符串的不同方法的意外性能结果

转载 作者:行者123 更新时间:2023-11-30 23:11:06 26 4
gpt4 key购买 nike

我创建了 3 种不同的方法,使用以下格式将 byte[] 转换为十六进制字符串:{ 0xx2, ... } ( Working Demo )

using System;
using System.Diagnostics;
using System.Text;

public class Program
{
public delegate string ParseMethod(byte[] Msg);
public class Parser
{
public string Name { get; private set;}
public ParseMethod Handler { get; private set; }

public Parser(string name, ParseMethod method)
{
Name = name;
Handler = method;
}
}

public static void Main()
{
Parser HexA = new Parser("ToHexA", ToHexA);
Parser HexB = new Parser("ToHexB", ToHexB);
Parser HexC = new Parser("ToHexC", ToHexC);

TestCorrectness(HexA);
TestCorrectness(HexB);
TestCorrectness(HexC);

Console.WriteLine("Small Message Performance:");
TestPerformance(HexA, MsgSize: 10, Iterations: 1000000);
TestPerformance(HexB, MsgSize: 10, Iterations: 1000000);
TestPerformance(HexC, MsgSize: 10, Iterations: 1000000);
Console.WriteLine();

Console.WriteLine("Large Message Performance:");
TestPerformance(HexA, MsgSize: 500, Iterations: 1000000);
TestPerformance(HexB, MsgSize: 500, Iterations: 1000000);
TestPerformance(HexC, MsgSize: 500, Iterations: 1000000);
Console.WriteLine();
}

private static void TestCorrectness(Parser parser)
{
Console.WriteLine("Testing Correctness of \"{0}(byte[] Msg)\"", parser.Name);
Console.WriteLine("\"{0}\"", parser.Handler(new byte[]{}));
Console.WriteLine("\"{0}\"", parser.Handler(new byte[]{ 97 }));
Console.WriteLine("\"{0}\"", parser.Handler(new byte[]{ 97, 98, 99, 0, 100 }));
Console.WriteLine();
}

private static void TestPerformance(Parser parser, int MsgSize, int Iterations)
{
Stopwatch sw = new Stopwatch();
sw.Reset();

byte[] Msg = new byte[MsgSize];

sw.Start();
for (uint i = 0; i < Iterations; ++i)
{
parser.Handler(Msg);
}
sw.Stop();

Console.WriteLine("Performance for \"{0}\", {1}", parser.Name, sw.Elapsed);
}

private static string ToHexA(byte[] buffer)
{
return
(
"{ 0x" +
BitConverter.ToString(buffer).ToLower()
.Replace("-", ", 0x") +
" }"
)
.Replace(" 0x }", "}");
}

private static string ToHexB(byte[] buffer)
{
if (buffer.Length == 0) { return "{}"; }

const string Preamble = "{ 0x";
const string Delimiter = ", 0x";
const string Epilogue = " }";

string Msg = Preamble + buffer[0].ToString("x2");
for (int i = 1; i < buffer.Length; ++i)
{
Msg += Delimiter + buffer[i].ToString("x2");
}

return Msg += Epilogue;
}

private static string ToHexC(byte[] buffer)
{
if (buffer.Length == 0) { return "{}"; }

const string Preamble = "{ 0x";
const string Delimiter = ", 0x";
const string Epilogue = " }";

StringBuilder HexOut = new StringBuilder(
Preamble.Length +
(Delimiter.Length * (buffer.Length - 1)) +
(2 * buffer.Length) +
Epilogue.Length
);

HexOut.Append(Preamble);
HexOut.Append(buffer[0].ToString("x2"));
for (int i = 1; i < buffer.Length; ++i)
{
HexOut.Append(Delimiter);
HexOut.Append(buffer[i].ToString("x2"));
}
HexOut.Append(Epilogue);

return HexOut.ToString();
}
}

运行这段代码我得到以下性能统计数据

Small Message Performance:
Performance for "ToHexA", 00:00:01.3078387
Performance for "ToHexB", 00:00:01.6939201
Performance for "ToHexC", 00:00:01.2997903

Large Message Performance:
Performance for "ToHexA", 00:00:32.5230253
Performance for "ToHexB", 00:04:23.4798762
Performance for "ToHexC", 00:00:56.2404684

令我惊讶的是,ToHexA(相对于 ToHexC)在较长的消息中执行得更快,但在较短的消息中执行得较慢。据我了解,Replace()+ToLower() 都必须执行创建/复制操作,因为字符串是不可变的。

与此同时,我怀疑 StringBuilder 的开销可能使其不太适合较短的消息,但在这种情况下它的执行速度比 ToHexA 快。

我唯一期望的是与 ToHexB 相关的性能下降,这是一种介于两个世界最糟糕的情况之间...

那么,这两种方法及其性能特点是怎么回事呢?

编辑以使我对这个问题的意图更清楚

我对两种性能非常接近的方法的确切性能特征并不特别感兴趣。我知道这在不同的设备、体系结构、后台进程等上会有所不同,并且存在更好的测试方法来控制变量和什么不是

我问这个问题的目的是为了更好地理解为什么两种明显不同(在性能方面)的方法会叠加在一起。接受的答案以满足此问题的方式解释了 ToString 的机制。

这个问题的一个限制是输出的确切格式是特定的并且(我们只能思考为什么,我认为这是非常标准的)出于某种原因不是 C#/.NET 中的标准输出格式之一;正是这种独特的格式导致了这个问题:针对 StringBuilder

堆叠多个替换和连接操作

最佳答案

您的方法 C 将成为最快的串联方法,但是每次调用 ToString("x2") 时您仍然会产生垃圾。

除此之外,采用格式字符串的 ToString 重载对于这样的事情来说非常慢,因为它们首先必须处理格式字符串才能进行实际工作,并且这种格式字符串处理会重复进行很多很多次。太糟糕了,没有一个 Converter<byte, string> GetToString(string format) 处理格式字符串一次并返回一个有状态的转换对象。

无论如何,这里很容易避免byte.ToString()

static readonly char[] digits = "0123456789abcdef".ToCharArray();
private static string ToHexD(byte[] buffer)
{
if (buffer.Length == 0) { return "{}"; }

const string Preamble = "{ 0x";
const string Delimiter = ", 0x";
const string Epilogue = " }";

int expectedLength =
Preamble.Length +
(Delimiter.Length * (buffer.Length - 1)) +
(2 * buffer.Length) +
Epilogue.Length;
StringBuilder HexOut = new StringBuilder(expectedLength);

HexOut.Append(Preamble);
HexOut.Append(digits[buffer[0] >> 4]).Append(digits[buffer[0] & 0x0F]);
for (int i = 1; i < buffer.Length; ++i)
{
HexOut.Append(Delimiter);
HexOut.Append(digits[buffer[i] >> 4]).Append(digits[buffer[i] & 0x0F]);
}
HexOut.Append(Epilogue);

return HexOut.ToString();
}

Append(char) 的两次调用也应该比对 Append(string) 的一次调用更快,尽管这比节省格式字符串的时间重要得多。

正如预期的那样,这在所有规模上都轻而易举地击败了 BitConverter -then- Replace() 方法(大约是原来的两倍)。

为了稍微进一步提高速度,buffer[i] >> 4buffer[i] & 0x0f 的计算都可以通过准备每个长度为 256 的首位和尾位查找表来避免。但是按位运算非常快;在许多缺乏大型 L1 数据缓存的微 Controller 上,增加查找表的大小会比按位操作消耗更多的性能。

关于C# 将 byte[] 解析为十六进制字符串的不同方法的意外性能结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44979440/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com