Domanda

mi sono imbattuto in una situazione in cui ho una abbastanza grande di file che ho bisogno di leggere i dati binari da.

Di conseguenza, mi sono reso conto che l'implementazione predefinita BinaryReader in .NET è piuttosto lento. 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));
        }
    }
...
}

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

E 'ancora un po' mi sconcerta il motivo per cui ci vuole ancora tanto tempo per leggere un file relativamente piccolo come. Se copio il file da un disco all'altro ci vogliono solo un paio di secondi, quindi il throughput del disco non è un problema.

I ulteriormente 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 smistamento per mappare l'intero file direttamente in memoria in cima ad una qualche struttura su misura, in quanto i dati è lineare, dimensioni fisse e sequenziale.

RISOLTO: sono arrivato alla conclusione che il buffer di FileStream / BufferedStream sono irregolari. Si prega di consultare la risposta accettata e la mia risposta personale (con la soluzione) al di sotto.

È stato utile?

Soluzione

Quando si esegue un FileCopy, 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'attuazione flusso è abbastanza intelligente per tamponare, avete ancora almeno 500 MB / 4 = 131.072.000 chiamate API.

Non è più saggio di leggere solo un grande pezzo di dati, e quindi passare attraverso di essa in modo sequenziale, e ripetere fino a quando il file è stato elaborato?

Altri suggerimenti

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, il br.BaseStream.Length proprietà su un FileStream fa una (relativamente) chiamata di sistema lento per ottenere la dimensione del file in ogni 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.

Interessante, leggere l'intero file in un buffer e che attraverso di essa in memoria fatto una grande differenza. Questo è a costo di memoria, ma abbiamo un sacco.

Questo mi fa pensare che il FileStream (o BufferedStream di per quella materia) implementazione buffer è imperfetta, perché non importa quale buffer di dimensioni ho provato, prestazioni ancora risucchiato.

  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.

Un avvertimento; si potrebbe desiderare di controllare due volte il vostro CPU endianness ... assumendo little-endian non è molto di sicurezza (si pensi: Itanium, ecc).

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

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