2010-07-08 25 views
6

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); 
} 
+0

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

+2

'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). –

+0

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

Respuesta

2

dtb's answer es una mejor manera (en realidad, tiene que copiar los datos, así, no hay ganancia allí), pero sólo quería señalar que para extraer ushort valores de una matriz de bytes que debe utilizar BitConverter no BinaryReader

EDIT: código de ejemplo para p/invocando ReadFile:

[DllImport("kernel32.dll", SetLastError=true)] 
[return:MarshalAs(UnmanagedType.Bool)] 
static extern bool ReadFile(IntPtr handle, IntPtr buffer, uint numBytesToRead, out uint numBytesRead, IntPtr overlapped); 

[DllImport("kernel32.dll", SetLastError=true)] 
[return:MarshalAs(UnmanagedType.Bool)] 
static extern bool SetFilePointerEx(IntPtr hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod); 

unsafe bool read(FileStream fs, ushort[] buffer, int offset, int count) 
{ 
    if (null == fs) throw new ArgumentNullException(); 
    if (null == buffer) throw new ArgumentNullException(); 
    if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentException(); 
    uint bytesToRead = 2 * count; 
    if (bytesToRead < count) throw new ArgumentException(); // detect integer overflow 
    long offset = fs.Position; 
    SafeFileHandle nativeHandle = fs.SafeFileHandle; // clears Position property 
    try { 
    long unused; 
    if (!SetFilePositionEx(nativeHandle, offset, out unused, 0); 
    fixed (ushort* pFirst = &buffer[offset]) 
     if (!ReadFile(nativeHandle, new IntPtr(pFirst), bytesToRead, out bytesToRead, IntPtr.Zero) 
     return false; 
    if (bytesToRead < 2 * count) 
     return false; 
    offset += bytesToRead; 
    return true; 
    } 
    finally { 
    fs.Position = offset; // restore Position property 
    } 
} 
+1

Ben, gracias, eché un vistazo a BitConverter(), pero no estoy seguro de entender su sugerencia. BinaryReader() es para leer desde archivos (lo cual estoy haciendo), y BitConverter() es para convertir matrices de bytes [] existentes a otros tipos. ¿No es BinaryReader(). ReadUInt16() equivalente a leer en los bytes una matriz y llamar a BitConverter(). ToUInt16()? Tal vez estoy malinterpretando ... – dale

+0

Pero 'ReadUInt16' solo lee un elemento a la vez ... que es una manera horrible de hacer E/S. –

+0

No, no lo es. BinaryReader es responsable de convertir los bytes de una secuencia subyacente al tipo solicitado, sin leer los bytes de IO en primer lugar. –

8

Puede usar un MemoryMappedFile. Después de haber mapeado la memoria del archivo, puede crear una vista (es decir, un MemoryMappedViewAccessor) que proporciona un método ReadArray<T>.Este método puede leer estructuras del archivo sin clasificación, y funciona con tipos primitivos que se encuentran en ushort.

+2

Este es un gran enfoque si tiene .NET 4. Tiene incluso menos copia que el código C que Dale quería emular. En versiones anteriores de .NET, probablemente tendría que p/invocar 'ReadFile' para emular el código C, o p/invocar' CreateFileMapping' para esta forma más rápida. –

+0

dtb, gracias, no había visto ReadArray(), ¡e incluso Google aún no lo sabe! Parece una herramienta MUY práctica. Hice un poco de tiempo, y es aproximadamente el doble de rápido que un bucle for() con ReadUInt16(), así que sospecho que está haciendo algo de copia debajo del capó (la lectura de bytes sin conversión sigue siendo aproximadamente 10 veces más rápida). Veo que la clase de Accessor tiene muchos métodos similares a BinaryReader. Me pregunto si MS eventualmente podría agregar un método ReadArray() a BinaryReader, entonces podríamos leer estructuras directamente desde una secuencia sin tener que pasar por la asignación de memoria. – dale

+0

Tiene razón, por supuesto, dado que los metadatos .NET se almacenan en el mismo bloque de memoria que el contenido, no tiene más remedio que copiar. Si P/Invoca 'CreateFile' y' ReadFile' pasando un puntero al primer elemento de su 'ushort []' (requiere un código inseguro) debería obtener la misma velocidad que al leer un 'byte []'. –

1

que podría ser un poco tarde al juego aquí ... pero el método más rápido que encontré fue el uso de una combinación de las respuestas anteriores .

Si hago lo siguiente:

MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(somePath); 
Stream io = mmf.CreateViewStream(); 

int count; 
byte[] byteBuffer = new byte[1024 << 2]; 
ushort[] dataBuffer = new ushort[buffer.Length >> 1]; 

while((count = io.Read(byteBuffer, 0, byteBuffer.Length)) > 0) 
    Buffer.BlockCopy(buffer, 0, dataBuffer, 0, count); 

Esto era ~ 2 veces más rápido que la respuesta aceptada.

Para mí, el método unsafe era el mismo que el Buffer.BlockCopy sin el MemoryMappedFile. El MemoryMappedFile redujo el tiempo.

Cuestiones relacionadas