简体   繁体   English

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

[英]Efficiently convert byte array to Decimal

If I have a byte array and want to convert a contiguous 16 byte block of that array, containing .net's representation of a Decimal , into a proper Decimal struct, what is the most efficient way to do it? 如果我有一个字节数组,并且想要将该数组的连续的16字节块(包含.net的Decimal表示形式)转换为适当的Decimal结构,最有效的方法是什么?

Here's the code that showed up in my profiler as the biggest CPU consumer in a case that I'm optimizing. 在我正在优化的情况下,这是我的探查器中显示为最大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();
    }
}

To get rid of MemoryStream and BinaryReader , I thought feeding an array of BitConverter.ToInt32(src, offset + x) s into the Decimal(Int32[]) constructor would be faster than the solution I present below, but the version below is, strangely enough, twice as fast. 为了摆脱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]);
}

This is 10 times as fast as the MemoryStream/BinaryReader combo, and I tested it with a bunch of extreme values to make sure it works, but the decimal representation is not as straightforward as that of other primitive types, so I'm not yet convinced it works for 100% of the possible decimal values. 这是MemoryStream/BinaryReader组合速度的10倍 ,我用一堆极限值对其进行了测试以确保它可以正常工作,但是十进制表示并不像其他原始类型那样简单,因此我还没有确信它适用于100%的可能十进制值。

In theory however, there could be a way to copy those 16 contiguous byte to some other place in memory and declare that to be a Decimal, without any checks. 但是,从理论上讲,有一种方法可以将这16个连续字节复制到内存中的其他位置,并声明它为Decimal,而无需进行任何检查。 Is anyone aware of a method to do this? 有人知道这样做的方法吗?

(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 unchecked memcpy could potentially break things...) (只有一个问题:尽管小数表示为16个字节,但是某些可能的值不能构成有效的小数,因此,执行未经检查的memcpy可能会破坏内容...)

Or is there any other faster way? 还是还有其他更快的方法?

@Eugene Beresovksy read from a stream is very costly. 从流中读取@Eugene Beresovksy非常昂贵。 MemoryStream is certainly a powerful and versatile tool, but it has a pretty high cost to a direct reading a binary array. MemoryStream当然是功能强大且用途广泛的工具,但是直接读取二进制数组的成本却很高。 Perhaps because of this the second method performs better. 也许正因为如此,第二种方法的性能更好。

I have a 3rd solution for you, but before I write it, it is necessary to say that I haven't tested the performance of it. 我为您提供了第三个解决方案,但是在我编写它之前,有必要说我还没有测试它的性能。

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 });
}

This is a way to make the building based on a binary without worrying about the canonical of System.Decimal . 这是一种使构建基于二进制文件而不用担心System.Decimal规范的方法。 It is the inverse of the default .net bit extraction method: 它是默认.net位提取方法的逆过程:

System.Int32[] bits = Decimal.GetBits((decimal)10);

EDITED: 编辑:

This solution perhaps don't peform better but also don't have this problem: "(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...)" . 此解决方案可能不会表现更好,但也不会出现此问题: "(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...)"

Even though this is an old question, I was a bit intrigued, so decided to run some experiments. 尽管这是一个古老的问题,但我还是很感兴趣,因此决定进行一些实验。 Let's start with the experiment code. 让我们从实验代码开始。

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();
}

Result: Time: 1.68s serialization, 1.81s deserialization . 结果: Time: 1.68s serialization, 1.81s deserialization This is our baseline. 这是我们的基准。 I also tried Buffer.BlockCopy to an int[4] , which gives us 0.42s for deserialization. 我还尝试了Buffer.BlockCopyint[4] ,这给了我们0.42s的反序列化时间。 Using the method described in the question, deserialization goes down to 0.29s. 使用问题中描述的方法,反序列化可降至0.29s。

In theory however, there could be a way to copy those 16 contiguous byte to some other place in memory and declare that to be a Decimal, without any checks. 但是,从理论上讲,有一种方法可以将这16个连续字节复制到内存中的其他位置,并声明它为Decimal,而无需进行任何检查。 Is anyone aware of a method to do this? 有人知道这样做的方法吗?

Well yes, the fastest way to do this is to use unsafe code, which is okay here because decimals are value types: 嗯,是的,最快的方法是使用不安全的代码,这在这里还可以,因为小数是值类型:

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();
}

At this point, our result is: Time: 0.07s serialization, 0.16s deserialization . 此时,我们的结果是: Time: 0.07s serialization, 0.16s deserialization Pretty sure that's the fastest this is going to get... still, you have to accept unsafe here, and I assume stuff is written the same way as it's read. 可以肯定的是,这将是最快的速度...仍然,您必须在这里接受不安全的信息,并且我假设内容的编写方式与读取的方式相同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM