Más rápido (inseguro) BinaryReader in.NET


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

En consecuencia, me di cuenta de que la implementación predeterminada de BinaryReader en.NET es bastante lenta. 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 se diseñaron las computadoras para trabajar con valores de 32 bits desde que se inventó la CPU de 32 bits.

Así que hice mi propio (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));
        }
    }
...
}

Que es mucho más rápido - Me las arreglé para reducir 5-7 segundos del tiempo que tomó para leer un archivo de 500 MB, pero todavía es bastante lento en general (29 segundos inicialmente y ~22 segundos ahora con mi FastBinaryReader).

Todavía me desconcierta en cuanto a por qué todavía toma tanto tiempo leer un archivo tan relativamente pequeño. Si copio el archivo de un disco a otro, solo tarda un par de segundos, por lo que el rendimiento del disco no es un problema.

Además entre líneas el 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;
   }

¿Alguna otra idea sobre cómo hacer esto aún más rápido? Estaba pensando que tal vez podría usar marshalling para mapear todo el archivo directamente en la memoria sobre una estructura personalizada, ya que los datos son lineales, de tamaño fijo y secuenciales.

RESUELTO: Llegué a la conclusión de que el buffering/BufferedStream de FileStream es defectuoso. Por favor vea la respuesta aceptada y mi propia respuesta (con el solución) a continuación.

Author: Peter Mortensen, 2009-08-06

4 answers

Cuando realiza una copia de archivos, se leen y escriben grandes trozos de datos en el disco.

Está leyendo el archivo entero cuatro bytes a la vez. Esto va a ser más lento. Incluso si la implementación de stream es lo suficientemente inteligente como para almacenar en búfer, todavía tiene al menos 500 MB/4 = 131072000 llamadas API.

¿No es más prudente leer una gran cantidad de datos, y luego ir a través de ella secuencialmente, y repetir hasta que el archivo ha sido procesado?

 9
Author: Toad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-27 06:41:10

Me encontré con un problema de rendimiento similar con BinaryReader / FileStream,y después de perfilar, descubrí que el problema no es con FileStream buffering, sino con esta línea:

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

Específicamente, la propiedad br.BaseStream.Length en un FileStream hace una llamada al sistema (relativamente) lenta para obtener el tamaño del archivo en cada bucle. Después de cambiar el código a esto:

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

Y usando un tamaño de búfer apropiado para el FileStream, logré un rendimiento similar al del ejemplo MemoryStream.

 18
Author: danp60,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-26 23:39:19

Interesante, leer todo el archivo en un búfer y revisarlo en memoria hizo una gran diferencia. Esto es a costa de la memoria, pero tenemos mucho.

Esto me hace pensar que la implementación del búfer de FileStream (o BufferedStream para el caso) es defectuosa, porque no importa el tamaño del búfer que probé, el rendimiento sigue siendo una mierda.

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

Hasta 2-5 segundos (depende de la caché de disco, supongo) ahora de 22. Lo cual es suficiente por ahora.

 9
Author: andreialecu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-06 12:21:53

Una advertencia; usted puede ser que desee comprobar su CPU endianness... asumiendo que little-endian no es bastante seguro (piense: itanium, etc.).

Es posible que también desee ver si BufferedStream hace alguna diferencia (no estoy seguro de que lo hará).

 5
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-06 11:50:37