简体   繁体   English

将 float[] 转换为 byte[] 的最快方法是什么?

[英]What is the fastest way to convert a float[] to a byte[]?

I would like to get a byte[] from a float[] as quickly as possible, without looping through the whole array (via a cast, probably).我想尽快从float[]中获取byte[] ,而不是遍历整个数组(可能通过强制转换)。 Unsafe code is fine.不安全的代码很好。 Thanks!谢谢!

I am looking for a byte array 4 time longer than the float array (the dimension of the byte array will be 4 times that of the float array, since each float is composed of 4 bytes).我正在寻找比 float 数组长 4 倍的字节数组(字节数组的维度将是 float 数组的 4 倍,因为每个 float 由 4 个字节组成)。 I'll pass this to a BinaryWriter.我会将其传递给 BinaryWriter。

EDIT : To those critics screaming "premature optimization": I have benchmarked this using ANTS profiler before I optimized.编辑:对于那些尖叫“过早优化”的批评者:我在优化之前使用 ANTS 分析器对此进行了基准测试。 There was a significant speed increase because the file has a write-through cache and the float array is exactly sized to match the sector size on the disk.速度显着提高,因为该文件具有直写缓存,并且浮点数组的大小与磁盘上的扇区大小完全匹配。 The binary writer wraps a file handle created with pinvoke 'd win32 API. The optimization occurs since this lessens the number of function calls.二进制编写器包装了一个使用pinvoke 'd win32 API 创建的文件句柄。优化发生是因为这减少了 function 调用的次数。

And, with regard to memory, this application creates massive caches which use plenty of memory. I can allocate the byte buffer once and re-use it many times--the double memory usage in this particular instance amounts to a roundoff error in the overall memory consumption of the app.并且,关于 memory,此应用程序创建大量缓存,使用大量 memory。我可以分配字节缓冲区一次并重复使用它多次——memory 在这个特定实例中的双重使用相当于整体的舍入误差memory 应用消费。

So I guess the lesson here is not to make premature assumptions;)所以我想这里的教训是不要做出过早的假设;)

There is a dirty fast (not unsafe code) way of doing this:有一种肮脏的快速(不是不安全的代码)方法可以做到这一点:

[StructLayout(LayoutKind.Explicit)]
struct BytetoDoubleConverter
{
    [FieldOffset(0)]
    public Byte[] Bytes;

    [FieldOffset(0)]
    public Double[] Doubles;
}
//...
static Double Sum(byte[] data)
{
    BytetoDoubleConverter convert = new BytetoDoubleConverter { Bytes = data };
    Double result = 0;
    for (int i = 0; i < convert.Doubles.Length / sizeof(Double); i++)
    {
        result += convert.Doubles[i];
    }
    return result;
}

This will work, but I'm not sure of the support on Mono or newer versions of the CLR .这会起作用,但我不确定Mono或更新版本的CLR是否支持。 The only strange thing is that the array.Length is the bytes length.唯一奇怪的是array.Length是字节长度。 This can be explained because it looks at the array length stored with the array, and because this array was a byte array that length will still be in byte length.这可以解释,因为它查看与数组一起存储的数组长度,并且因为这个数组是一个字节数组,所以长度仍然是字节长度。 The indexer does think about the Double being eight bytes large so no calculation is necessary there.索引器确实认为 Double 是 8 个字节大,因此不需要计算。

