【问题标题】:What is the fastest way to convert a float[] to a byte[]?将 float[] 转换为 byte[] 的最快方法是什么?
【发布时间】:2009-03-06 14:32:46
【问题描述】:

我想尽快从float[] 获得byte[],而不是循环遍历整个数组(可能通过演员表)。不安全的代码很好。谢谢!

我正在寻找一个比浮点数组长 4 倍的字节数组(字节数组的维度将是浮点数组的 4 倍,因为每个浮点数由 4 个字节组成)。我会将其传递给 BinaryWriter。

编辑: 对于那些尖叫“过早优化”的批评者: 在优化之前,我已经使用 ANTS 分析器对此进行了基准测试。由于文件具有直写缓存,并且浮点数组的大小与磁盘上的扇区大小完全匹配,因此速度显着提高。二进制编写器包装使用pinvoke'd win32 API 创建的文件句柄。进行优化是因为这减少了函数调用的数量。

而且,在内存方面,此应用程序会创建大量缓存,这些缓存会占用大量内存。我可以分配一次字节缓冲区并多次重复使用它——在这个特定实例中的双倍内存使用相当于应用程序整体内存消耗的舍入误差。

所以我想这里的教训是不要做出过早的假设;)

【问题讨论】:

  • 你真正想要什么?每个浮点数都转换为一个字节,还是一个包含浮点数的字节表示的四倍长的数组?
  • “长四倍”是什么意思?
  • 将浮点数数组转换为字节数组?所以 2 个浮点数需要 8 个字节?还是我误解了。
  • 这将有助于了解您打算在 for 之后使用什么字节。如果您愿意使用不安全的代码,那么 yopu 接受的答案在某些情况下并不是最优的......
  • 他在问题中说...“我会将其传递给 BinaryWriter”。

标签: c# unsafe


【解决方案1】:

有一个肮脏的快速(不是不安全的代码)这样做的方式:

[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 个字节大,因此不需要计算。

我又寻找了一些,它实际上在MSDNHow to: Create a C/C++ Union by Using Attributes (C# and Visual Basic) 上进行了描述,因此很有可能在未来的版本中会支持它。不过我不确定 Mono。

【讨论】:

  • +1 不错的技术!从垃圾收集器的角度来看,这个引用别名安全吗?
  • 为了避免任何人误解,System.Double(或在 C# 中简称为 double)是 8 个字节(或 64 位),不是 4 个字节(或 32 位)。
  • 这也可以让你访问未初始化或其他内存。缓冲区溢出利用go!
  • "sizeof' 不安全。
  • 也许 Length, heap address,... 存储在 cli 数组中,然后 Bytes.LengthDoubles.Length 获得相同的地址和相同的值。使用外部功能时不安全
【解决方案2】:

过早的优化是万恶之源! @Vlad 建议遍历每个浮点数是比切换到字节 [] 更合理的答案。使用下面的运行时表来增加元素数量(平均 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[] 所花费的时间远远超过了好处。

所以选择简单的:

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

【讨论】:

  • 在优化之前,我已经使用 ANTS 分析器对此进行了基准测试。由于文件具有直写缓存,并且浮点数组的大小与磁盘上的扇区大小完全匹配,因此速度显着提高。二进制编写器包装使用 win32 API 创建的文件句柄。 ;)
  • 不错不错,但我要补充一点,除非您要编写数百万次浮点数或执行数千次,否则在程序执行的宏伟计划中,约 200 毫秒是一个不重要的数字。
  • 在 10,000 处有一个最佳点,速度提高了 3 倍(或者是拼写错误?- 应该是 30.34 毫秒?) - 你如何解释?
  • 您是否将此与foreach (byte b in bytedata) { writer.Write(b); } 进行比较?因为这是一个相当愚蠢的比较,所以你想要这个字节的全部原因是你可以直接使用writer.Write(bytedata),跳过每次写入调用的大量开销。将 1MB 写入磁盘不应该花费 2 秒,这简直是荒谬的。以这种方式编写完整的 PC 备份需要一周时间。
【解决方案3】:

有一种方法可以避免内存复制和迭代。

您可以使用非常丑陋的 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);
    }
});

