2009-08-06 11 views
22

Mi sono imbattuto in una situazione in cui ho un file piuttosto grande di cui ho bisogno per leggere i dati binari.BinaryReader più veloce (non sicuro) in .NET

Di conseguenza, mi sono reso conto che l'implementazione BinaryReader predefinita in .NET è piuttosto lenta. Su guardando con .NET Reflector mi sono imbattuto in questo:

public virtual int ReadInt32() 
{ 
    if (this.m_isMemoryStream) 
    { 
     MemoryStream stream = this.m_stream as MemoryStream; 
     return stream.InternalReadInt32(); 
    } 
    this.FillBuffer(4); 
    return (((this.m_buffer[0] | (this.m_buffer[1] << 8)) | (this.m_buffer[2] << 0x10)) | (this.m_buffer[3] << 0x18)); 
} 

Il che mi sembra estremamente inefficiente, pensando a come i computer sono stati progettati per funzionare con valori a 32 bit in quanto la CPU a 32 bit è stato inventato.

Così ho fatto il mio (non sicuro) di classe FastBinaryReader con codice come questo, invece:

public unsafe class FastBinaryReader :IDisposable 
{ 
    private static byte[] buffer = new byte[50]; 
    //private Stream baseStream; 

    public Stream BaseStream { get; private set; } 
    public FastBinaryReader(Stream input) 
    { 
     BaseStream = input; 
    } 


    public int ReadInt32() 
    { 
     BaseStream.Read(buffer, 0, 4); 

     fixed (byte* numRef = &(buffer[0])) 
     { 
      return *(((int*)numRef)); 
     } 
    } 
... 
} 

che è molto più veloce - sono riuscito a radersi 5-7 secondi dal tempo impiegato per leggere un 500   MB file, ma è ancora piuttosto lento nel complesso (29 secondi inizialmente e ~ 22 secondi ora con il mio FastBinaryReader).

È ancora un po 'sconcertante sul perché ci voglia così tanto tempo per leggere un file relativamente piccolo. Se copio il file da un disco a un altro ci vogliono solo un paio di secondi, quindi il throughput del disco non è un problema.

Ho inoltre inline il ReadInt32, ecc chiamate, e ho finito con questo codice:

using (var br = new FastBinaryReader(new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan))) 

    while (br.BaseStream.Position < br.BaseStream.Length) 
    { 
     var doc = DocumentData.Deserialize(br); 
     docData[doc.InternalId] = doc; 
    } 
} 

public static DocumentData Deserialize(FastBinaryReader reader) 
    { 
     byte[] buffer = new byte[4 + 4 + 8 + 4 + 4 + 1 + 4]; 
     reader.BaseStream.Read(buffer, 0, buffer.Length); 

     DocumentData data = new DocumentData(); 
     fixed (byte* numRef = &(buffer[0])) 
     { 
      data.InternalId = *((int*)&(numRef[0])); 
      data.b = *((int*)&(numRef[4])); 
      data.c = *((long*)&(numRef[8])); 
      data.d = *((float*)&(numRef[16])); 
      data.e = *((float*)&(numRef[20])); 
      data.f = numRef[24]; 
      data.g = *((int*)&(numRef[25])); 
     } 
     return data; 
    } 

Eventuali ulteriori idee su come rendere questo ancora più veloce? Stavo pensando che forse potrei usare il marshalling per mappare l'intero file direttamente nella memoria sopra una struttura personalizzata, dato che i dati sono lineari, dimensioni fisse e sequenziali.

RISOLTO: Sono giunto alla conclusione che Buffering/BufferedStream di FileStream sono difettosi. Si prega di vedere la risposta accettata e la mia risposta (con la soluzione) di seguito.

+0

Può essere utile: http://stackoverflow.com/questions/19558435/questo-è-il-best-buffer-size-when-using-binaryreader-to-read-big-files-1gb/19837238? noredirect = 1 # 19837238 –

risposta

9

Quando si esegue una filecopia, grandi blocchi di dati vengono letti e scritti sul disco.

Stai leggendo l'intero file quattro byte alla volta. Questo è destinato ad essere più lento. Anche se l'implementazione del flusso è abbastanza intelligente da bufferizzare, hai ancora almeno 500   MB/4 = 131072000 chiamate API.