I've looked for it some more, and it's actually described on MSDN , How to: Create a C/C++ Union by Using Attributes (C# and Visual Basic) , so chances are this will be supported in future versions.我又找了一些,它实际上在MSDN上进行了描述,如何:使用属性创建 C/C++ 联合(C# 和 Visual Basic) ,所以将来的版本可能会支持它。 I am not sure about Mono though.我不确定 Mono 虽然。

Premature optimization is the root of all evil.过早的优化是万恶之源。 @Vlad's suggestion to iterate over each float is a much more reasonable answer than switching to a byte[]: Take the following table of runtimes for increasing numbers of elements (average of 50 runs): @Vlad 对每个浮点数进行迭代的建议比切换到 byte[] 是一个更合理的答案:使用下表来增加元素数量(平均 50 次运行):

Elements      BinaryWriter(float)      BinaryWriter(byte[])
-----------------------------------------------------------
10               8.72ms                    8.76ms
100              8.94ms                    8.82ms
1000            10.32ms                    9.06ms
10000           32.56ms                   10.34ms
100000         213.28ms                  739.90ms
1000000       1955.92ms                10668.56ms

There is little difference between the two for small numbers of elements.对于少量元素,两者之间几乎没有区别。 Once you get into the huge number of elements range, the time spent copying from the float[] to the byte[] far outweighs the benefits.一旦进入大量元素范围,从 float[] 复制到 byte[] 所花费的时间远远超过了好处。

So go with what is simple:所以 go 很简单:

float[] data = new float[...];
foreach(float value in data)
{
    writer.Write(value);
}

There is a way which avoids memory copying and iteration.有一种方法可以避免 memory 复制和迭代。

You can use a really ugly hack to temporary change your array to another type using (unsafe) memory manipulation.您可以使用(不安全的)memory 操作来使用非常丑陋的 hack 将您的数组临时更改为另一种类型。

I tested this hack in both 32 & 64 bit OS, so it should be portable.我在 32 位和 64 位操作系统中测试了这个 hack,所以它应该是可移植的。

The source + sample usage is maintained at https://gist.github.com/1050703 , but for your convenience I'll paste it here as well:源代码 + 示例用法保留在https://gist.github.com/1050703 ,但为了您的方便,我也将其粘贴在这里:

public static unsafe class FastArraySerializer
{
    [StructLayout(LayoutKind.Explicit)]
    private struct Union
    {
        [FieldOffset(0)] public byte[] bytes;
        [FieldOffset(0)] public float[] floats;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    private struct ArrayHeader
    {
        public UIntPtr type;
        public UIntPtr length;
    }

    private static readonly UIntPtr BYTE_ARRAY_TYPE;
    private static readonly UIntPtr FLOAT_ARRAY_TYPE;

    static FastArraySerializer()
    {
        fixed (void* pBytes = new byte[1])
        fixed (void* pFloats = new float[1])
        {
            BYTE_ARRAY_TYPE = getHeader(pBytes)->type;
            FLOAT_ARRAY_TYPE = getHeader(pFloats)->type;
        }
    }

    public static void AsByteArray(this float[] floats, Action<byte[]> action)
    {
        if (floats.handleNullOrEmptyArray(action)) 
            return;

        var union = new Union {floats = floats};
        union.floats.toByteArray();
        try
        {
            action(union.bytes);
        }
        finally
        {
            union.bytes.toFloatArray();
        }
    }

    public static void AsFloatArray(this byte[] bytes, Action<float[]> action)
    {
        if (bytes.handleNullOrEmptyArray(action)) 
            return;

        var union = new Union {bytes = bytes};
        union.bytes.toFloatArray();
        try
        {
            action(union.floats);
        }
        finally
        {
            union.floats.toByteArray();
        }
    }

    public static bool handleNullOrEmptyArray<TSrc,TDst>(this TSrc[] array, Action<TDst[]> action)
    {
        if (array == null)
        {
            action(null);
            return true;
        }

        if (array.Length == 0)
        {
            action(new TDst[0]);
            return true;
        }

        return false;
    }

    private static ArrayHeader* getHeader(void* pBytes)
    {
        return (ArrayHeader*)pBytes - 1;
    }

    private static void toFloatArray(this byte[] bytes)
    {
        fixed (void* pArray = bytes)
        {
            var pHeader = getHeader(pArray);

            pHeader->type = FLOAT_ARRAY_TYPE;
            pHeader->length = (UIntPtr)(bytes.Length / sizeof(float));
        }
    }

    private static void toByteArray(this float[] floats)
    {
        fixed(void* pArray = floats)
        {
            var pHeader = getHeader(pArray);

            pHeader->type = BYTE_ARRAY_TYPE;
            pHeader->length = (UIntPtr)(floats.Length * sizeof(float));
        }
    }
}

And the usage is:用法是:

var floats = new float[] {0, 1, 0, 1};
floats.AsByteArray(bytes =>
{
    foreach (var b in bytes)
    {
        Console.WriteLine(b);
    }
});

If you do not want any conversion to happen, I would suggest Buffer.BlockCopy().如果您不希望发生任何转换,我建议使用 Buffer.BlockCopy()。

public static void BlockCopy(
    Array src,
    int srcOffset,
    Array dst,
    int dstOffset,
    int count
)

For example:例如:

float[] floatArray = new float[1000];
byte[] byteArray = new byte[floatArray.Length * 4];

Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);

You're better-off letting the BinaryWriter do this for you .你最好让 BinaryWriter为你做这件事 There's going to be iteration over your entire set of data regardless of which method you use, so there's no point in playing with bytes.无论您使用哪种方法,都会对整个数据集进行迭代,因此使用字节毫无意义。

Although you can obtain a byte* pointer using unsafe and fixed , you cannot convert the byte* to byte[] in order for the writer to accept it as a parameter without performing data copy.尽管您可以使用unsafefixed获得byte*指针,但您不能将byte*转换为byte[]以便编写器在不执行数据复制的情况下将其作为参数接受。 Which you do not want to do as it will double your memory footprint and add an extra iteration over the inevitable iteration that needs to be performed in order to output the data to disk.您不想这样做,因为它会使您的 memory 占用空间加倍,在不可避免的迭代上添加额外的迭代,以便将 output 数据写入磁盘。

Instead, you are still better off iterating over the array of floats and writing each float to the writer individually , using the Write(double) method.相反,您最好使用Write(double)方法迭代浮点数数组并将每个float单独写入写入器。 It will still be fast because of buffering inside the writer.由于写入器内部的缓冲,它仍然会很快。 See sixlettervariables 's numbers.参见sixlettervariables的数字。

Using the new Span<> in .Net Core 2.1 or later....Net Core 2.1或更高版本中使用新的Span<> ...

byte[] byteArray2 = MemoryMarshal.Cast<float, byte>(floatArray).ToArray();

Or, if Span can be used instead, then a direct reinterpret cast can be done: (very fast - zero copying)或者,如果可以改用Span ,则可以进行直接重新解释转换:(非常快 - 零复制)

Span<byte> byteArray3 = MemoryMarshal.Cast<float, byte>(floatArray);
// with span we can get a byte, set a byte, iterate, and more.
byte someByte = byteSpan[2]; 
byteSpan[2] = 33;

I did some crude benchmarks.我做了一些粗略的基准测试。 The time taken for each is in the comments.每个花费的时间在评论中。 [release/no debugger/x64] [发布/无调试器/x64]

float[] floatArray = new float[100];
for (int i = 0; i < 100; i++) floatArray[i] = i *  7.7777f;
Stopwatch start = Stopwatch.StartNew();
for (int j = 0; j < 100; j++)
{
    start.Restart();
    for (int k = 0; k < 1000; k++)
    {
        Span<byte> byteSpan = MemoryMarshal.Cast<float, byte>(floatArray);
    }
    long timeTaken1 = start.ElapsedTicks; ////// 0 ticks  //////

    start.Restart();
    for (int k = 0; k < 1000; k++)
    {
        byte[] byteArray2 = MemoryMarshal.Cast<float, byte>(floatArray).ToArray();
    }
    long timeTaken2 = start.ElapsedTicks; //////  26 ticks  //////

    start.Restart();
    for (int k = 0; k < 1000; k++)
    {
        byte[] byteArray = new byte[sizeof(float) * floatArray.Length];
        for (int i = 0; i < floatArray.Length; i++)
            BitConverter.GetBytes(floatArray[i]).CopyTo(byteArray, i * sizeof(float));
    }
    long timeTaken3 = start.ElapsedTicks;  //////  1310  ticks //////

    start.Restart();
    for (int k = 0; k < 1000; k++)
    {
        byte[] byteArray = new byte[sizeof(float) * floatArray.Length];
        Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);
    }
    long timeTaken4 = start.ElapsedTicks;  ////// 33 ticks  //////

    start.Restart();
    for (int k = 0; k < 1000; k++)
    {
        byte[] byteArray = new byte[sizeof(float) * floatArray.Length];
        MemoryStream memStream = new MemoryStream();
        BinaryWriter writer = new BinaryWriter(memStream);
        foreach (float value in floatArray)
            writer.Write(value);
        writer.Close();
    }
    long timeTaken5 = start.ElapsedTicks;   ////// 1080 ticks   //////

    Console.WriteLine($"{timeTaken1/10,6} {timeTaken2 / 10,6} {timeTaken3 / 10,6} {timeTaken4 / 10,6} {timeTaken5 / 10,6} ");
}