【讨论】:

  • -1 完全不便携。你在 64 位机器上试过吗?
  • 不 - 这是一个黑客。如果并且当我可以访问 64 位机器时,我可能会检查它并可能会对其进行调整。这也不是未来的证据。在 CLR v.Next 中,它可能完全被破坏了。这里有一个权衡:您可以使用更强大的解决方案并支付性能费用,或者使用我能想到的最快方式并生活在边缘:-)
  • 我有机会在 64 位机器上使用它,所以我使代码可移植。
  • +1 :-) 谢谢!我将此方法与自定义结构一起使用,它确实很有帮助。
  • 这个 hack 正在破坏内部垃圾收集器的数据结构。它将导致与 C++ 中的 use-after-free 相同的类的间歇性崩溃、数据损坏和安全错误。 .NET 运行时绝对不支持像这样破解内部垃圾收集器数据结构。 github.com/HelloKitty/Reinterpret.Net/issues/1 对这次黑客攻击将导致的崩溃进行了长时间的讨论。
【解决方案4】:

如果您不希望发生任何转换,我建议使用 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);

【讨论】:

  • 这将使内存分配量增加一倍除了迭代你的两个数组(一次复制,一次写入)。在速度方面和内存方面都非常低效。不推荐。
  • 最后一个参数不需要乘以sizeof(float)吗?
  • 其实你应该只使用Buffer.ByteLength:msdn.microsoft.com/en-us/library/system.buffer.bytelength.aspx
  • 您最好只遍历 float[] 数组并为每个浮点数调用 Write。此解决方案效率极低。
  • 不知道那个方法,谢谢!至于效率,每当我使用 BlockCopy 时,我都有一个 byte[] 并且需要一个 float[],因此没有不必要的重复。另外,如果您坚持使用 BlockCopy,则不需要不安全的代码,这可能是有利的。选择最适合您需求的方法。
【解决方案5】:

最好让 BinaryWriter do this for you。无论您使用哪种方法,都会对您的整个数据集进行迭代,因此玩字节是没有意义的。

【讨论】:

    【解决方案6】:

    虽然您可以使用unsafefixed 获得byte* 指针,但您不能将byte* 转换为byte[],以便编写器接受它作为参数而不执行数据复制。您不希望这样做,因为它会使您的内存占用加倍在需要执行的不可避免的迭代上添加额外的迭代,以便将数据输出到磁盘。

    相反,您最好使用Write(double) 方法遍历浮点数数组并将每个float 分别写入写入器。由于写入器内部的缓冲,它仍然会很快。查看sixlettervariables 的号码。

    【讨论】:

    • 不确定你的意思。我只想对浮点数组进行字节级索引(实际上,我正在将数组传递给 Writer)。
    • @Vlad:这是什么意思?数据类型如何不能表示为字节?看我的回答。
    • 表示(float)0 和(byte)0 的二进制表示不一样(因为它们的大小不同。)
    • 似乎不起作用:错误 CS1503:参数 '1':无法从 'byte*' 转换为 'byte[]'
    • Vlad 是正确的,您不能将内存中构成浮点 [] 的位伪造为字节 []。您可以在数组的前面获得一个字节*,这可能足以满足您的需要,但一个字节*不能被魔术成一个字节[]
    【解决方案7】:

    我们有一个名为 LudicrousSpeedSerialization 的类,它包含以下不安全的方法:

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

    【讨论】:

      【解决方案8】:

      虽然它基本上确实在幕后做了一个 for 循环,但它确实在一行中完成了这项工作

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-09
        • 2014-06-08
        • 2015-05-15
        • 1970-01-01
        • 2013-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多