C#将byte []解析为十六进制字符串

时间:2017-07-07 20:52:55

标签: c# performance

我创建了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

堆叠多个替换和连接操作

2 个答案:

答案 0 :(得分:2)

您的方法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 - 然后 - Replace()方法(大约只有两倍)。

为了进一步提高速度,可以通过准备每个长度为256的前导数字和尾数位查找表来避免计算buffer[i] >> 4buffer[i] & 0x0f。但按位操作非常快;在许多缺少大型L1数据缓存的微控制器上,增加查找表的大小会比按位运算成本更高。

答案 1 :(得分:0)

如上所述,这是一个高度调整的功能,专门用于此请求的输出。请原谅“神奇的价值观”:),我把它作为POC扔了:),但它非常快,这是重点。它避免了所有的字符串操作,StringBuilder,调用其他方法等等。

private unsafe static string ToHexA2(byte[] buffer)
{
    int length = buffer.Length;
    int num = (length * 6) + 2;
    char[] array = new char[num];
    int num2 = 0;

    fixed (char* ptr = array)
    {
        *(long*)ptr = 0x0020007B;

        for (int i = 2; i < num; i += 6)
        {
            byte b = buffer[num2++];
            *(long*)(ptr + i) = 0x00780030;
            *(long*)(ptr + i + 2) = digits[b >> 4] | (digits[b & 0x0F] << 16);
            *(long*)(ptr + i + 4) = 0x0020002C;
        }

        *(long*)(ptr + num - 2) = 0x007D0020;
    }

    return new string(array, 0, array.Length);
}