将单个字符写入XmlWriter

时间:2016-01-22 14:47:55

标签: c# xml serialization char

我试图将byte []写成xml作为十六进制。像:

new byte [] {1,2,3,10} => " 0102030A"

我看到有关转换的好帖子,但由于xmlwriter没有WriteChar方法或WriteRaw有单个char覆盖,因此没有找到将chars逐个写入xml的好方法。 (就像在TextWriter中一样)

这是我正在做的事情:

const string HexChars = "0123456789ABCDEF";

public static void WriteHex(this XmlWriter writer, byte[] bytes)
{
    unchecked
    {
        for (int i = 0; i < bytes.Length; i++)
        {
            var b = bytes[i];
            writer.WriteRaw(HexChars[b >> 4].ToString());
            writer.WriteRaw(HexChars[b & 15].ToString());
        }
    }
}

我不想用byte []的双倍大小实例化新数组,然后将其写入xml。 WriteBinHex方法在值之间增加了超量,这就是为什么我没有使用它。我看到基本流是用属性公开的,但我想使用它是个坏主意。我试图实现的目标是做更多&#34;流畅的&#34;方式。

所以我的问题是,将单个字符写入xml的最快方法是什么?

目前正在考虑使用较小的char []缓冲区来进行循环写入,如果我找不到更好的方法。

修改

对不起,我错了WriteBinHex,它与我想要的输出完全相同。 我添加了一些基准作为答案,所以也许它可以帮助其他人。

2 个答案:

答案 0 :(得分:3)

由于你想单独写字,WriteRaw似乎是最快的方法。特别是,因为你已经排除了WriteValue。

您可以通过预先计算字符串来优化此HexChars[b >> 4].ToString()表达式。

如果我是你,我会使用一种写入整个字符串的方法,这样字符就不必单独通过整个处理和调用树。当我看到这些方法使用Reflector做什么时,这可以提供10倍的加速。但是你说你不是在考虑这种方法。

在Reflector中我看到WriteRaw也做了很多东西。我认为这需要进行基准测试。

如果您不喜欢临时char[]byte[]分配,可以使用[ThreadStatic]临时缓冲区。缓冲区大小可能在16-256范围内。足够大,可以减少所有常量开销,并且足够小以适应L1缓存,并且不会过多地污染缓存。

答案 1 :(得分:-1)

我尝试了5种方法,这里是基准测试。

首先,代码是发布编译,使用秒表,测量4个不同长度的数组。在每次测量之前收集GC。每个长度的迭代计数不同以显示相似的时间值(例如:字节[16]迭代100K次,字节[128K]迭代40次)。每次迭代都会创建一个xml编写器,将相同的byte []写为10个元素。

将所有方法与下面的方法进行比较,即XmlWriter的WriteBinHex:

writer.WriteBinHex(bytes, 0, bytes.Length);

以下所有方法都在未经检查的块中运行(例如未经检查的{...})

方法-1:完整字符[]

var result = new char[bytes.Length * 2];
byte b;
for (int i = 0; i < bytes.Length; i++)
{
    b = bytes[i];
    result[i * 2] = HexChars[b >> 4];
    result[i * 2 + 1] = HexChars[b & 15];
}
writer.WriteRaw(result, 0, result.Length);

方法-2:缓冲区

var bufferIndex = 0;
var bufferLength = bytes.Length < 2048 ? bytes.Length * 2 : 4096;
var buffer = new char[bufferLength];

for (int i = 0; i < bytes.Length; i++)
{
    var b = bytes[i];
    buffer[bufferIndex] = HexChars[b >> 4];
    buffer[bufferIndex + 1] = HexChars[b & 15];

    bufferIndex += 2;
    if (bufferIndex.Equals(bufferLength))
    {
        writer.WriteRaw(buffer, 0, bufferLength);
        bufferIndex = 0;
    }
}

if (bufferIndex > 0)
    writer.WriteRaw(buffer, 0, bufferIndex);

方法-3:RawCharByChar

for (int i = 0; i < bytes.Length; i++)
{
    var b = bytes[i];
    writer.WriteRaw(HexChars[b >> 4].ToString());
    writer.WriteRaw(HexChars[b & 15].ToString());
}

方法-4:StringFormatX2

for (int i = 0; i < bytes.Length; i++)
    writer.WriteRaw(bytes[i].ToString("x2"));

结果:(长度与时间以毫秒为单位)

方法:BinHex
16字节:971 ms,1 Kb:800 ms,128 Kb:906 ms,2Mb:1291 ms

方法:Full Char []
16字节:828 ms,1 Kb:612 ms,128 Kb:780 ms,2 Mb:1112 ms
AVG:-16%

方法:缓冲区
16字节:834 ms,1 Kb:671 ms,128 Kb:712 ms,2 Mb:1059 ms
AVG:-17%

