Pergunta

Estou prestes a começar toneladas de leitura de arquivos binários, cada um com 1000 ou mais registros. Novos arquivos são adicionados constantemente por isso estou escrevendo um serviço do Windows para monitorar os diretórios e processar novos arquivos conforme eles são recebidos. Os arquivos foram criados com um programa c ++. Eu recriado as definições de struct em C # e pode ler a multa de dados, mas eu estou preocupado que a maneira que eu estou fazendo isso acabará por matar a minha aplicação.

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

Eu não acho que eu preciso usar GCHandle porque eu não estou realmente se comunicando com o aplicativo C ++, tudo está sendo feito a partir do código gerenciado, mas eu não sei de um método alternativo.

Foi útil?

Solução

Para sua aplicação em particular, só uma coisa lhe dará a resposta definitiva: Perfil-lo.

Dito isto, aqui estão as lições que eu aprendi ao trabalhar com grandes soluções PInvoke. A maneira mais eficaz de dados marechal é a campos marechal que são blittable. Ou seja, o CLR pode simplesmente fazer o que equivale a um memcpy para mover dados entre o código nativo e gerenciado. Em termos simples, obter todas as matrizes não-in-line e cordas fora de suas estruturas. Se eles estão presentes na estrutura nativa, representá-los com um IntPtr e empacotar os valores sob demanda em código gerenciado.

Eu não já perfilado a diferença entre usar Marshal.PtrToStructure vs. ter um API nativa dereference o valor. Este é provavelmente algo que você deve investir in deve PtrToStructure ser revelado como um gargalo por meio de perfis.

Para grandes hierarquias organizar sob demanda vs. puxando toda uma estrutura em código gerenciado em uma única vez. Eu tenho que correr para esta questão o mais quando se trata de grandes estruturas de árvore. Mobilizar um nó individual é muito rápido se é blittable e desempenho sábio ele funciona apenas marshal o que você precisa nesse momento.

Outras dicas

Usando Marshal.PtrToStructure é bastante lento. Eu encontrei o seguinte artigo sobre CodeProject que está a comparar (e benchmarking) diferentes formas de leitura de dados binários muito útil:

Reading rápido arquivo binário com C #

Além de resposta abrangente da JaredPar, você não precisa usar GCHandle, você pode usar o código inseguro em seu lugar.

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

O propósito da declaração GCHandle / fixed é pin / corrigir o segmento de memória particular, fazendo o bem a memória do ponto de vista do GC. Se a memória era móvel, qualquer deslocalização tornaria seus ponteiros inválidos.

Não tenho certeza qual o caminho é mais rápido embora.

Isto pode ser fora dos limites da sua pergunta, mas eu estaria inclinado a escrever um pouco de montagem em C ++ gerenciado que fez um fread () ou algo semelhante rápido de ler nas estruturas. Uma vez que você tem deles ler, você pode usar C # para fazer tudo o que precisa com eles.

aqui está uma pequena classe que eu fiz um tempo atrás enquanto brincava com arquivos estruturados. era o método mais rápido que eu pudesse descobrir no momento tímido de ir inseguro (que era o que eu estava tentando substituir e manter um desempenho comparável.)

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

Para usar:

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

(muito novo aqui, espero que não era demais para post ... apenas colado na classe, não cortar os comentários ou nada para reduzi-lo.)

Parece que esta tem nada a ver nem com C ++, nem triagem. Você sabe que a estrutura que mais você precisa.

Obviamente você precisa de um código simples que irá ler grupo de bytes que representam um struct e em seguida, usando BitConverter para lugar bytes em campos C # correspondente ..

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top