我目前正在优化一个应用程序,其中一个经常执行的操作是读写二进制文件.我需要两种类型的功能:
Set(byte[] target, int index, int value); int Get(byte[] source, int index);
有符号和无符号short,int和long都需要这些函数,包括大端和小端序.
以下是我所做的一些例子,但我需要对优缺点进行评估:
第一种方法是使用Marshal将值写入byte []的内存中,第二种方法是使用普通指针来完成此操作,第三种方法使用BitConverter和BlockCopy来执行此操作
unsafe void Set(byte[] target, int index, int value) { fixed (byte* p = &target[0]) { Marshal.WriteInt32(new IntPtr(p), index, value); } } unsafe void Set(byte[] target, int index, int value) { int* p = &value; for (int i = 0; i < 4; i++) { target[offset + i] = *((byte*)p + i); } } void Set(byte[] target, int index, int value) { byte[] data = BitConverter.GetBytes(value); Buffer.BlockCopy(data, 0, target, index, data.Length); }
以下是Read/Get方法:
第一个是使用Marshal从byte []读取值,第二个是使用普通指针,第三个是再次使用BitConverter:
unsafe int Get(byte[] source, int index) { fixed (byte* p = &source[0]) { return Marshal.ReadInt32(new IntPtr(p), index); } } unsafe int Get(byte[] source, int index) { fixed (byte* p = &source[0]) { return *(int*)(p + index); } } unsafe int Get(byte[] source, int index) { return BitConverter.ToInt32(source, index); }
边界检查需要完成,但不是我的问题的一部分......
如果有人能说出在这种情况下最好和最快的方法,或者给我一些其他解决方案,我会很高兴.通用解决方案将是更可取的
我刚做了一些性能测试,结果如下:
设置Marshal:45 ms,设置指针:48 ms,设置BitConverter:71 ms获得Marshal:45 ms,获取指针:26 ms,获取BitConverter:30 ms
似乎使用指针是快速的方式,但我认为Marshal和BitConverter做了一些内部检查......有人可以验证这个吗?
重要提示:如果您只需要一个端点,请通过wj32/dtb查看指针魔术
就个人而言,我会直接写一个Stream
(可能有一些缓冲),并重新使用我通常认为是干净的共享缓冲区.然后你可以做一些快捷方式并假设索引0/1/2/3.
当然不要使用BitConverter
,因为它不能用于你需要的小/大端.我也倾向于使用位移而不是不安全等.基于以下内容,它实际上是最快的(所以我很高兴这就是我已经在这里做代码的方式,寻找EncodeInt32Fixed
):
Set1: 371ms Set2: 171ms Set3: 993ms Set4: 91ms <==== bit-shifting ;-p
码:
using System; using System.Diagnostics; using System.Runtime.InteropServices; static class Program { static void Main() { const int LOOP = 10000000, INDEX = 100, VALUE = 512; byte[] buffer = new byte[1024]; Stopwatch watch; watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { Set1(buffer, INDEX, VALUE); } watch.Stop(); Console.WriteLine("Set1: " + watch.ElapsedMilliseconds + "ms"); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { Set2(buffer, INDEX, VALUE); } watch.Stop(); Console.WriteLine("Set2: " + watch.ElapsedMilliseconds + "ms"); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { Set3(buffer, INDEX, VALUE); } watch.Stop(); Console.WriteLine("Set3: " + watch.ElapsedMilliseconds + "ms"); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { Set4(buffer, INDEX, VALUE); } watch.Stop(); Console.WriteLine("Set4: " + watch.ElapsedMilliseconds + "ms"); Console.WriteLine("done"); Console.ReadLine(); } unsafe static void Set1(byte[] target, int index, int value) { fixed (byte* p = &target[0]) { Marshal.WriteInt32(new IntPtr(p), index, value); } } unsafe static void Set2(byte[] target, int index, int value) { int* p = &value; for (int i = 0; i < 4; i++) { target[index + i] = *((byte*)p + i); } } static void Set3(byte[] target, int index, int value) { byte[] data = BitConverter.GetBytes(value); Buffer.BlockCopy(data, 0, target, index, data.Length); } static void Set4(byte[] target, int index, int value) { target[index++] = (byte)value; target[index++] = (byte)(value >> 8); target[index++] = (byte)(value >> 16); target[index] = (byte)(value >> 24); } }
使用马克Gravell的Set1
到Set4
和Set5
下面,我把我的机器上的以下数字:
Set1: 197ms Set2: 102ms Set3: 604ms Set4: 68ms Set5: 55ms <==== pointer magic ;-p
码:
unsafe static void Set5(byte[] target, int index, int value) { fixed (byte* p = &target[index]) { *((int*)p) = value; } }
当然,它会多快时,字节数组是不是在每次迭代但只有一次固定:
Set6: 10ms (little endian) Set7: 85ms (big endian)
码:
if (!BitConverter.IsLittleEndian) { throw new NotSupportedException(); } watch = Stopwatch.StartNew(); fixed (byte* p = buffer) { for (int i = 0; i < LOOP; i++) { *((int*)(p + INDEX)) = VALUE; } } watch.Stop(); Console.WriteLine("Set6: " + watch.ElapsedMilliseconds + "ms"); watch = Stopwatch.StartNew(); fixed (byte* p = buffer) { for (int i = 0; i < LOOP; i++) { *((int*)(p + INDEX)) = System.Net.IPAddress.HostToNetworkOrder(VALUE); } } watch.Stop(); Console.WriteLine("Set7: " + watch.ElapsedMilliseconds + "ms");