2009-05-18 2 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

특정 응용 프로그램의 경우 단 한 가지만 명확한 답을 줄 수 있습니다. 프로필.

큰 PInvoke 솔루션으로 작업하면서 배웠던 교훈이 여기에 있습니다. 데이터를 마샬링하는 가장 효과적인 방법은 blittable 인 필드를 마샬링하는 것입니다. CLR은 네이티브 코드와 관리 코드간에 데이터를 이동하는 memcpy에 대한 간단한 작업을 수행 할 수 있음을 의미합니다. 간단히 말해, 모든 비 인라인 배열과 문자열을 구조 밖으로 가져옵니다. 네이티브 구조체에 존재하는 경우에는 IntPtr로 표현하고 필요에 따라 값을 관리되는 코드로 마샬링합니다.

Marshal.PtrToStructure와 기본 API 역 참조를 사용하는 것의 차이점을 프로파일 링 한 적이 없습니다. 이것은 아마도 PtrToStructure가 프로파일 링을 통해 병목 현상으로 드러난다면 투자해야 할 대상 일 것입니다.

큰 계층 구조의 경우 전체 구조를 한 번에 관리 코드로 끌어들이는 대신 요청에 따라 마샬링합니다. 큰 나무 구조를 다룰 때이 문제를 가장 많이 겪었습니다. 개별 노드를 마샬링하는 것은 매우 빠르며 성능면에서는 현시점에서 필요한 것을 마샬링하기 만하면됩니다.

7

Marshal.PtrToStructure을 사용하면 다소 느립니다. 나는 매우 도움이 바이너리 데이터를 읽는 다른 방법을 비교 (벤치마킹)되어 CodeProject의에서 다음 문서 발견 :

Fast Binary File Reading with C#

+1

감사를 해당 C#을 필드에 바이트를 배치 할 BitConverter를 사용하여 다음 하나의 구조체를 나타내는 바이트의 그룹을 읽고하는 간단한 코드가 필요합니다,이 articlt뿐만 아니라 파일 핸들러 사이의 차이점을 보여줍니다뿐만 아니라 제공 구조체 변환의 바이트 예제. –

1

이이 질문의 범위를 벗어난 될 수있다,하지만 나는 것 Managed C++에서 fread() 또는 구조체에서 읽는 것과 비슷한 빠른 작업을 수행하는 작은 어셈블리를 작성하려고합니다. 일단 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++이나 마샬링과는 아무런 관련이없는 것처럼 보입니다. 그 밖의 무엇이 필요한지 구조를 알 것입니다. 분명히

당신이 ...

관련 문제