Pergunta

me deparei com uma situação onde eu tenho um muito grande arquivo que eu preciso ler dados binários a partir de.

Por isso, eu percebi que a implementação BinaryReader padrão no .NET é bastante lento. Ao olhar para ele com .NET Reflector me deparei com esta:

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));
}

O que me parece extremamente ineficiente, pensando em como os computadores foram projetados para trabalhar com valores de 32 bits desde a CPU 32 bit foi inventado.

Então eu fiz minha própria classe FastBinaryReader (inseguro) com um código como este em vez disso:

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));
        }
    }
...
}

O que é muito mais rápido -. Eu consegui raspar 5-7 segundos do tempo que levou para ler um arquivo de 500 MB, mas ainda é bastante lento em geral (29 segundos inicialmente e ~ 22 segundos agora com minha FastBinaryReader)

Ainda tipo de defletores me a respeito de porque ele ainda tem muito tempo para ler esse arquivo relativamente pequeno. Se eu copiar o arquivo a partir de um disco para outro leva apenas um par de segundos, de modo rígido rendimento não é um problema.

Eu ainda inlined a ReadInt32, etc. chamadas, e acabei com este código:

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;
   }

Quaisquer outras idéias sobre como tornar isso ainda mais rápido? Eu estava pensando que talvez eu poderia usar triagem para mapear todo o arquivo diretamente para a memória em cima de uma estrutura personalizada, desde que os dados é linear, tamanho e seqüencial fixo.

RESOLVIDO: cheguei à conclusão de que o buffer de FileStream / BufferedStream são falhos. Por favor, veja a resposta aceita e minha própria resposta (com a solução) abaixo.

Foi útil?

Solução

Quando você faz uma FileCopy, grandes blocos de dados são lidos e gravados no disco.

Você está lendo o arquivo inteiro de quatro bytes de cada vez. Este é obrigado a ser mais lento. Ainda que a implementação corrente é inteligente o suficiente para o buffer, você ainda tem pelo menos 500 MB / 4 = 131072000 chamadas de API.

Não é mais sensato para apenas ler um pedaço grande de dados e, em seguida, passar por isso sequencialmente, e repita até que o arquivo foi processado?

Outras dicas

Eu corri para um problema de desempenho semelhante com BinaryReader / FileStream, e depois de profiling, descobri que o problema não é com o buffer FileStream, mas em vez disso, com esta linha:

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

Especificamente, a propriedade br.BaseStream.Length em um FileStream faz uma (relativamente) chamada de sistema lento para obter o tamanho do arquivo em cada loop. Depois de mudar o código para isso:

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

e usando um tamanho de tampão apropriado para o FileStream, I conseguido um desempenho semelhante ao exemplo MemoryStream.

Interessante, lendo o arquivo inteiro em um buffer e passar por isso na memória fez uma enorme diferença. Isso é com o custo de memória, mas nós temos a abundância.

Isto faz-me pensar que o FileStream (ou BufferedStream de para que o assunto) tampão implementação é falho, porque não importa o tamanho do buffer eu tentei, o desempenho ainda sugado.

  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;
          }
      }
  }

Para baixo para 2-5 segundos (depende do cache de disco eu estou supondo) agora de 22. O que é bom o suficiente para agora.

Uma ressalva; você pode querer verificar novamente o seu da CPU endianness ... assumindo little-endian não é muito segura (pense: itanium etc).

Você também pode querer ver se BufferedStream faz alguma diferença (eu não tenho certeza que ele vai).

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