2009-05-18 25 views
5

Estoy a punto de comenzar a leer toneladas de archivos binarios, cada uno con 1000 o más registros. Los nuevos archivos se agregan constantemente, por lo que estoy escribiendo un servicio de Windows para monitorear los directorios y procesar nuevos archivos a medida que se reciben. Los archivos fueron creados con un programa C++. He recreado las definiciones de estructura en C# y puedo leer bien los datos, pero me preocupa que la forma en que lo hago eventualmente mate mi aplicación.¿Cuál es la forma más eficiente de ordenar las estructuras de C++ a C#?

using (BinaryReader br = new BinaryReader(File.Open("myfile.bin", FileMode.Open))) 
{ 
    long pos = 0L; 
    long length = br.BaseStream.Length; 

    CPP_STRUCT_DEF record; 
    byte[] buffer = new byte[Marshal.SizeOf(typeof(CPP_STRUCT_DEF))]; 
    GCHandle pin; 

    while (pos < length) 
    { 
     buffer = br.ReadBytes(buffer.Length); 
     pin = GCHandle.Alloc(buffer, GCHandleType.Pinned); 
     record = (CPP_STRUCT_DEF)Marshal.PtrToStructure(pin.AddrOfPinnedObject(), typeof(CPP_STRUCT_DEF)); 
     pin.Free(); 

     pos += buffer.Length; 

     /* Do stuff with my record */ 
    } 
} 

No creo que tenga que utilizar GCHandle porque no estoy realmente la comunicación con la aplicación de C++, todo lo que se está haciendo desde el código administrado, pero no sé de un método alternativo.

Respuesta

6

Para su aplicación particular, solo una cosa le dará la respuesta definitiva: perfilarla.

Lo que se dice aquí son las lecciones que he aprendido al trabajar con soluciones grandes de PInvoke. La forma más efectiva de recopilar datos es ordenar los campos que son blittables. Lo que significa que CLR puede hacer lo que equivale a una memcpy para mover datos entre el código nativo y el código administrado. En términos simples, obtenga todas las matrices y cadenas no en línea de sus estructuras. Si están presentes en la estructura nativa, represéntelos con un IntPtr y calcule los valores bajo demanda en el código administrado.

Nunca he perfilado la diferencia entre usar Marshal.PtrToStructure o tener una desreferencia API nativa del valor. Esto es probablemente algo en lo que debería invertir si PtrToStructure se revela como un cuello de botella a través de la creación de perfiles.

Para jerarquías grandes, mariscal según demanda o tirando de una estructura completa en código administrado a la vez. Me he encontrado con este problema al máximo cuando se trata de grandes estructuras de árboles. Alinear un nodo individual es muy rápido si es blittable y en cuanto al rendimiento, funciona solo para reunir lo que necesita en ese momento.

7

El uso de Marshal.PtrToStructure es bastante lento. He encontrado el siguiente artículo en CodeProject que está comparando (y la evaluación comparativa) diferentes maneras de leer datos binarios de gran ayuda:

Fast Binary File Reading with C#

+1

Gracias, este articlt no sólo muestra las diferencias entre los controladores de archivos, sino que también da un buen ejemplo de conversión byte a struct. –

1

esto puede estar fuera de los límites de su pregunta, pero estaría inclinado a escribir una pequeña asamblea en Managed C++ que hizo un fread() o algo similarmente rápido para leer en las estructuras. Una vez que los hayas leído, puedes usar C# para hacer todo lo que necesites con ellos.

2

Además de la respuesta completa de JaredPar, no necesita usar GCHandle, puede usar un código no seguro en su lugar.

fixed(byte *pBuffer = buffer) { 
    record = *((CPP_STRUCT_DEF *)pBuffer); 
} 

Todo el propósito de la declaración GCHandle/fixed es al pin/fijar el segmento de memoria en particular, por lo que la memoria inamovible desde el punto de vista del GC. Si la memoria se podía mover, cualquier reubicación haría que los punteros no fueran válidos.

No estoy seguro de qué camino es más rápido.

+0

Gracias por la sugerencia. Voy a hacer un perfil como sugirió Jarred, pero también haré un perfil con este método. – scottm

0

he aquí una pequeña clase que hice hace un tiempo mientras jugaba con archivos estructurados. fue el método más rápido que pude encontrar en el momento de no ser seguro (que era lo que estaba tratando de reemplazar y mantener un rendimiento comparable).)

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Runtime.InteropServices; 

namespace PersonalUse.IO { 

    public sealed class RecordReader<T> : IDisposable, IEnumerable<T> where T : new() { 

     const int DEFAULT_STREAM_BUFFER_SIZE = 2 << 16; // default stream buffer (64k) 
     const int DEFAULT_RECORD_BUFFER_SIZE = 100; // default record buffer (100 records) 

     readonly long _fileSize; // size of the underlying file 
     readonly int _recordSize; // size of the record structure 
     byte[] _buffer; // the buffer itself, [record buffer size] * _recordSize 
     FileStream _fs; 

     T[] _structBuffer; 
     GCHandle _h; // handle/pinned pointer to _structBuffer 

