有效地将字节数组转换为十进制

本文关键字:转换 十进制 数组 字节数 字节 有效地 | 更新日期: 2023-09-27 18:17:16

如果我有一个字节数组,并想转换该数组的连续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();
    }
}

为了摆脱MemoryStreamBinaryReader,我认为将BitConverter.ToInt32(src, offset + x) s数组输入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]);
}

这是10倍的速度作为MemoryStream/BinaryReader组合,我测试了它与一堆极端值,以确保它的工作,但十进制表示不像其他基本类型那样直接,所以我还不相信它适用于100%的可能的十进制值。然而,理论上,有一种方法可以将这16个连续字节复制到内存中的其他位置,并声明为Decimal,而不需要进行任何检查。有人知道这样做的方法吗?

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

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

有效地将字节数组转换为十进制

尽管这是一个老问题,但我还是有点好奇,所以决定做一些实验。让我们从实验代码开始。

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.BlockCopyint[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。我很确定这是最快的速度了……但是,您必须接受这里的不安全,并且我假设内容的写入方式与读取方式相同。

@Eugene Beresovksy从流读取是非常昂贵的。MemoryStream当然是一个强大而通用的工具,但它直接读取二进制数组的成本相当高。也许正因为如此,第二种方法的性能更好。

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

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