簡體   English   中英

將 float[] 轉換為 byte[] 的最快方法是什么?

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

我想盡快從float[]中獲取byte[] ,而不是遍歷整個數組(可能通過強制轉換)。 不安全的代碼很好。 謝謝!

我正在尋找比 float 數組長 4 倍的字節數組(字節數組的維度將是 float 數組的 4 倍,因為每個 float 由 4 個字節組成)。 我會將其傳遞給 BinaryWriter。

編輯:對於那些尖叫“過早優化”的批評者:我在優化之前使用 ANTS 分析器對此進行了基准測試。 速度顯着提高,因為該文件具有直寫緩存,並且浮點數組的大小與磁盤上的扇區大小完全匹配。 二進制編寫器包裝了一個使用pinvoke 'd win32 API 創建的文件句柄。優化發生是因為這減少了 function 調用的次數。

並且,關於 memory,此應用程序創建大量緩存,使用大量 memory。我可以分配字節緩沖區一次並重復使用它多次——memory 在這個特定實例中的雙重使用相當於整體的舍入誤差memory 應用消費。

所以我想這里的教訓是不要做出過早的假設;)

有一種骯臟的快速(不是不安全的代碼)方法可以做到這一點:

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

這會起作用,但我不確定Mono或更新版本的CLR是否支持。 唯一奇怪的是array.Length是字節長度。 這可以解釋,因為它查看與數組一起存儲的數組長度,並且因為這個數組是一個字節數組,所以長度仍然是字節長度。 索引器確實認為 Double 是 8 個字節大,因此不需要計算。

我又找了一些,它實際上在MSDN上進行了描述,如何:使用屬性創建 C/C++ 聯合(C# 和 Visual Basic) ,所以將來的版本可能會支持它。 我不確定 Mono 雖然。

過早的優化是萬惡之源。 @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

對於少量元素,兩者之間幾乎沒有區別。 一旦進入大量元素范圍,從 float[] 復制到 byte[] 所花費的時間遠遠超過了好處。

所以 go 很簡單:

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

有一種方法可以避免 memory 復制和迭代。

您可以使用(不安全的)memory 操作來使用非常丑陋的 hack 將您的數組臨時更改為另一種類型。

我在 32 位和 64 位操作系統中測試了這個 hack,所以它應該是可移植的。

源代碼 + 示例用法保留在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));
        }
    }
}

用法是:

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

如果您不希望發生任何轉換,我建議使用 Buffer.BlockCopy()。

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

例如:

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

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

你最好讓 BinaryWriter為你做這件事 無論您使用哪種方法,都會對整個數據集進行迭代,因此使用字節毫無意義。

盡管您可以使用unsafefixed獲得byte*指針,但您不能將byte*轉換為byte[]以便編寫器在不執行數據復制的情況下將其作為參數接受。 您不想這樣做,因為它會使您的 memory 占用空間加倍,在不可避免的迭代上添加額外的迭代,以便將 output 數據寫入磁盤。

相反,您最好使用Write(double)方法迭代浮點數數組並將每個float單獨寫入寫入器。 由於寫入器內部的緩沖,它仍然會很快。 參見sixlettervariables的數字。

.Net Core 2.1或更高版本中使用新的Span<> ...

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

或者,如果可以改用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;

我做了一些粗略的基准測試。 每個花費的時間在評論中。 [發布/無調試器/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} ");
}

我們有一個名為 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;
    }

雖然它基本上確實在幕后做了一個 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM