有效地将字节数组转换为Decimal

如果我有一个字节数组,并希望将该数组的连续16字节块(包含.net的Decimal表示forms)转换为适当的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(); } } 

为了摆脱MemoryStreamBinaryReader ,我认为将一个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]); } 

这是MemoryStream/BinaryReader组合的10倍 ,我用一堆极值测试它以确保它有效,但十进制表示并不像其他原始类型那样简单,所以我还没有确信它适用于100%的可能小数值。

然而,理论上,可以有一种方法将这16个连续字节复制到内存中的某个其他位置,并将其声明为十进制,而不进行任何检查。 有人知道这样做的方法吗?

(只有一个问题:虽然小数表示为16个字节,但是某些可能的值不构成有效的小数,因此执行未经检查的memcpy可能会破坏事情……)

或者还有其他更快的方法吗?

@Eugene Beresovksy从一个流中读取的内容非常昂贵。 MemoryStream当然是一个function强大且function多样的工具,但直接读取二进制数组的成本相当高。 也许正因为如此,第二种方法表现更好。

我有第三个解决方案,但在我写之前,有必要说我没有测试它的性能。

 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...)"

即使这是一个老问题,我有点好奇,所以决定进行一些实验。 让我们从实验代码开始。

 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 Buffer.BlockCopy 。 使用问题中描述的方法,反序列化降至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 。 很确定这是最快的......但是,你必须在这里接受不安全的东西,并且我认为东西的写法与它的读法相同。