2009-05-18 21 views
5

私は、それぞれが1000以上のレコードを持つバイナリファイルを読み始めようとしています。新しいファイルが絶えず追加されているので、ディレクトリを監視して新しいファイルを受け取ったときに処理するWindowsサービスを作成しています。ファイルはC++プログラムで作成されています。私はC#で構造体の定義を作り直して、データをうまく読み込めますが、私がやっているやり方が最終的に私のアプリケーションを終了させるのではないかと心配しています。私は私が実際にC++アプリケーションと通信していないよので、GCHandleを使用する必要はないと思うC#の構造体を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 */ 
    } 
} 

、すべてのものは、マネージコードから行われているが、私は別の方法を知りません。

答えて

6

特定のアプリケーションでは、決定的な答えが1つだけあります。プロファイルしてください。

大規模なPInvokeソリューションで作業しながら学んだことが、ここで言われています。データをマーシャリングする最も効果的な方法は、blittableであるフィールドをマーシャリングすることです。つまり、CLRは、ネイティブコードとマネージコード間でデータを移動するmemcpyに相当する処理を簡単に実行できます。簡単に言うと、すべての非インライン配列と文字列を構造体から取り出します。それらがネイティブ構造に存在する場合は、それらをIntPtrで表現し、必要に応じて値をマネージコードにマーシャリングします。

Marshal.PtrToStructureとネイティブAPIの参照を使用することの違いをプロファイルしたことはありません。これはおそらくPtrToStructureがプロファイリングを介してボトルネックとして明らかにされるべきである場合に投資すべきものです。

大規模な階層の場合は、オンザフライでマーシャリングを行い、構造全体を一度にマネージコードにプルする必要があります。私は大きな木の構造を扱うときにこの問題に最もぶつかりました。個々のノードをマーシャリングすることは非常に高速です。それがblittableでパフォーマンスが賢明ならば、その時点で必要なものだけをマーシャリングするだけです。

7

Marshal.PtrToStructureを使用するのはかなり遅いです。私は、バイナリデータを読み取るのさまざまな方法が非常に有用比較(およびベンチマーク)さCodeProjectの上、以下の記事を見つけた:

Fast Binary File Reading with C#

+1

感謝を必要とする、このarticltは、ファイルハンドラの違いを示しているが、も与えていないだけバイトから構造への変換の良い例です。 –

1

これは、あなたの質問の境界外かもしれませんが、私は次のようになりfread()や同様の速さで構造体を読み込んだManaged C++の小さなアセンブリを書く傾向があります。読み込んだら、C#を使って必要なものをすべて処理できます。

2

JaredParの包括的な答えに加えて、GCHandleを使用する必要はなく、代わりに安全でないコードを使用することができます。

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

GCHandle/fixed文の全体の目的は、ビューのGCの点から不動メモリを作り、/ピン特定のメモリセグメントを固定することです。メモリが移動可能だった場合、再配置するとポインタが無効になります。

どの方法が高速かわかりません。

+0

提案していただきありがとうございます。私はJarredの提案のようにプロファイルするつもりですが、この方法でプロファイルを作成します。 – scottm

0

ここは小さなクラスですが、構造化されたファイルで遊んでいます。それは私が安全でないということに恥ずかしがり屋(私が匹敵する性能を置き換えて維持しようとしていたことだったのです。)

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 

使用する:

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

(かなり新しいここでは、それは投稿するあまりありませんでした願っています...ちょうどクラスに貼り付け、コメントか何かをチョップしませんでしたこれを短縮するには)

0

これはC++やマーシャリングとも関係がないようです。他に何が必要なのかあなたは構造を知っています。

明らかにあなたが1つの構造体を表すバイトのグループを読み、それに対応するC#のフィールドにバイトを配置するBitConverterを使用する単純なコード..

関連する問題