12

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

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

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

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

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

4

8 回答 8

22

有一种肮脏的快速(不是不安全的代码)方法可以做到这一点:

[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。

于 2009-03-06T15:46:35.983 回答
21

过早优化是万恶之源!@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);
}
于 2009-03-06T15:37:32.603 回答
17

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

您可以使用非常丑陋的 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);
    }
});
于 2010-08-26T16:30:10.227 回答
7

如果您不希望发生任何转换,我建议使用 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);
于 2009-03-06T15:05:58.453 回答
3

你最好让 BinaryWriter为你做这件事。无论您使用哪种方法,都会对整个数据集进行迭代,因此使用字节毫无意义。

于 2009-03-06T15:23:08.503 回答
1

尽管您可以使用and获取byte*指针,但您不能将 转换为以便编写器在不执行数据复制的情况下将其作为参数接受。您不想这样做,因为它会使您的内存占用加倍,在不可避免的迭代上添加额外的迭代,以便将数据输出到磁盘。unsafefixedbyte*byte[]

相反,您最好使用该方法迭代浮点数组并将每个浮点数float单独Write(double)写入编写器。由于写入器内部的缓冲,它仍然会很快。见sixlettervariables的数字。

于 2009-03-06T14:44:53.043 回答
0

我们有一个名为 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;
    }
于 2009-03-06T16:39:44.217 回答
-3

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

byte[] byteArray = floatArray.Select(
                    f=>System.BitConverter.GetBytes(f)).Aggregate(
                    (bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });
于 2009-03-06T15:22:02.953 回答