2011-10-31 19 views
19

En algunas situaciones, la clase MemoryMappedViewAccessor simplemente no se corta para leer bytes de manera eficiente; lo mejor que obtenemos es el genérico ReadArray<byte> que es la ruta para todas las estructuras e implica varios pasos innecesarios cuando solo necesitas bytes.¿Cómo puedo leer rápidamente los bytes de un archivo mapeado en memoria en .NET?

Es posible utilizar un MemoryMappedViewStream, pero debido a que se basa en un Stream primero debe buscar la posición correcta, y luego la operación de lectura tiene muchos más pasos innecesarios.

¿Hay una forma rápida y de alto rendimiento de leer una matriz de bytes desde un archivo mapeado en memoria en .NET, dado que solo debe ser un área particular del espacio de direcciones para leer?

Respuesta

27

Esta solución requiere un código inseguro (compilar con el interruptor /unsafe), pero toma un puntero a la memoria directamente; luego se puede usar Marshal.Copy. Esto es mucho, mucho más rápido que los métodos proporcionados por .NET Framework.

// assumes part of a class where _view is a MemoryMappedViewAccessor object 

    public unsafe byte[] ReadBytes(int offset, int num) 
    { 
     byte[] arr = new byte[num]; 
     byte *ptr = (byte*)0; 
     this._view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); 
     Marshal.Copy(IntPtr.Add(new IntPtr(ptr), offset), arr, 0, num); 
     this._view.SafeMemoryMappedViewHandle.ReleasePointer(); 
     return arr; 
    } 

    public unsafe void WriteBytes(int offset, byte[] data) 
    { 
     byte* ptr = (byte*)0; 
     this._view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); 
     Marshal.Copy(data, 0, IntPtr.Add(new IntPtr(ptr), offset), data.Length); 
     this._view.SafeMemoryMappedViewHandle.ReleasePointer(); 
    } 
+3

Debería estar utilizando un Bloque de ejecución crítica y, finalmente, para asegurarse de que ReleasePointer se ejecuta incluso si Marshal.Copy arroja una excepción. –

+3

Buena respuesta =) De hecho, los perfiles muestran que el contenedor gestionado es 30 veces más lento que el uso de un puntero inseguro para acceder a la memoria asignada. –

+2

@MattHowells Estoy de acuerdo. Leí que CER podría afectar el rendimiento, pero parece insignificante (al menos en una prueba controlada). Independientemente de las implicaciones de rendimiento, es el patrón de uso correcto que se describe en "comentarios" aquí; https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safebuffer.acquirepointer(v=vs.110).aspx – LaFleur

2

Ver este informe de error: No way to determine internal offset used by MemoryMappedViewAccessor - Makes SafeMemoryMappedViewHandle property unusable.

Desde el informe:

MemoryMappedViewAccessor tiene una propiedad SafeMemoryMappedViewHandle, que devuelve el ViewHandle ser utilizado internamente por el MemoryMappedView, pero no tiene ninguna propiedad para devolver el desplazamiento utilizado por MemoryMappedView.

Como MemoryMappedView es la página que alinea la compensación solicitada en MemoryMappedFile.CreateViewAccessor (desplazamiento, tamaño), es imposible utilizar SafeMemoryMappedViewHandle para nada útil sin conocer el desplazamiento.

Tenga en cuenta que lo que realmente queremos hacer es utilizar el método AcquirePointer (ref byte * pointer) para permitir que se ejecute un código basado en un puntero rápido (posiblemente no gestionado). Estamos de acuerdo con que el puntero esté alineado con la página, pero debe ser posible averiguar cuál es el desplazamiento de la dirección solicitada originalmente.

+0

Parece tonto ... si tienes el control de la vista, no necesita .NET para indicarle el desplazamiento, ya que lo ha especificado. (Eso es lo que hago: '_view' es un acceso en offset 0) –

+0

Fwiw, este código también ha sido sometido a pruebas de estrés hasta la muerte [miles de millones de llamadas, miles de MMF diferentes] en varias máquinas –

+0

Ahora cientos de miles de millones de llamadas, y cientos de miles de FMM. Este error no ocurre con mi código;) –

1

Una versión segura de esta solución es:

var file = MemoryMappedFile.CreateFromFile(...); 
var accessor = file.CreateViewAccessor(); 
var bytes = new byte[yourLength]; 

// assuming the string is at the start of the file 
// aka position: 0 
// https://msdn.microsoft.com/en-us/library/dd267761(v=vs.110).aspx 
accessor.ReadArray<byte>(
    position: 0,  // The number of bytes in the accessor at which to begin reading 
    array: bytes,  // The array to contain the structures read from the accessor 
    offset: 0,  // The index in `array` in which to place the first copied structure 
    count: yourLength // The number of structures of type T to read from the accessor. 
); 

var myString = Encoding.UTF8.GetString(bytes); 

He probado esto, que hace el trabajo. No puedo comentar sobre su rendimiento o si es la MEJOR solución general solo que funciona.

+1

Cool, sí definitivamente evita el uso de punteros :) 'ReadArray ' es mucho, mucho más lento, sin embargo –

0

Sé que esta es una pregunta anterior que se ha respondido, pero quería agregar mis dos centavos.

Ejecuté una prueba con la respuesta aceptada (utilizando el código inseguro) y con el enfoque MemoryMappedViewStream para leer una matriz de 200 MB de bytes.

MemoryMappedViewStream

 const int MMF_MAX_SIZE = 209_715_200; 
     var buffer = new byte[ MMF_VIEW_SIZE ]; 

     using(var mmf = MemoryMappedFile.OpenExisting("mmf1")) 
     using(var view = mmf.CreateViewStream(0, buffer.Length, MemoryMappedFileAccess.ReadWrite)) 
     { 
      if(view.CanRead) 
      { 
       Console.WriteLine("Begin read"); 
       sw.Start(); 
       view.Read(buffer, 0, MMF_MAX_SIZE); 
       sw.Stop(); 
       Console.WriteLine($"Read done - {sw.ElapsedMilliseconds}ms"); 
      } 
     } 

que corrió la prueba 3 veces con cada enfoque y recibieron las siguientes veces.

MemoryMappedViewStream:

  1. 483ms
  2. 501ms
  3. 490ms

método inseguro

  1. 531ms
  2. 517ms
  3. 523ms

Desde la pequeña cantidad de pruebas parece que el MemoryMappedViewStream tiene una muy leve ventaja. Con eso en mente para cualquiera que lea esta publicación en el futuro, me gustaría ir con el MemoryMappedViewStream.

Cuestiones relacionadas