我想获得一个byte[]
从float[]
尽可能快地,不通过整个数组循环(通过铸造,可能).不安全的代码很好.谢谢!
我正在寻找比浮点数组长4倍的字节数组(字节数组的维数将是浮点数组的4倍,因为每个浮点数由4个字节组成).我将它传递给BinaryWriter.
编辑:对那些尖叫"过早优化"的评论家:我在优化之前使用ANTS探查器对此进行了基准测试.速度显着提高,因为该文件具有直写缓存,并且浮点数组的大小恰好与磁盘上的扇区大小相匹配.二进制编写器包装使用pinvoke
'd win32 API 创建的文件句柄.优化发生,因为这减少了函数调用的数量.
而且,关于内存,这个应用程序创建了大量使用大量内存的缓存.我可以分配一次字节缓冲区并重复使用它多次 - 在这个特定实例中的双内存使用量相当于应用程序整体内存消耗的舍入误差.
所以我想这里的教训不是做出过早的假设;)
有一种脏的快速(不安全的代码)方式:
[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是八个字节大,因此不需要进行计算.
我已经查找了一些,它实际上是在MSDN上描述的,如何:使用属性(C#和Visual Basic)创建一个C/C++联盟,所以很有可能在将来的版本中支持它.我不确定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 []所花费的时间远远超过了好处.
所以简单地说:
float[] data = new float[...]; foreach(float value in data) { writer.Write(value); }
有一种方法可以避免内存复制和迭代.
你可以使用一个非常丑陋的黑客来使用(不安全的)内存操作临时将你的数组更改为另一种类型.
我在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, Actionaction) { 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 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 (this TSrc[] array, Action 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);