Domanda

Sono per iniziare tonnellate di lettura di file binari, ciascuno con 1000 o più record. I nuovi file vengono aggiunti costantemente in modo che sto scrivendo un servizio di Windows per monitorare le directory ed elaborare i nuovi file non appena vengono ricevute. I file sono stati creati con un programma c ++. Ho ricreato le definizioni struct in C # e in grado di leggere la multa di dati, ma io sono preoccupato del fatto che il modo in cui lo sto facendo finirà per uccidere la mia applicazione.

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.

È stato utile?

Soluzione

Per la vostra particolare applicazione, solo una cosa vi darà la risposta definitiva: il profilo it.

Detto questo ecco le lezioni che ho imparato mentre si lavora con grandi soluzioni PInvoke. Il modo più efficace per il marshalling di dati è quello di maresciallo campi che sono copiabili. Significato il CLR può semplice fare ciò che equivale a un memcpy per spostare i dati tra il codice nativo e gestito. In termini semplici, ottenere tutti gli array non in linea e le stringhe dalle vostre strutture. Se sono presenti nella struttura nativa, rappresentarli con un IntPtr e il maresciallo dei valori su richiesta in codice gestito.

Non ho mai profilata la differenza tra l'utilizzo vs Marshal.PtrToStructure avere un'API native dereference il valore. Questo è probabilmente qualcosa che si dovrebbe investire in deve PtrToStructure essere rivelata come un collo di bottiglia tramite profiling.

Per gerarchie grande maresciallo su richiesta vs. tirando un'intera struttura in codice gestito in una sola volta. Ho eseguito in questo problema di più quando si tratta di grandi strutture ad albero. Marshalling un singolo nodo è molto veloce se si tratta di copiabili e le prestazioni saggio funziona a solo marshalling di quello che ti serve in quel momento.

Altri suggerimenti

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

  

Fast File binario di lettura con C #

In aggiunta alla risposta esauriente di JaredPar, non è necessario utilizzare GCHandle, è possibile utilizzare il codice non sicuro, invece.

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 è mobile, qualsiasi trasferimento renderebbe i puntatori non validi.

Non è sicuro che il senso è più veloce però.

Questo può essere al di fuori dei limiti della tua domanda, ma sarei propenso a dare un po 'di assemblaggio in Managed C ++ che ha fatto un fread () o qualcosa di simile veloce da leggere nelle strutture. Una volta che hai li leggono, è possibile utilizzare C # per fare tutto ciò che serve con loro.

ecco una piccola classe ho fatto un po 'indietro mentre giocava con i file strutturati. è stato il metodo più veloce ho potuto capire al momento timido di intenzione non sicuro (che era quello che stavo cercando di sostituire e mantenere le prestazioni paragonabili.)

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 qualsiasi cosa per accorciarlo.)

Sembra questo non ha nulla a che fare né con C ++, né di smistamento. Sapete la struttura cos'altro avete bisogno.

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top