2009-05-18 64 views
5

Tôi sắp bắt đầu đọc tấn tệp nhị phân, mỗi tệp có 1000 bản ghi trở lên. Các tệp mới được thêm liên tục nên tôi đang viết một dịch vụ Windows để theo dõi các thư mục và xử lý các tệp mới khi chúng được nhận. Các tệp được tạo bằng chương trình C++. Tôi đã tái tạo các định nghĩa struct trong C# và có thể đọc dữ liệu tốt, nhưng tôi lo ngại rằng cách tôi đang làm nó cuối cùng sẽ giết ứng dụng của tôi.Cách hiệu quả nhất để so sánh cấu trúc C++ với C# là gì?

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 */ 
    } 
} 

Tôi không nghĩ rằng tôi cần phải sử dụng GCHandle vì tôi không thực sự giao tiếp với các ứng dụng ++ C, mọi thứ đang được thực hiện từ mã được quản lý, nhưng tôi không biết về một phương pháp khác.

Trả lời

6

Đối với ứng dụng cụ thể của bạn, chỉ một điều sẽ cung cấp cho bạn câu trả lời dứt khoát: Hồ sơ nó.

Điều đó được nói ở đây là những bài học tôi đã học được khi làm việc với các giải pháp PInvoke lớn. Cách hiệu quả nhất để so sánh dữ liệu là sắp xếp các trường có thể được blittable. Có nghĩa là CLR có thể đơn giản làm những gì số tiền để một memcpy để di chuyển dữ liệu giữa mã nguồn gốc và quản lý. Nói một cách đơn giản, hãy lấy tất cả các mảng và chuỗi không phải nội tuyến ra khỏi cấu trúc của bạn. Nếu chúng có mặt trong cấu trúc gốc, hãy trình bày chúng với một IntPtr và so sánh các giá trị theo yêu cầu vào mã được quản lý.

Tôi chưa bao giờ lược tả sự khác biệt giữa việc sử dụng Marshal.PtrToStructure so với việc có một tham chiếu API gốc giá trị. Đây có lẽ là một cái gì đó bạn nên đầu tư vào nên PtrToStructure được tiết lộ như một nút cổ chai thông qua hồ sơ.

Đối với các cấu trúc phân cấp lớn so khớp theo yêu cầu so với kéo toàn bộ cấu trúc vào mã được quản lý tại một thời điểm. Tôi đã gặp phải vấn đề này nhiều nhất khi giao dịch với các cấu trúc cây lớn. Marshalling một nút riêng lẻ là rất nhanh nếu nó blittable và hiệu suất khôn ngoan nó hoạt động ra để chỉ marshal những gì bạn cần tại thời điểm đó.

7

Sử dụng Marshal.PtrToStructure khá chậm. Tôi tìm thấy bài viết sau đây trên CodeProject được so sánh (và benchmark) cách khác nhau để đọc dữ liệu nhị phân rất hữu ích:

Fast Binary File Reading with C#

+1

Cảm ơn, articlt này không chỉ cho thấy sự khác biệt giữa bộ xử lý tập tin, mà còn cung cấp cho một ví dụ tốt về byte để chuyển đổi cấu trúc. –

1

Đây có thể là ngoài giới hạn của câu hỏi của bạn, nhưng tôi sẽ có khuynh hướng viết một assembly nhỏ trong Managed C++ mà đã làm một fread() hoặc một cái gì đó tương tự nhanh để đọc trong các cấu trúc. Khi bạn đã đọc chúng, bạn có thể sử dụng C# để thực hiện mọi thứ bạn cần với chúng.

2

Ngoài câu trả lời toàn diện của JaredPar, bạn không cần sử dụng GCHandle, bạn có thể sử dụng mã không an toàn để thay thế.

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

Toàn bộ mục đích của báo cáo kết quả GCHandle/fixed là để ghim/sửa chữa các phân đoạn bộ nhớ đặc biệt, làm cho bộ nhớ bất di bất dịch từ quan điểm của GC của. Nếu bộ nhớ được di chuyển, bất kỳ việc di dời nào cũng sẽ làm cho con trỏ của bạn không hợp lệ.

Bạn không chắc chắn cách nào sẽ nhanh hơn.

+0

Cảm ơn bạn đã đề xuất. Tôi sẽ hồ sơ như Jarred đề nghị, nhưng tôi cũng sẽ hồ sơ bằng cách sử dụng phương pháp này. – scottm

0

đây là một lớp học nhỏ tôi đã quay trở lại khi đang chơi với các tệp có cấu trúc. đó là phương pháp nhanh nhất tôi có thể tìm ra tại thời điểm nhút nhát đi không an toàn (đó là những gì tôi đã cố gắng để thay thế và duy trì hiệu suất tương đương.)

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 

sử dụng:

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

(khá mới ở đây, hy vọng đó không phải là quá nhiều để gửi ... chỉ cần dán trong lớp, không chặt ra những ý kiến ​​hay bất cứ điều gì để rút ngắn nó.)

0

Dường như điều này không liên quan gì đến cả C++ lẫn bản dịch. Bạn biết cấu trúc bạn cần gì khác.

Rõ ràng bạn cần một mã đơn giản mà sẽ đọc nhóm các byte đại diện cho một struct và sau đó sử dụng BitConverter đặt byte vào tương ứng với C# các lĩnh vực ..

Các vấn đề liên quan