2009-05-18 16 views
5

Sto per iniziare a leggere tonnellate di file binari, ognuno con 1000 o più record. Nuovi file vengono aggiunti costantemente così sto scrivendo un servizio di Windows per monitorare le directory ed elaborare i nuovi file così come vengono ricevuti. I file sono stati creati con un programma C++. Ho ricreato le definizioni di struct in C# e posso leggere i dati bene, ma sono preoccupato che il modo in cui sto facendo finirà per uccidere la mia applicazione.Qual è il modo più efficiente per eseguire il marshalling delle strutture C++ su 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 */ 
    } 
} 

Non penso che ho bisogno di usare GCHandle perché io non sono in realtà la comunicazione con l'applicazione C++, tutto è stato fatto dal codice gestito, ma non so di un metodo alternativo.

risposta

6

Per la tua particolare applicazione, solo una cosa ti darà la risposta definitiva: Profile it.

Ciò detto qui sono le lezioni che ho imparato lavorando con le soluzioni PInvoke di grandi dimensioni. Il modo più efficace per effettuare il marshalling dei dati è quello di effettuare il marshalling dei campi che sono blittibili. Significa che il CLR può fare semplicemente ciò che equivale a una memcpy per spostare i dati tra codice nativo e codice gestito. In termini semplici, rimuovi tutti gli array e le stringhe non in linea dalle tue strutture. Se sono presenti nella struttura nativa, li rappresentano con un IntPtr e eseguono il marshalling dei valori su richiesta nel codice gestito.

Non ho mai profilato la differenza tra l'utilizzo di Marshal.PtrToStructure e l'avere un valore di deaferenza API nativa. Probabilmente è una cosa su cui dovresti investire se PtrToStructure dovesse rivelarsi un collo di bottiglia attraverso la profilazione.

Per gerarchie di grandi dimensioni che effettuano il marshalling su richiesta e che trascinano un'intera struttura nel codice gestito in una sola volta. Ho incontrato questo problema di più quando si tratta di grandi strutture ad albero. Effettuare il marshalling di un singolo nodo è molto veloce se è blittabile e le prestazioni saggia si risolvono in modo da fornire solo quello che è necessario in quel momento.

7

L'utilizzo di Marshal.PtrToStructure è piuttosto lento. Ho trovato il seguente articolo su CodeProject, che è il confronto (e benchmarking) diversi modi di lettura dei dati binari molto utile:

Fast Binary File Reading with C#

+1

Grazie, questo articlt non solo mostra le differenze tra i gestori di file, ma dà anche un bell'esempio di byte per la conversione delle struct. –

1

Questo può essere al di fuori dei limiti della tua domanda, ma vorrei essere incline a scrivere un piccolo assemblaggio in Managed C++ che ha fatto un fread() o qualcosa di simile veloce da leggere nelle strutture. Una volta che li hai letti, puoi usare C# per fare tutto il necessario con loro.

2

Oltre alla risposta completa di JaredPar, non è necessario utilizzare GCHandle, è possibile utilizzare invece il codice non sicuro.

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

Lo scopo della dichiarazione GCHandle/fixed è al pin/fissare il segmento di memoria particolare rendendo la memoria inamovibile dal punto di vista del GC. Se la memoria era mobile, qualsiasi riposizionamento renderebbe i tuoi indicatori invalidi.

Non so quale sia la strada più veloce.

+0

Grazie per il suggerimento. Vado al profilo come suggerito da Jarred, ma userò anche il profilo usando questo metodo. – scottm

0

Ecco una piccola classe che ho fatto un po 'indietro mentre giocavo con i file strutturati. era il metodo più veloce che riuscivo a capire al momento di non andare sicuro (che era quello che stavo cercando di sostituire e mantenere prestazioni comparabili.)

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 

da usare:

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

(abbastanza nuovo qui, spero che non era troppo da inviare ... appena incollato nella classe, non tagliare i commenti o niente per accorciarlo.)

0

Sembra che questo non abbia nulla a che fare né con il C++ né con il marshalling. Conosci la struttura di cos'altro hai bisogno.

Ovviamente è necessario un semplice codice che leggerà gruppo di byte che rappresentano una struct e quindi utilizzando BitConverter di inserire il byte in campi corrispondenti C# ..

Problemi correlati