Busco la manera más eficiente/directa de hacerlo/C operación de este sencillo C++:Directamente leer archivo binario grande en C# w/a copiar
void ReadData(FILE *f, uint16 *buf, int startsamp, int nsamps)
{
fseek(f, startsamp*sizeof(uint16), SEEK_SET);
fread(buf, sizeof(uint16), nsamps, f);
}
en C#/.NET.. (Estoy ignorando los valores de retorno para mayor claridad - el código de producción los verificaría.) Específicamente, necesito leer en muchos (de 10 a 100 millones) muestras de datos enteros "ushort" de 2 bytes (16 bits) (formato fijo , no se requiere análisis) almacenado en binario en un archivo de disco. Lo bueno de la forma C es que lee las muestras directamente en el búfer "uint16 *" sin participación de CPU y sin copia. Sí, es potencialmente "inseguro", ya que utiliza punteros void * para almacenamientos intermedios de tamaño desconocido, pero parece que debería haber una alternativa .NET "segura".
¿Cuál es la mejor manera de lograr esto en C#? He mirado alrededor y he encontrado algunas pistas ("uniones" usando FieldOffset, código "inseguro" usando punteros, Marshalling), pero ninguna parece funcionar para esta situación, sin usar algún tipo de copia/conversión. Me gustaría evitar BinaryReader.ReadUInt16(), ya que es muy lento y consume mucha CPU. En mi máquina hay una diferencia de aproximadamente 25x en la velocidad entre un bucle for() con ReadUInt16(), y la lectura de los bytes directamente en una matriz de bytes [] con una sola lectura(). Esa relación podría ser incluso mayor con las E/S sin bloqueo (solapamiento del procesamiento "útil" mientras se espera la E/S del disco).
Idealmente, me gustaría simplemente "disfrazar" una matriz ushort [] como una matriz de bytes [] para poder llenarla directamente con Read(), o de alguna manera tener Read() llenar la matriz ushort [] directamente:
// DOES NOT WORK!!
public void GetData(FileStream f, ushort [] buf, int startsamp, int nsamps)
{
f.Position = startsamp*sizeof(ushort);
f.Read(buf, 0, nsamps);
}
Pero no hay un método de lectura() que tome una matriz ushort [], solo una matriz de bytes [].
¿Se puede hacer esto directamente en C#, o necesito usar código no administrado, o una biblioteca de terceros, o debo recurrir a la conversión de muestra por muestra que requiere mucha CPU? Aunque se prefiere "seguro", estoy de acuerdo con el uso de código "inseguro", o algún truco con Marshal, aún no lo he descubierto.
¡Gracias por cualquier orientación!
[ACTUALIZACIÓN]
quería añadir algo de código según lo sugerido por DTB, ya que parece que hay pocos ejemplos preciosos de ReadArray alrededor. Este es muy simple, sin que se muestre ningún error.
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);
}
Los datos se vuelcan de forma segura en la matriz pasada. También puede especificar un tipo para tipos más complejos. Parece capaz de inferir los tipos simples por su cuenta, pero con el especificador de tipo, que se vería así:
mmacc.ReadArray<short>(startsamp*sizeof(short), data, 0, nsamps);
[UPATE2]
que quería añadir el código como se sugiere por Ben de respuesta ganadora, en forma de "huesos desnudos", similar a la anterior, a modo de comparación. Este código fue compilado y probado, y funciona, y es RÁPIDO. Utilicé el tipo de SafeFileHandle directamente en DllImport (en lugar del IntPtr más habitual) para simplificar las cosas.
[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);
}
Si no desea utilizar 'BinaryReader.ReadUInt16();' probablemente necesite leer los datos en una matriz de bytes y luego procesar la matriz de bytes. Incluso si lo divide en pedazos, los datos de 100M de 2 bytes son ~ 200MB, por lo que debería poder leerlos en la memoria de una sola vez y procesarlos. – Nate
'fread' probablemente no es de copia cero de E/S, está almacenado en búfer (todas las funciones' stdio.h' pueden almacenarse en búfer y se encuentran en la mayoría de las implementaciones). –
Ben, concede que el sistema operativo puede hacer copias bajo el capó, pero es la copia adicional en el programa lo que estaba tratando de evitar. – dale