我试图将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,它与我想要的输出完全相同。 我添加了一些基准作为答案,所以也许它可以帮助其他人。
答案 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。感谢任何评论让它更快,谢谢..