如果我有一个字节数组并希望将该数组的连续16字节块(包含.net的Decimal
表示形式)转换为正确的Decimal
结构,那么最有效的方法是什么做到了吗?
在我正在优化的情况下,这些代码在我的分析器中显示为最大的CPU使用者。
public static decimal ByteArrayToDecimal(byte[] src, int offset)
{
using (MemoryStream stream = new MemoryStream(src))
{
stream.Position = offset;
using (BinaryReader reader = new BinaryReader(stream))
return reader.ReadDecimal();
}
}
为了摆脱MemoryStream
和BinaryReader
,我认为在BitConverter.ToInt32(src, offset + x)
构造函数中输入Decimal(Int32[])
数组会比我在下面提供的解决方案更快,但是奇怪的是,以下版本的速度是原来的两倍。
const byte DecimalSignBit = 128;
public static decimal ByteArrayToDecimal(byte[] src, int offset)
{
return new decimal(
BitConverter.ToInt32(src, offset),
BitConverter.ToInt32(src, offset + 4),
BitConverter.ToInt32(src, offset + 8),
src[offset + 15] == DecimalSignBit,
src[offset + 14]);
}
这比<{1}}组合快10倍,我用一堆极值来测试它以确保它有效,但十进制表示并不像其他原始类型的,所以我还不相信它适用于100%的可能十进制值。
理论上,可以有一种方法将这16个连续字节复制到内存中的其他位置并声明为十进制,而不进行任何检查。有人知道这样做的方法吗?
(只有一个问题:虽然小数表示为16个字节,但是某些可能的值不构成有效小数,因此执行未经检查的MemoryStream/BinaryReader
可能会破坏事情...)
或者还有其他更快的方法吗?
答案 0 :(得分:3)
我有第三个解决方案,但在我写之前,有必要说我没有测试它的性能。
public static decimal ByteArrayToDecimal(byte[] src, int offset)
{
var i1 = BitConverter.ToInt32(src, offset);
var i2 = BitConverter.ToInt32(src, offset + 4);
var i3 = BitConverter.ToInt32(src, offset + 8);
var i4 = BitConverter.ToInt32(src, offset + 12);
return new decimal(new int[] { i1, i2, i3, i4 });
}
这是一种基于二进制文件构建的方法,而不必担心System.Decimal
的规范。它是默认的.net位提取方法的反转:
System.Int32[] bits = Decimal.GetBits((decimal)10);
编辑:
这个解决方案可能不会更好,但也没有这个问题:"(There's only one problem: Although decimals are represented as 16 bytes, some of the possible values do not constitute valid decimals, so doing an uncheckedmemcpy could potentially break things...)"
。
答案 1 :(得分:3)
即使这是一个老问题,我有点好奇,所以决定进行一些实验。让我们从实验代码开始。
static void Main(string[] args)
{
byte[] serialized = new byte[16 * 10000000];
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000000; ++i)
{
decimal d = i;
// Serialize
using (var ms = new MemoryStream(serialized))
{
ms.Position = (i * 16);
using (var bw = new BinaryWriter(ms))
{
bw.Write(d);
}
}
}
var ser = sw.Elapsed.TotalSeconds;
sw = Stopwatch.StartNew();
decimal total = 0;
for (int i = 0; i < 10000000; ++i)
{
// Deserialize
using (var ms = new MemoryStream(serialized))
{
ms.Position = (i * 16);
using (var br = new BinaryReader(ms))
{
total += br.ReadDecimal();
}
}
}
var dser = sw.Elapsed.TotalSeconds;
Console.WriteLine("Time: {0:0.00}s serialization, {1:0.00}s deserialization", ser, dser);
Console.ReadLine();
}
结果:Time: 1.68s serialization, 1.81s deserialization
。这是我们的基准。我还尝试Buffer.BlockCopy
到int[4]
,这为反序列化提供了0.42秒。使用问题中描述的方法,反序列化降至0.29秒。
然而,在理论上,可能有办法复制那些16个连续的 字节到内存中的其他位置并声明为十进制, 没有任何检查。有人知道这样做的方法吗?
是的,最快的方法是使用不安全的代码,这里没问题,因为小数是值类型:
static unsafe void Main(string[] args)
{
byte[] serialized = new byte[16 * 10000000];
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000000; ++i)
{
decimal d = i;
fixed (byte* sp = serialized)
{
*(decimal*)(sp + i * 16) = d;
}
}
var ser = sw.Elapsed.TotalSeconds;
sw = Stopwatch.StartNew();
decimal total = 0;
for (int i = 0; i < 10000000; ++i)
{
// Deserialize
decimal d;
fixed (byte* sp = serialized)
{
d = *(decimal*)(sp + i * 16);
}
total += d;
}
var dser = sw.Elapsed.TotalSeconds;
Console.WriteLine("Time: {0:0.00}s serialization, {1:0.00}s deserialization", ser, dser);
Console.ReadLine();
}
此时,我们的结果是:Time: 0.07s serialization, 0.16s deserialization
。很确定这是最快的......但是,你必须在这里接受不安全的东西,而且我认为东西的写法与它的阅读方式相同。