Non è più saggio leggere solo una grande porzione di dati, quindi eseguirla in sequenza e ripetere finché il file non è stato elaborato?

+1

C'è un parametro nel costruttore FileStream che specifica la dimensione del buffer, quindi la lettura viene effettivamente eseguita in blocchi. Ho provato vari valori per la dimensione del buffer, ma non ci sono stati miglioramenti significativi. Dimensioni del buffer molto grandi in realtà danneggiano le prestazioni nelle mie misurazioni. – andreialecu

+0

continui a fare l'immenso numero di chiamate a "ReadInt32". Solo scaricarlo da una memoria consecutiva sarà molto più veloce. – Toad

+0

Per favore rileggi la domanda, non sto utilizzando ReadInt32 nell'implementazione reale, c'è solo 1 lettura per oggetto e tutte le conversioni sono in linea, vedi gli ultimi due blocchi di codice. – andreialecu

5

Un avvertimento; potresti voler ricontrollare il tuo CPU's endianness ... presumendo che little-endian non sia piuttosto sicuro (pensare: itanium ecc.).

Si potrebbe anche voler vedere se BufferedStream fa alcuna differenza (non sono sicuro che lo farà).

+0

Sì, sono a conoscenza dei problemi di endianess, ma questa è un'applicazione proprietaria in cui ho il pieno controllo sulla distribuzione. Per quanto riguarda BufferedStream, dalla mia comprensione il FileStream è già bufferizzato, quindi aggiungerebbe semplicemente un buffer intermedio non necessario. P.S .: Sto anche usando la tua libreria protobuf in questo progetto, così tante grazie per questo :) – andreialecu

+3

Ho appena fatto una nuova misura con un wrapping BufferedStream e, come anticipato, non c'è differenza. – andreialecu

9

Interessante, leggere l'intero file in un buffer e attraversarlo in memoria ha fatto un'enorme differenza. Questo è a costo della memoria, ma ne abbiamo in abbondanza.

Questo mi fa pensare che l'implementazione del buffer di FileStream (o BufferedStream per quella materia) sia difettosa, perché non importa quale buffer di dimensioni ho provato, le prestazioni sono ancora risucchiate.

using (var br = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan)) 
    { 
     byte[] buffer = new byte[br.Length]; 
     br.Read(buffer, 0, buffer.Length); 
     using (var memoryStream = new MemoryStream(buffer)) 
     { 
      while (memoryStream.Position < memoryStream.Length) 
      { 
       var doc = DocumentData.Deserialize(memoryStream); 
       docData[doc.InternalId] = doc; 
      } 
     } 
    } 

Giù per 2-5 secondi (dipende dalla cache del disco sto cercando di indovinare) ora da 22. Il che è abbastanza buono per ora.

+0

quindi la mia risposta non era così imperfetta; ^) – Toad

+3

Grazie. Ma c'è in realtà un problema con l'implementazione del buffer di .NET, perché ho provato una dimensione del buffer esattamente grande come il file (che avrebbe dovuto essere equivalente all'intermediario MemoryStream), e che comunque risucchiava dal punto di vista delle prestazioni. In teoria il tuo suggerimento avrebbe dovuto essere ridondante, ma in pratica - il jackpot. – andreialecu

+6

puoi solo dire var buffer = File.ReadAllBytes (cacheFilePath); salva un po 'di codice ed è molto più veloce – gjvdkamp

16

mi sono imbattuto in un problema di prestazioni simile con BinaryReader/FileStream, e dopo profilatura, ho scoperto che il problema non è con FileStream buffering, ma invece con questa linea:

while (br.BaseStream.Position < br.BaseStream.Length) { 

In particolare, la proprietà br.BaseStream.Length su un FileStream effettua una (relativamente) lenta chiamata di sistema per ottenere la dimensione del file su ciascun ciclo. Dopo aver cambiato il codice a questo:

long length = br.BaseStream.Length; 
while (br.BaseStream.Position < length) { 

e utilizzando una dimensione di buffer appropriata per la FileStream, ho raggiunto prestazioni simili all'esempio MemoryStream.

Problemi correlati