方法:RawCharByChar
16字节:2624 ms,1 Kb:6515 ms,128 Kb:6979 ms,2 Mb:8282 ms
AVG:+ 524%

方法:StringFormatX2
16字节:3706 ms,1 Kb:10025 ms,128 Kb:10490 ms,2 Mb:26562 ms
AVG:+1113%

在这种情况下,我将继续使用Buffer实现,比WriteBinHex快17%。

修改

使用线程静态标记的缓冲区字段(与WriteBinHex方法相比)

16字节:-3%,1 KB:-10%,128 Kbyte:-14%,2 Mb:-11%
平均值:-9%正常缓冲区为-17%所以我放弃了ThreadLocal / Static。还试用了128/256个char缓冲区,得到了类似的结果。

[ThreadStatic]
static char[] _threadStaticBuffer = new char[240]; 

private void Test(XmlWriter writer, byte[] bytes)
{
    var bufferIndex = 0;
    var bufferLength = bytes.Length < 120? bytes.Length * 2 : 240;
    var buffer = _threadStaticBuffer;

    for (int i = 0; i < bytes.Length; i++)
    {
        var b = bytes[i];
        buffer[bufferIndex] = HexChars[b >> 4];
        buffer[bufferIndex + 1] = HexChars[b & 15];

        bufferIndex += 2;
        if (bufferIndex.Equals(bufferLength))
        {
            writer.WriteRaw(buffer, 0, bufferLength);
            bufferIndex = 0;
        }
    }

    if (bufferIndex > 0)
        writer.WriteRaw(buffer, 0, bufferIndex);
}

修改-2:

在我阅读了一些帖子之后,我使用https://stackoverflow.com/a/624379/2266524中的方法对我的方法-2进行了基准测试,而不是使用16个字符查找,而是使用256 * uint查找。

以下是与WriteBinHex方法相比的结果:

方法:WriteBinHex
16字节:745,1 Kb:679,128 Kb:739,2 Mb:1038

方法:Buffered char [] 256 uint lookup
16字节:653,1 Kb:454,128 Kb:502,2 Mb:758
AVG:-26%

方法:Buffered char [] unsafe 256 uint lookup
16字节:645,1 Kb:371,128 Kb:424,2 Mb:663
AVG:-34%

代码:

方法-5:具有256 uint查找的缓冲区

private static readonly uint[] _hexConversionLookup = CreateHexConversionLookup();
private static uint[] CreateHexConversionLookup()
{
    var result = new uint[256];
    for (int i = 0; i < 256; i++)
    {
        string s = i.ToString("X2");
        result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
    }
    return result;
}

private void TestBufferWith256UintLookup(XmlWriter writer, byte[] bytes)
{
    unchecked
    {
        var bufferIndex = 0;
        var bufferLength = bytes.Length < 2048 ? bytes.Length * 2 : 4096;
        var buffer = new char[bufferLength];

        for (int i = 0; i < bytes.Length; i++)
        {
            var b = _hexConversionLookup[bytes[i]];
            buffer[bufferIndex] = (char)b;
            buffer[bufferIndex + 1] = (char)(b >> 16);

            bufferIndex += 2;
            if (bufferIndex == bufferLength)
            {
                writer.WriteRaw(buffer, 0, bufferLength);
                bufferIndex = 0;
            }
        }

        if (bufferIndex > 0)
            writer.WriteRaw(buffer, 0, bufferIndex);
    }
}

方法-6:具有256 uint查找的不安全缓冲区

private static readonly uint[] _hexConversionLookup = CreateHexConversionLookup();
private static uint[] CreateHexConversionLookup()
{
    var result = new uint[256];
    for (int i = 0; i < 256; i++)
    {
        string s = i.ToString("X2");
        result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
    }
    return result;
}

private unsafe static readonly uint* _byteHexCharsP = (uint*)GCHandle.Alloc(_hexConversionLookup, GCHandleType.Pinned).AddrOfPinnedObject();

private unsafe void TestBufferWith256UintLookupUnsafe(XmlWriter writer, byte[] bytes)
{
    fixed (byte* bytesP = bytes)
    {
        var bufferIndex = 0;
        var bufferLength = bytes.Length < 2048 ? bytes.Length : 2048;
        var charBuffer = new char[bufferLength * 2];
        fixed (char* bufferP = charBuffer)
        {
            uint* buffer = (uint*)bufferP;
            for (int i = 0; i < bytes.Length; i++)
            {
                buffer[bufferIndex] = _byteHexCharsP[bytesP[i]];

                bufferIndex++;
                if (bufferIndex == bufferLength)
                {
                    writer.WriteRaw(charBuffer, 0, bufferLength * 2);
                    bufferIndex = 0;
                }
            }
        }

        if (bufferIndex > 0)
            writer.WriteRaw(charBuffer, 0, bufferIndex * 2);
    }
}

我的选择是#6,但您可能更喜欢安全版#5。感谢任何评论让它更快,谢谢..