Pregunta

Me encontré con una situación en la que tengo un archivo bastante grande que tengo que leer datos binarios de.

En consecuencia, me di cuenta de que la aplicación BinaryReader defecto en .NET es bastante lento. Al mirar con .NET Reflector me encontré con esto:

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

Lo que me parece extremadamente ineficiente, pensando en cómo las computadoras fueron diseñadas para trabajar con valores de 32 bits ya que la CPU de 32 bits fue inventado.

Así que hice mi propia (inseguro) FastBinaryReader clase con código como este en su lugar:

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

Lo cual es mucho más rápido -. Me las arreglé para cortar 5-7 segundos el tiempo que tomó para leer un archivo de 500 MB, pero sigue siendo bastante lento en general (29 segundos al principio y ~ 22 segundos ahora con mi FastBinaryReader)

Todavía tipo de me desconcierta por qué todavía necesita tanto tiempo para leer un archivo de este tipo relativamente pequeño. Si copio el archivo de un disco a otro sólo se necesita un par de segundos, por lo que el rendimiento del disco no es un problema.

Me inlined aún más la ReadInt32, etc. llamadas, y terminé con 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;
   }

Todas las ideas adicionales sobre cómo hacer esto aún más rápido? Estaba pensando que tal vez podría utilizar de clasificación para mapear todo el archivo directamente en la memoria en la parte superior de una estructura personalizada, ya que los datos es lineal, tamaño fijo y secuencial.

RESUELTO: llegué a la conclusión de que el búfer de FileStream / BufferedStream son erróneas. Por favor, vea la respuesta aceptada y mi propia respuesta (con la solución) a continuación.

¿Fue útil?

Solución

Cuando usted hace una FileCopy, grandes cantidades de datos se leen y se escriben en el disco.

Estás leyendo todo el archivo de cuatro bytes a la vez. Esto se ve obligada a ser más lento. Incluso si la aplicación corriente es lo suficientemente inteligente como para amortiguar, todavía tiene al menos 500 MB / 4 = 131 072 000 llamadas a la API.

¿No es más prudente acaba de leer una gran parte de los datos, y luego ir a través de él de forma secuencial, y repetir hasta que el archivo ha sido procesado?

Otros consejos

Me encontré con un problema de rendimiento similar con BinaryReader / FileStream, y después de perfilado, descubrí que el problema no es con el almacenamiento en búfer FileStream, pero en su lugar con esta línea:

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

En concreto, el br.BaseStream.Length propiedad en un FileStream hace un (relativamente) llamada al sistema lento para conseguir el tamaño del archivo en cada bucle. Después de cambiar el código para esto:

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

y el uso de un tamaño de búfer apropiado para el FileStream, he conseguido un rendimiento similar al ejemplo MemoryStream.

Interesante, leer todo el archivo en un búfer y pasar por ella en la memoria hizo una gran diferencia. Esto es a costa de la memoria, pero tenemos un montón.

Esto me hace pensar que el FileStream (o BufferedStream de para el caso) aplicación búfer es defectuosa, porque no importa qué tamaño de búfer lo intentara, el rendimiento sigue siendo succionado.

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

Abajo a 2-5 segundos (depende de la caché de disco supongo) ahora de 22. ¿Cuál es suficiente por ahora.

Una advertencia; es posible que desee volver a verificar su ... suponiendo ascendente hacia la izquierda no es muy seguro (piensa: Itanium etc).

También puede ser que desee para ver si BufferedStream hace ninguna diferencia (no estoy seguro de que lo hará).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top