c# performance pointers unsafe binaryreader

c# - Más rápido(inseguro) BinaryReader en.NET



performance pointers (4)

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 mirarlo con .NET Reflector encontré 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 funcionar con valores de 32 bits desde que se inventó la CPU de 32 bits.

Así que hice mi propia clase (insegura) FastBinaryReader 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: logré reducir de 5 a 7 segundos el tiempo que tomó leer un archivo de 500 MB, pero aún así es bastante lento en general (29 segundos inicialmente y ~ 22 segundos ahora con mi FastBinaryReader ).

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

Incliné aún más las llamadas ReadInt32, etc., 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 idea más sobre cómo hacer esto aún más rápido? Estaba pensando que tal vez podría usar la ordenación para asignar el archivo completo directamente a la memoria sobre alguna estructura personalizada, ya que los datos son lineales, de tamaño fijo y secuencial.

SOLUCIONADO: Llegué a la conclusión de que el almacenamiento en búfer de FileStream / BufferedStream tiene fallas. Por favor vea la respuesta aceptada y mi propia respuesta (con la solución) a continuación.


Cuando hace una copia de archivo, se leen y se escriben en el disco grandes porciones de datos.

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

¿No es más sabio leer una gran cantidad de datos y luego analizarlos de forma secuencial y repetir hasta que el archivo haya sido procesado?


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 malo.

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 del caché de disco que supongo) ahora de 22. Lo que es suficiente por ahora.


Me encontré con un problema de rendimiento similar con BinaryReader / FileStream, y después de crear un perfil, descubrí que el problema no es con el almacenamiento en búfer de FileStream , 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 al usar un tamaño de búfer apropiado para FileStream , logré un rendimiento similar al del ejemplo de MemoryStream .


Una advertencia; es posible que desee revisar la endianness de su CPU ... suponiendo que little-endian no es del todo seguro (piense: itanium, etc.).

También es posible que desee ver si BufferedStream hace alguna diferencia (no estoy seguro de que lo haga).