     int _recordsInBuffer; // how many records are in the buffer 
     int _bufferIndex; // the index of the current record in the buffer 
     long _recordPosition; // position of the record in the file 

     /// <overloads>Initializes a new instance of the <see cref="RecordReader{T}"/> class.</overloads> 
     /// <summary> 
     /// Initializes a new instance of the <see cref="RecordReader{T}"/> class. 
     /// </summary> 
     /// <param name="filename">filename to be read</param> 
     public RecordReader(string filename) : this(filename, DEFAULT_STREAM_BUFFER_SIZE, DEFAULT_RECORD_BUFFER_SIZE) { } 

     /// <summary> 
     /// Initializes a new instance of the <see cref="RecordReader{T}"/> class. 
     /// </summary> 
     /// <param name="filename">filename to be read</param> 
     /// <param name="streamBufferSize">buffer size for the underlying <see cref="FileStream"/>, in bytes.</param> 
     public RecordReader(string filename, int streamBufferSize) : this(filename, streamBufferSize, DEFAULT_RECORD_BUFFER_SIZE) { } 

     /// <summary> 
     /// Initializes a new instance of the <see cref="RecordReader{T}"/> class. 
     /// </summary> 
     /// <param name="filename">filename to be read</param> 
     /// <param name="streamBufferSize">buffer size for the underlying <see cref="FileStream"/>, in bytes.</param> 
     /// <param name="recordBufferSize">size of record buffer, in records.</param> 
     public RecordReader(string filename, int streamBufferSize, int recordBufferSize) { 
      _fileSize = new FileInfo(filename).Length; 
      _recordSize = Marshal.SizeOf(typeof(T)); 
      _buffer = new byte[recordBufferSize * _recordSize]; 
      _fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, streamBufferSize, FileOptions.SequentialScan); 

      _structBuffer = new T[recordBufferSize]; 
      _h = GCHandle.Alloc(_structBuffer, GCHandleType.Pinned); 

      FillBuffer(); 
     } 

     // fill the buffer, reset position 
     void FillBuffer() { 
      int bytes = _fs.Read(_buffer, 0, _buffer.Length); 
      Marshal.Copy(_buffer, 0, _h.AddrOfPinnedObject(), _buffer.Length); 
      _recordsInBuffer = bytes/_recordSize; 
      _bufferIndex = 0; 
     } 

     /// <summary> 
     /// Read a record 
     /// </summary> 
     /// <returns>a record of type T</returns> 
     public T Read() { 
      if(_recordsInBuffer == 0) 
       return new T(); //EOF 
      if(_bufferIndex < _recordsInBuffer) { 
       // update positional info 
       _recordPosition++; 
       return _structBuffer[_bufferIndex++]; 
      } else { 
       // refill the buffer 
       FillBuffer(); 
       return Read(); 
      } 
     } 

     /// <summary> 
     /// Advances the record position without reading. 
     /// </summary> 
     public void Next() { 
      if(_recordsInBuffer == 0) 
       return; // EOF 
      else if(_bufferIndex < _recordsInBuffer) { 
       _bufferIndex++; 
       _recordPosition++; 
      } else { 
       FillBuffer(); 
       Next(); 
      } 
     } 

     public long FileSize { 
      get { return _fileSize; } 
     } 

     public long FilePosition { 
      get { return _recordSize * _recordPosition; } 
     } 

     public long RecordSize { 
      get { return _recordSize; } 
     } 

     public long RecordPosition { 
      get { return _recordPosition; } 
     } 

     public bool EOF { 
      get { return _recordsInBuffer == 0; } 
     } 

     public void Close() { 
      Dispose(true); 
     } 

     void Dispose(bool disposing) { 
      try { 
       if(disposing && _fs != null) { 
        _fs.Close(); 
       } 
      } finally { 
       if(_fs != null) { 
        _fs = null; 
        _buffer = null; 
        _recordPosition = 0; 
        _bufferIndex = 0; 
        _recordsInBuffer = 0; 
       } 
       if(_h.IsAllocated) { 
        _h.Free(); 
        _structBuffer = null; 
       } 
      } 
     } 

     #region IDisposable Members 

     public void Dispose() { 
      Dispose(true); 
     } 

     #endregion 

     #region IEnumerable<T> Members 

     public IEnumerator<T> GetEnumerator() { 
      while(_recordsInBuffer != 0) { 
       yield return Read(); 
      } 
     } 

     #endregion 

     #region IEnumerable Members 

     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { 
      return GetEnumerator(); 
     } 

     #endregion 

    } // end class 

} // end namespace 

de usar:

using(RecordReader<CPP_STRUCT_DEF> reader = new RecordReader<CPP_STRUCT_DEF>(path)) { 
    foreach(CPP_STRUCT_DEF record in reader) { 
     // do stuff 
    } 
} 

(bastante nuevo aquí, espero que no era demasiado para publicar ... acaba de pegar en la clase, no cortar los comentarios o nada para acortarlo.)

0

Parece que esto no tiene nada que ver ni con C++ ni con la clasificación. Usted conoce la estructura, qué más necesita.

Obviamente se necesita un código simple que leerá grupo de bytes que representa una estructura y luego usando BitConverter colocar bytes en sus correspondientes campos de C# ..

Cuestiones relacionadas