We have a class called LudicrousSpeedSerialization and it contains the following unsafe method:我们有一个名为 LudicrousSpeedSerialization 的 class ,它包含以下不安全的方法:

    static public byte[] ConvertFloatsToBytes(float[] data)
    {
        int n = data.Length;
        byte[] ret = new byte[n * sizeof(float)];
        if (n == 0) return ret;

        unsafe
        {
            fixed (byte* pByteArray = &ret[0])
            {
                float* pFloatArray = (float*)pByteArray;
                for (int i = 0; i < n; i++)
                {
                    pFloatArray[i] = data[i];
                }
            }
        }

        return ret;
    }

Although it basically does do a for loop behind the scenes, it does do the job in one line虽然它基本上确实在幕后做了一个 for 循环,但它确实在一行中完成了这项工作

byte[] byteArray = floatArray.Select(
                    f=>System.BitConverter.GetBytes(f)).Aggregate(
                    (bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });

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

相关问题 将byte []转换为float []的最快方法是什么,反之亦然? - What is the Fastest way to convert byte[] to float[] and vice versa? 将 bool 转换为字节的最快方法是什么? - What is fastest way to convert bool to byte? 将float转换为字节然后将字节数组保存在内存中的最快方法? - Fastest way to convert float to bytes and then save byte array in memory? 转换以下字节数组的最快方法是什么 - &gt;类对象的集合 - What is the fastest way to convert the following byte array -> collection of class objects 在 c# 中将 ushort 转换为字节的最快方法是什么? - What's the fastest way to convert a ushort to a byte in c#? 将字节 [] 转换为字符串的最快方法? - Fastest way to convert byte[] to string? C#,是否可以在没有副本的情况下将字节*转换为字节[]? 什么是最快的方式? - C#, Is it possible to convert a byte* to byte[] without a copy? What's the fastest way? 将图像转换为字节数组的最快方法 - Fastest way to convert Image to Byte array 读取字节[]的最快(可能是不安全)方式是什么? - What is the fastest (possibly unsafe) way to read a byte[]? 将浮点量化为字节的最佳方法是什么 - What is the best way to quantize a float to a byte
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM