如何在C#中将float[]快速的转换为byte[]

发布时间 2023-12-21 18:27:10作者: aaaSoft

昨天喻兄抛出一个问题“如何在C#中将float[]快速的转换为byte[]”。于是开始了尝试。先写了下面的初始化代码

using System.Diagnostics;
using System.Runtime.InteropServices;

Random random = new Random();
//源数组
var srcArray = new float[500 * 1024 * 1024];
//目的数组
var desArraySize = Buffer.ByteLength(srcArray);
byte[] desArray = new byte[desArraySize];
//源数组填充随机数据
for (var i = 0; i < srcArray.Length; i++)
    srcArray[i] = Convert.ToSingle(random.NextDouble());
Stopwatch stopwatch = Stopwatch.StartNew();

最开始想到的肯定是用BitConvert类的GetBytes方法得到一个float的4个byte,然后循环复制。不过这肯定是最慢的方法,因为每次BitConvert.GetBytes方法会创建一个4字节的数组,存在一个内存分配,并且循环一次只能复制4个字节。

//方法1
    {
        Array.Clear(desArray);
        stopwatch.Restart();
        for (var i = 0; i < srcArray.Length; i++)
        {
            var src = srcArray[i];
            var srcBytes = BitConverter.GetBytes(src);
            srcBytes.CopyTo(desArray, i * srcBytes.Length);
        }
        stopwatch.Stop();
        Console.WriteLine("方法1用时:" + stopwatch.Elapsed);
    }

想到C#中的数组在内存上是连续的,直接访问原数组这块内存,然后块复制到目的数据的内存应该是最快的。最开始尝试用的Marshal类,这个类从.NET Framework 2.0开始就存在了,主要就是处理托管和非托管相关的转换。先用Marshal类的UnsafeAddrOfPinnedArrayElement方法得到源数组的托管指针,然后调用Marshal类的Copy方法复制数据。

//方法2
    {
        Array.Clear(desArray);
        stopwatch.Restart();
        IntPtr srcArrayPtr = Marshal.UnsafeAddrOfPinnedArrayElement(srcArray, 0);
        Marshal.Copy(srcArrayPtr, desArray, 0, desArraySize);
        stopwatch.Stop();
        Console.WriteLine("方法2用时:" + stopwatch.Elapsed);
    }

在Windows上测试,上面的Marshal.Copy方法已经比最开始的循环快了一个数量级了。不过Marshal.Copy还是用的托管指针。然后我就在想能不用C语言中的memcpy方法用非托管指针进行复制,会不会更快呢?在C#中使用unsafe代码段可以轻松地得到非托管指针,然后在网上查阅资料后,找到了System.Buffer类中有MemoryCopy方法,应该是对标C语言中的memcpy方法的。

//方法3
    {
        Array.Clear(desArray);
        stopwatch.Restart();
        unsafe
        {
            fixed (void* desArrayPtr = desArray)
            fixed (void* srcArrayPtr = srcArray)
                Buffer.MemoryCopy(srcArrayPtr, desArrayPtr, desArraySize, desArraySize);      
        }
        stopwatch.Stop();
        Console.WriteLine("方法3用时:" + stopwatch.Elapsed);        
    }

使用500MB数据测试5次得到下面结果,经测试方法2和方法3的速度差不多。

------------------
方法1用时:00:00:07.0318303
方法2用时:00:00:00.1493652
方法3用时:00:00:00.1358148
------------------
方法1用时:00:00:06.5965146
方法2用时:00:00:00.1299518
方法3用时:00:00:00.1332787
------------------
方法1用时:00:00:06.4803075
方法2用时:00:00:00.1301812
方法3用时:00:00:00.1294293
------------------
方法1用时:00:00:06.4821344
方法2用时:00:00:00.1294174
方法3用时:00:00:00.1299445
------------------
方法1用时:00:00:06.4693005
方法2用时:00:00:00.1300947
方法3用时:00:00:00.1279827