我正在寻找最高效/直接的方法来执行这个简单的C/C++操作:
void ReadData(FILE *f, uint16 *buf, int startsamp, int nsamps)
{
fseek(f, startsamp*sizeof(uint16), SEEK_SET);
fread(buf, sizeof(uint16), nsamps, f);
}
在C#/.NET中,我需要读取存储在二进制文件中的许多(可能是10到100百万)2字节(16位)“ushort”整数数据样本(固定格式,无需解析)。 C方式的好处在于它直接将样本读入“uint16 *”缓冲区中,没有CPU参与,也没有复制。是的,这可能是“不安全”的,因为它使用未知大小的缓冲区的void *指针,但似乎应该有一个“安全”的.NET替代品。
在C#中,最佳方法是什么?我找了一些提示(使用FieldOffset的“联合”,使用指针的“不安全”代码,马歇尔),但似乎都不适用于这种情况,而且需要进行某种形式的复制/转换。我想避免BinaryReader.ReadUInt16(),因为它非常缓慢和CPU密集。在我的计算机上,使用ReadUInt16()的for()循环和使用单个Read()直接读取字节到byte []数组之间的速度差约为25倍。在非阻塞I/O(重叠“有用”处理,同时等待磁盘I/O)时,这个比率可能会更高。
理想情况下,我想要简单地将ushort []数组伪装成byte []数组,以便可以直接用Read()填充它,或者以某种方式使Read()直接填充ushort []数组:
// DOES NOT WORK!!
public void GetData(FileStream f, ushort [] buf, int startsamp, int nsamps)
{
f.Position = startsamp*sizeof(ushort);
f.Read(buf, 0, nsamps);
}
但是,没有一个接受ushort[]数组的Read()方法,只有一个接受byte[]数组的方法。
在C#中能直接完成这个任务吗?或者我需要使用非托管代码或第三方库,或者必须采用耗费CPU的逐个样本转换?虽然“安全”更可取,但我可以使用“不安全”的代码或某些使用Marshal的技巧,我只是还没有弄明白。
感谢任何指导!
[更新]
我想添加一些代码,如dtb所建议的那样,因为似乎很少有ReadArray的示例。这是一个非常简单的示例,没有显示错误检查。
public void ReadMap(string fname, short [] data, int startsamp, int nsamps)
{
var mmf = MemoryMappedFile.CreateFromFile(fname);
var mmacc = mmf.CreateViewAccessor();
mmacc.ReadArray(startsamp*sizeof(short), data, 0, nsamps);
}
数据安全地转储到您传递的数组中。您还可以为更复杂的类型指定类型。它似乎能够自行推断简单类型,但使用类型说明符,它会像这样:
mmacc.ReadArray<short>(startsamp*sizeof(short), data, 0, nsamps);
[更新2]
我想按照Ben的获胜答案所建议的方式添加代码,以“裸骨”形式类似于上面的代码进行比较。这段代码已经编译和测试过,可以正常工作,并且速度很快。我直接在DllImport中使用了SafeFileHandle类型(而不是更常见的IntPtr),以简化事情。
[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool ReadFile(SafeFileHandle handle, IntPtr buffer, uint numBytesToRead, out uint numBytesRead, IntPtr overlapped);
[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool SetFilePointerEx(SafeFileHandle hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod);
unsafe void ReadPINV(FileStream f, short[] buffer, int startsamp, int nsamps)
{
long unused; uint BytesRead;
SafeFileHandle nativeHandle = f.SafeFileHandle; // clears Position property
SetFilePointerEx(nativeHandle, startsamp*sizeof(short), out unused, 0);
fixed(short* pFirst = &buffer[0])
ReadFile(nativeHandle, (IntPtr)pFirst, (uint)nsamps*sizeof(short), out BytesRead, IntPtr.Zero);
}
BinaryReader.ReadUInt16();
,那么您可能需要将数据读入字节数组,然后再处理该字节数组。即使您把它分成块,100M 2字节的数据也约为200MB,因此您应该能够一次性将其全部读入内存并处理。 - Natefread
可能不是零拷贝 I/O,它是有缓冲的(所有stdio.h
函数都可以有缓冲,并且在大多数实现中都是如此)。 - Ben Voigt