mmap () vs. bloques de lectura


185

Estoy trabajando en un programa que procesará archivos que podrían tener un tamaño de 100 GB o más. Los archivos contienen conjuntos de registros de longitud variable. Tengo una primera implementación en funcionamiento y ahora estoy buscando mejorar el rendimiento, particularmente para hacer E / S de manera más eficiente ya que el archivo de entrada se escanea muchas veces.

¿Existe una regla general para usar mmap()versus leer en bloques a través de la fstreambiblioteca de C ++ ? Lo que me gustaría hacer es leer bloques grandes del disco en un búfer, procesar registros completos del búfer y luego leer más.

El mmap()código podría volverse muy desordenado ya que mmaplos bloques 'd deben ubicarse en los límites del tamaño de la página (según tengo entendido) y los registros podrían potencialmente atravesar los límites de la página. Con fstreams, solo puedo buscar el comienzo de un registro y comenzar a leer nuevamente, ya que no estamos limitados a bloques de lectura que se encuentran en los límites del tamaño de página.

¿Cómo puedo decidir entre estas dos opciones sin escribir primero una implementación completa? ¿Alguna regla general (por ejemplo, mmap()es 2 veces más rápido) o pruebas simples?


1
Esta es una lectura interesante: medium.com/@sasha_f/… En los experimentos mmap()es 2-6 veces más rápido que usar syscalls, por ejemplo read().
mplattner

Respuestas:


208

Estaba tratando de encontrar la última palabra sobre el rendimiento de mmap / read en Linux y me encontré con una buena publicación ( enlace ) en la lista de correo del kernel de Linux. Es de 2000, por lo que ha habido muchas mejoras en IO y memoria virtual en el núcleo desde entonces, pero explica muy bien el motivo mmapo readpodría ser más rápido o más lento.

  • Una llamada a mmaptiene más sobrecarga que read(al igual que epolltiene más sobrecarga que poll, que tiene más sobrecarga que read). Cambiar las asignaciones de memoria virtual es una operación bastante costosa en algunos procesadores por las mismas razones que cambiar entre diferentes procesos es costoso.
  • El sistema IO ya puede usar el caché de disco, por lo que si lee un archivo, lo golpeará o lo perderá sin importar el método que use.

Sin embargo,

  • Los mapas de memoria son generalmente más rápidos para el acceso aleatorio, especialmente si sus patrones de acceso son escasos e impredecibles.
  • Los mapas de memoria le permiten seguir usando páginas de la memoria caché hasta que haya terminado. Esto significa que si usa un archivo en gran medida durante un período prolongado, luego ciérrelo y vuelva a abrirlo, las páginas seguirán almacenadas en caché. Con read, su archivo puede haber sido vaciado del caché hace años. Esto no se aplica si usa un archivo y lo descarta de inmediato. (Si intenta mlockpáginas solo para mantenerlas en la memoria caché, intenta burlar la memoria caché del disco y este tipo de tonterías rara vez ayuda al rendimiento del sistema).
  • Leer un archivo directamente es muy simple y rápido.

La discusión de mmap / read me recuerda a otras dos discusiones de rendimiento:

  • Algunos programadores de Java se sorprendieron al descubrir que la E / S sin bloqueo es a menudo más lenta que el bloqueo de E / S, lo que tiene mucho sentido si sabe que la E / S sin bloqueo requiere hacer más syscalls.

  • Algunos otros programadores de redes se sorprendieron al saber que a epollmenudo es más lento que poll, lo que tiene mucho sentido si sabes que administrar epollrequiere hacer más llamadas al sistema.

Conclusión: use mapas de memoria si accede a datos aleatoriamente, guárdelos durante mucho tiempo o si sabe que puede compartirlos con otros procesos ( MAP_SHAREDno es muy interesante si no existe un intercambio real). Lea los archivos normalmente si accede a los datos secuencialmente o los descarta después de leerlos. Y si cualquiera de estos métodos hace que su programa sea menos compleja, lo que . Para muchos casos del mundo real, no hay forma segura de mostrar que uno sea más rápido sin probar su aplicación real y NO un punto de referencia.

(Perdón por negar esta pregunta, pero estaba buscando una respuesta y esta pregunta seguía apareciendo en la parte superior de los resultados de Google).


Tenga en cuenta que usar cualquier consejo basado en hardware y software de la década de 2000, sin probarlo hoy, sería un enfoque muy sospechoso. Además, si bien muchos de los hechos sobre mmapvs read()en ese hilo siguen siendo ciertos como lo fueron en el pasado, el rendimiento general no puede determinarse realmente sumando los pros y los contras, sino solo probando una configuración de hardware en particular. Por ejemplo, es discutible que "Una llamada a mmap tiene más sobrecarga que lectura": sí, mmaptiene que agregar asignaciones a la tabla de páginas de proceso, pero readtiene que copiar todos los bytes leídos del núcleo al espacio de usuario.
BeeOnRope

El resultado es que, en mi hardware (Intel moderno, alrededor de 2018), mmaptiene una sobrecarga más baja que readpara lecturas de más de un tamaño de página (4 KiB). Ahora es muy cierto que si desea acceder a los datos de manera dispersa y aleatoria, mmapes realmente muy bueno, pero lo contrario no es necesariamente cierto: también mmappuede ser el mejor acceso secuencial.
BeeOnRope

1
@BeeOnRope: Puede ser escéptico de los consejos basados ​​en hardware y software de la década de 2000, pero yo soy aún más escéptico de los puntos de referencia que no proporcionan una metodología y datos. Si desea presentar un caso que mmapsea ​​más rápido, esperaría ver, como mínimo, todo el aparato de prueba (código fuente) con los resultados tabulados y el número de modelo del procesador.
Dietrich Epp

@BeeOnRope: también tenga en cuenta que cuando prueba bits del sistema de memoria como este, las microbenchmarks pueden ser extremadamente engañosas porque una descarga TLB puede afectar negativamente el rendimiento del resto de su programa, y ​​este impacto no se mostrará si solo mides el mmap mismo.
Dietrich Epp

2
@DietrichEpp: sí, conoceré bien los efectos TLB. Tenga en cuenta que mmapno vacía el TLB excepto en circunstancias inusuales (pero munmappodría). Mis pruebas incluyeron microbenchmarks (incluidos munmap) y también "en la aplicación" que se ejecuta en un caso de uso del mundo real. Por supuesto, mi solicitud no es la misma que su solicitud, por lo que las personas deben realizar pruebas locales. Ni siquiera está claro que mmapsea ​​favorecido por un micro-punto de referencia: read()también recibe un gran impulso ya que el búfer de destino del lado del usuario generalmente permanece en L1, lo que puede no suceder en una aplicación más grande. Entonces sí, "es complicado".
BeeOnRope

47

El principal costo de rendimiento será la E / S de disco. "mmap ()" es ciertamente más rápido que istream, pero la diferencia puede no ser notable porque la E / S del disco dominará sus tiempos de ejecución.

Intenté el fragmento de código de Ben Collins (ver arriba / abajo) para probar su afirmación de que "mmap () es mucho más rápido" y no encontré ninguna diferencia medible. Ver mis comentarios sobre su respuesta.

Ciertamente no recomendaría por separado mmap'ing cada registro a su vez a menos que sus "registros" sean enormes, eso sería terriblemente lento, que requeriría 2 llamadas al sistema para cada registro y posiblemente perder la página del caché de la memoria del disco ... .

En su caso, creo que mmap (), istream y las llamadas de bajo nivel open () / read () serán todas iguales. Recomendaría mmap () en estos casos:

  1. Hay acceso aleatorio (no secuencial) dentro del archivo, Y
  2. todo encaja cómodamente en la memoria O hay una localidad de referencia dentro del archivo para que ciertas páginas puedan mapearse y otras páginas mapeadas. De esa forma, el sistema operativo utiliza la RAM disponible para obtener el máximo beneficio.
  3. O bien, si varios procesos están leyendo / trabajando en el mismo archivo, entonces mmap () es fantástico porque todos los procesos comparten las mismas páginas físicas.

(por cierto, me encanta mmap () / MapViewOfFile ()).


Buen punto sobre el acceso aleatorio: esta podría ser una de las cosas que impulsa mi percepción.
Ben Collins

1
No diría que el archivo tiene que caber cómodamente en la memoria, solo en el espacio de direcciones. Por lo tanto, en los sistemas de 64 bits, no debería haber ninguna razón para no asignar archivos de gran tamaño. El sistema operativo sabe cómo manejar eso; es la misma lógica utilizada para el intercambio, pero en este caso no requiere espacio de intercambio adicional en el disco.
MvG

@ MVG: ¿Entiendes el punto sobre la E / S de disco? Si el archivo se ajusta al espacio de direcciones pero no a la memoria y tiene acceso aleatorio, entonces podría tener acceso a todos los registros que requieran mover y buscar la cabeza del disco, o una operación de página SSD, lo que sería un desastre para el rendimiento.
Tim Cooper

3
El aspecto de E / S del disco debe ser independiente del método de acceso. Si tiene acceso verdaderamente aleatorio a archivos más grandes que RAM, tanto mmap como seek + read están severamente vinculados al disco. De lo contrario, ambos se beneficiarán de los cachés. No veo el tamaño del archivo en comparación con el tamaño de la memoria como un argumento fuerte en ninguna dirección. El tamaño del archivo frente al espacio de direcciones, por otro lado, es un argumento muy fuerte, particularmente para el acceso verdaderamente aleatorio.
MvG

Mi respuesta original tenía y tiene este punto: "todo encaja cómodamente en la memoria O hay una localidad de referencia dentro del archivo". Entonces, el segundo punto aborda lo que estás diciendo.
Tim Cooper

43

mmap es mucho más rápido. Puede escribir un punto de referencia simple para probarlo usted mismo:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
  in.read(data, 0x1000);
  // do something with data
}

versus:

const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;

int fd = open("filename.bin", O_RDONLY);

while (off < file_size)
{
  data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
  // do stuff with data
  munmap(data, page_size);
  off += page_size;
}

Claramente, estoy omitiendo detalles (como cómo determinar cuándo llegas al final del archivo en caso de que tu archivo no sea un múltiplo de page_size, por ejemplo), pero realmente no debería ser mucho más complicado que esto .

Si puede, intente dividir sus datos en varios archivos que pueden ser mmap () editados en su totalidad en lugar de en parte (mucho más simple).

Hace un par de meses tuve una implementación a medias de una clase de flujo de mmap () ed de ventana deslizante para boost_iostreams, pero a nadie le importó y me ocupé de otras cosas. Lamentablemente, eliminé un archivo de proyectos antiguos sin terminar hace unas semanas, y esa fue una de las víctimas :-(

Actualización : también debería agregar la advertencia de que este punto de referencia se vería bastante diferente en Windows porque Microsoft implementó un ingenioso caché de archivos que hace la mayor parte de lo que haría con mmap en primer lugar. Es decir, para los archivos a los que se accede con frecuencia, podría hacer std :: ifstream.read () y sería tan rápido como mmap, porque el caché de archivos ya habría hecho un mapeo de memoria para usted, y es transparente.

Actualización final : Mire, gente: a través de muchas combinaciones de plataformas diferentes de SO y bibliotecas estándar y discos y jerarquías de memoria, no puedo decir con certeza que la llamada al sistema mmap, vista como una caja negra, siempre siempre será sustancialmente más rápida que read. Esa no era exactamente mi intención, incluso si mis palabras pudieran interpretarse de esa manera. Finalmente, mi punto era que la E / S mapeada en memoria es generalmente más rápida que la E / S basada en bytes; Esto sigue siendo cierto . Si encuentra experimentalmente que no hay diferencia entre los dos, entonces la única explicación que me parece razonable es que su plataforma implementa el mapeo de memoria bajo las cubiertas de una manera que es ventajosa para el rendimiento de las llamadas aread. La única forma de estar absolutamente seguro de que está utilizando E / S mapeadas en memoria de forma portátil es mediante el uso mmap. Si no le importa la portabilidad y puede confiar en las características particulares de sus plataformas de destino, el uso readpuede ser adecuado sin sacrificar de manera considerable el rendimiento.

Editar para limpiar la lista de respuestas: @jbl:

la ventana deslizante mmap suena interesante. ¿Puedes decir un poco más al respecto?

Claro, estaba escribiendo una biblioteca C ++ para Git (un libgit ++, si lo desea), y me encontré con un problema similar a este: necesitaba poder abrir archivos grandes (muy grandes) y no tener un rendimiento total. (como sería con std::fstream).

Boost::Iostreamsya tiene una fuente mapped_file, pero el problema era que estaba haciendo mmapping a archivos completos, lo que lo limita a 2 ^ (tamaño de palabras). En máquinas de 32 bits, 4 GB no es lo suficientemente grande. No es irracional esperar tener .packarchivos en Git que sean mucho más grandes que eso, por lo que necesitaba leer el archivo en trozos sin recurrir a la E / S de archivo normal. Debajo de las cubiertas de Boost::Iostreams, implementé una Fuente, que es más o menos otra vista de la interacción entre std::streambufy std::istream. También puede probar un enfoque similar simplemente heredando std::filebufen mapped_filebufay, de manera similar, heredando std::fstreamen a mapped_fstream. Es la interacción entre los dos lo que es difícil de acertar. Boost::Iostreams tiene parte del trabajo realizado para usted, y también proporciona ganchos para filtros y cadenas, por lo que pensé que sería más útil implementarlo de esa manera.


3
RE: caché de archivos mmaped en Windows. Exactamente: cuando el almacenamiento en búfer de archivos está habilitado, la memoria del núcleo asigna el archivo que está leyendo internamente, lo lee en ese búfer y lo copia nuevamente en su proceso. Es como si la memoria lo hubiera mapeado usted mismo, excepto con un paso de copia adicional.
Chris Smith

66
No me gusta estar en desacuerdo con una respuesta aceptada, pero creo que esta respuesta es incorrecta. Seguí su sugerencia y probé su código, en una máquina Linux de 64 bits, y mmap () no fue más rápido que la implementación de STL. Además, en teoría no esperaría que 'mmap ()' fuera más rápido (o más lento).
Tim Cooper

3
@Tim Cooper: puede encontrar este hilo ( markmail.org/message/… ) de interés. Tenga en cuenta las dos cosas: mmap no está correctamente optimizado en Linux, y uno también necesita usar madvise en su prueba para obtener los mejores resultados.
Ben Collins

9
Estimado Ben: He leído ese enlace. Si 'mmap ()' no es más rápido en Linux, y MapViewOfFile () no es más rápido en Windows, ¿puede afirmar que "mmap es mucho más rápido"? Además, por razones teóricas, creo que mmap () no es más rápido para lecturas secuenciales: ¿tiene alguna explicación en sentido contrario?
Tim Cooper

11
Ben, ¿por qué molestarse en mmap()archivar una página a la vez? Si a size_ttiene la capacidad suficiente para contener el tamaño del archivo (muy probablemente en sistemas de 64 bits), entonces solommap() el archivo completo en una llamada.
Steve Emmerson

39

Aquí hay muchas buenas respuestas que cubren muchos de los puntos más destacados, así que solo agregaré un par de problemas que no vi abordados directamente arriba. Es decir, esta respuesta no debe considerarse como una integral de los pros y los contras, sino más bien como una adición a otras respuestas aquí.

mmap parece magia

Tomar el caso en el que el archivo ya está completamente en caché 1 como la línea de base 2 , mmappodría parecerse a la magia :

  1. mmap solo requiere 1 llamada al sistema para (potencialmente) mapear todo el archivo, después de lo cual no se necesitan más llamadas al sistema.
  2. mmap no requiere una copia de los datos del archivo del kernel al espacio de usuario.
  3. mmaple permite acceder al archivo "como memoria", incluido el procesamiento con cualquier truco avanzado que pueda hacer contra la memoria, como la vectorización automática del compilador, la intrínseca SIMD , la captación previa, las rutinas optimizadas de análisis en memoria, OpenMP, etc.

En el caso de que el archivo ya esté en el caché, parece imposible de superar: simplemente accede directamente al caché de la página del núcleo como memoria y no puede ser más rápido que eso.

Bueno, si puede.

mmap no es realmente mágico porque ...

mmap todavía funciona por página

Un costo oculto primario de mmapvs read(2)(que es realmente el syscall comparable a nivel de sistema operativo para leer bloques ) es que mmaptendrá que hacer "algo de trabajo" para cada página 4K en el espacio de usuario, aunque pueda estar oculto por el mecanismo de falla de página.

Por ejemplo, una implementación típica que solo mmapes el archivo completo necesitará una falla de manera que 100 GB / 4K = 25 millones de fallas para leer un archivo de 100 GB. Ahora, estos serán fallas menores , pero las fallas de 25 mil millones de páginas todavía no serán súper rápidas. El costo de una falla menor probablemente esté en los cientos de nanos en el mejor de los casos.

mmap depende en gran medida del rendimiento de TLB

Ahora, puede pasar MAP_POPULATEa mmapdecirle que configure todas las tablas de páginas antes de regresar, por lo que no debe haber fallas de página al acceder. Ahora, esto tiene el pequeño problema de que también lee todo el archivo en la RAM, que explotará si intenta asignar un archivo de 100GB, pero ignoremos eso por ahora 3 . El kernel necesita hacer un trabajo por página para configurar estas tablas de páginas (aparece como tiempo de kernel). Esto termina siendo un costo importante en el mmapenfoque, y es proporcional al tamaño del archivo (es decir, no se vuelve relativamente menos importante a medida que crece el tamaño del archivo) 4 .

Finalmente, incluso en el acceso al espacio de usuario, dicha asignación no es exactamente gratuita (en comparación con grandes memorias intermedias que no se originan a partir de un archivo mmap), incluso una vez que se configuran las tablas de páginas, cada acceso a una nueva página va a, conceptualmente, incurrir en una falta de TLB. Ya quemmap crear un archivo significa usar el caché de la página y sus páginas 4K, nuevamente incurrirá en este costo 25 millones de veces por un archivo de 100GB.

Ahora, el costo real de estas fallas de TLB depende en gran medida de al menos los siguientes aspectos de su hardware: (a) cuántas entradas de TLB de 4K tiene y cómo funciona el resto del almacenamiento en caché de traducción (b) qué tan bien se ocupa la captación previa de hardware con el TLB, por ejemplo, ¿puede la captación previa desencadenar una caminata de página? (c) qué tan rápido y qué tan paralelo es el hardware que recorre la página. En los modernos procesadores Intel x86 de gama alta, el hardware de paso de página es en general muy fuerte: hay al menos 2 caminadores de página paralelos, un paso de página puede ocurrir simultáneamente con la ejecución continua, y la captación previa de hardware puede desencadenar un paso de página. Entonces, el impacto de TLB en una transmisión carga de lectura de es bastante bajo, y dicha carga a menudo tendrá un rendimiento similar independientemente del tamaño de la página. Sin embargo, otro hardware suele ser mucho peor.

read () evita estas trampas

La read()llamada al sistema, que es lo que generalmente subyace a las llamadas de tipo "lectura en bloque" que se ofrecen, por ejemplo, en C, C ++ y otros lenguajes, tiene una desventaja principal que todos conocen:

  • Cada read()llamada de N bytes debe copiar N bytes del núcleo al espacio del usuario.

Por otro lado, evita la mayoría de los costos anteriores: no es necesario asignar 25 millones de páginas 4K en el espacio del usuario. Por lo general, puede mallocusar un solo búfer pequeño en el espacio de usuario y reutilizarlo repetidamente para todas sus readllamadas. En el lado del kernel, casi no hay problema con las páginas 4K o las fallas de TLB porque toda la RAM generalmente se mapea linealmente usando algunas páginas muy grandes (por ejemplo, páginas de 1 GB en x86), por lo que las páginas subyacentes en el caché de páginas están cubiertas de manera muy eficiente en el espacio del kernel.

Básicamente, tiene la siguiente comparación para determinar cuál es más rápido para una sola lectura de un archivo grande:

¿Es el trabajo adicional por página implicado por el mmapenfoque más costoso que el trabajo por byte de copiar el contenido del archivo desde el núcleo al espacio de usuario implícito mediante el uso read()?

En muchos sistemas, en realidad están aproximadamente equilibrados. Tenga en cuenta que cada uno escala con atributos completamente diferentes del hardware y la pila del sistema operativo.

En particular, el mmapenfoque se vuelve relativamente más rápido cuando:

  • El sistema operativo tiene un manejo rápido de fallas menores y, especialmente, optimizaciones de aumento de fallas menores, como la resolución de fallas.
  • El sistema operativo tiene un buen MAP_POPULATE implementación que puede procesar eficientemente mapas grandes en casos donde, por ejemplo, las páginas subyacentes son contiguas en la memoria física.
  • El hardware tiene un sólido rendimiento de traducción de páginas, como TLB grandes, TLB rápidos de segundo nivel, caminadores de páginas rápidos y paralelos, buena interacción de captación previa con la traducción, etc.

... mientras que el read()enfoque se vuelve relativamente más rápido cuando:

  • La read()llamada al sistema tiene un buen rendimiento de copia. Por ejemplo, buen copy_to_userrendimiento en el lado del núcleo.
  • El núcleo tiene una forma eficiente (en relación con el país de usuario) de asignar memoria, por ejemplo, utilizando solo unas pocas páginas grandes con soporte de hardware.
  • El kernel tiene syscalls rápidas y una forma de mantener las entradas TLB del kernel en todas las syscalls.

Los factores de hardware anteriores varían enormemente entre diferentes plataformas, incluso dentro de la misma familia (por ejemplo, dentro de x86 generaciones y especialmente segmentos de mercado) y definitivamente entre arquitecturas (por ejemplo, ARM vs x86 vs PPC).

Los factores del sistema operativo siguen cambiando también, con varias mejoras en ambos lados que causan un gran salto en la velocidad relativa para un enfoque u otro. Una lista reciente incluye:

  • Adición de falla, descrita anteriormente, que realmente ayuda al mmapcaso sin MAP_POPULATE.
  • Adición de copy_to_usermétodos de vía rápida en arch/x86/lib/copy_user_64.S, por ejemplo, el uso REP MOVQcuando es rápido, que realmente ayudan al read()caso.

Actualización después de Specter and Meltdown

Las mitigaciones para las vulnerabilidades Spectre y Meltdown aumentaron considerablemente el costo de una llamada al sistema. En los sistemas que he medido, el costo de una llamada al sistema "no hacer nada" (que es una estimación de la sobrecarga pura de la llamada del sistema, aparte de cualquier trabajo real realizado por la llamada) pasó de aproximadamente 100 ns en un típico Sistema Linux moderno a unos 700 ns. Además, dependiendo de su sistema, la corrección de aislamiento de la tabla de páginas específicamente para Meltdown puede tener efectos posteriores adicionales además del costo directo de la llamada del sistema debido a la necesidad de volver a cargar las entradas TLB.

Todo esto es una desventaja relativa para los read()métodos basados ​​en comparación con los mmapmétodos basados, ya que los read()métodos deben hacer una llamada al sistema para cada valor de "tamaño de búfer". No puede aumentar arbitrariamente el tamaño del búfer para amortizar este costo, ya que el uso de grandes búferes generalmente funciona peor ya que excede el tamaño L1 y, por lo tanto, sufre constantemente errores de caché.

Por otro lado, con mmap, puede asignar en una gran región de memoria MAP_POPULATEy acceder de manera eficiente, a costa de una sola llamada al sistema.


1 Esto más o menos también incluye el caso en el que el archivo no estaba completamente en caché para empezar, pero donde la lectura del sistema operativo es lo suficientemente buena como para que parezca así (es decir, la página generalmente está en caché para cuando lo quiero). Este es un tema sutil, porque aunque el camino prelectura obras es a menudo bastante diferente entre mmapy readllamadas, y se puede ajustar aún más por las llamadas "asesorar" como se describe en 2 .

2 ... porque si el archivo no está en caché, su comportamiento estará completamente dominado por preocupaciones de E / S, incluyendo cuán comprensivo es su patrón de acceso al hardware subyacente, y todo su esfuerzo debe ser para garantizar que dicho acceso sea tan comprensivo como posible, por ejemplo, mediante el uso de madviseo fadvisellamadas (y cualquier cambio de nivel de aplicación que pueda hacer para mejorar los patrones de acceso).

3 Podría evitar eso, por ejemplo, introduciendo secuencialmente mmapen ventanas de un tamaño más pequeño, digamos 100 MB.

4 De hecho, resulta que el MAP_POPULATEenfoque es (al menos una combinación de hardware / sistema operativo) solo un poco más rápido que no usarlo, probablemente porque el kernel está usando faultround , por lo que el número real de fallas menores se reduce en un factor de 16 más o menos.


44
Gracias por proporcionar una respuesta más matizada a este complejo problema. Parece obvio para la mayoría de las personas que mmap es más rápido, cuando en realidad a menudo no es el caso. En mis experimentos, acceder aleatoriamente a una gran base de datos de 100 GB con un índice en memoria resultó ser más rápido con pread (), a pesar de que estaba asignando un búfer para cada uno de los millones de accesos. Y parece que muchas personas en la industria han observado lo mismo .
Caetano Sauer

55
Sí, depende mucho del escenario. Si las lecturas son lo suficientemente pequeñas y con el tiempo tiende a leer repetidamente los mismos bytes, mmaptendrá una ventaja insuperable ya que evita la sobrecarga fija de la llamada del núcleo. Por otro lado, mmaptambién aumenta la presión TLB, y en realidad hace que sea más lento para la fase de "calentamiento" donde los bytes se leen por primera vez en el proceso actual (aunque todavía están en la página), ya que puede hacerlo más trabajo que read, por ejemplo, para "solucionar" las páginas adyacentes ... ¡y para las mismas aplicaciones "calentar" es todo lo que importa! @CaetanoSauer
BeeOnRope

Creo que donde dices "... pero las fallas de 25 mil millones de páginas todavía no serán súper rápidas ..." debería leer "... pero las fallas de 25 millones de páginas todavía no serán súper rápidas ..." . No soy 100% positivo, por eso no estoy editando directamente.
Ton van den Heuvel

7

Lo siento, Ben Collins perdió su código fuente de ventanas deslizantes mmap. Sería bueno tenerlo en Boost.

Sí, mapear el archivo es mucho más rápido. Básicamente, está utilizando el subsistema de memoria virtual del sistema operativo para asociar la memoria al disco y viceversa. Piénselo de esta manera: si los desarrolladores del kernel del sistema operativo pudieran hacerlo más rápido, lo harían. Porque hacerlo hace que todo sea más rápido: bases de datos, tiempos de arranque, tiempos de carga de programas, etc.

El enfoque de ventana deslizante realmente no es tan difícil, ya que se pueden asignar varias páginas distinguidas a la vez. Por lo tanto, el tamaño del registro no importa siempre y cuando el más grande de todos los registros individuales quepa en la memoria. Lo importante es gestionar la contabilidad.

Si un registro no comienza en un límite getpagesize (), su mapeo debe comenzar en la página anterior. La longitud de la región asignada se extiende desde el primer byte del registro (redondeado si es necesario al múltiplo más cercano de getpagesize ()) hasta el último byte del registro (redondeado al múltiplo más cercano de getpagesize ()). Cuando termine de procesar un registro, puede desasignarlo () y pasar al siguiente.

Todo esto funciona bien en Windows también usando CreateFileMapping () y MapViewOfFile () (y GetSystemInfo () para obtener SYSTEM_INFO.dwAllocationGranularity --- no SYSTEM_INFO.dwPageSize).


Simplemente busqué en Google y encontré este pequeño fragmento sobre dwAllocationGranularity: estaba usando dwPageSize y todo se estaba rompiendo. ¡Gracias!
wickedchicken

4

mmap debería ser más rápido, pero no sé cuánto. Depende mucho de tu código. Si usa mmap, es mejor mapear todo el archivo a la vez, eso le hará la vida mucho más fácil. Un problema potencial es que si su archivo es más grande que 4GB (o en la práctica el límite es más bajo, a menudo 2GB) necesitará una arquitectura de 64 bits. Entonces, si está usando un entorno 32, probablemente no quiera usarlo.

Dicho esto, puede haber una mejor ruta para mejorar el rendimiento. Dijiste que el archivo de entrada se escanea muchas veces , si puedes leerlo de una vez y luego hacerlo, eso podría ser mucho más rápido.


3

Tal vez debería preprocesar los archivos, de modo que cada registro esté en un archivo separado (o al menos que cada archivo tenga un tamaño compatible con mmap).

¿También podría hacer todos los pasos de procesamiento para cada registro, antes de pasar al siguiente? ¿Tal vez eso evitaría algunos de los gastos generales de IO?


3

Estoy de acuerdo que mmap'd archivo de E / S va a ser más rápido, pero al mismo tiempo su evaluación comparativa del código, ¿no debería el contraejemplo ser un poco optimizado?

Ben Collins escribió:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
    in.read(data, 0x1000);
    // do something with data 
}

Sugeriría también intentar:

char data[0x1000];
std::ifstream iifle( "file.bin");
std::istream  in( ifile.rdbuf() );

while( in )
{
    in.read( data, 0x1000);
    // do something with data
}

Y más allá de eso, también puede intentar hacer que el tamaño del búfer sea del mismo tamaño que una página de memoria virtual, en caso de que 0x1000 no sea el tamaño de una página de memoria virtual en su máquina ... gana, pero esto debería acercar las cosas.


2

En mi opinión, usar mmap () "solo" libera al desarrollador de tener que escribir su propio código de almacenamiento en caché. En un caso simple de "leer el archivo de manera efectiva una vez", esto no va a ser difícil (aunque, como mlbrock señala, todavía guarda la copia de la memoria en el espacio de proceso), pero si va de un lado a otro en el archivo o omitiendo bits y demás, creo que los desarrolladores del kernel probablemente han hecho un mejor trabajo implementando el almacenamiento en caché que yo ...


1
Lo más probable es que pueda hacer un mejor trabajo de almacenamiento en caché de sus datos específicos de la aplicación que el núcleo, que opera en trozos de tamaño de página de una manera muy ciega (por ejemplo, solo usa un esquema simple de pseudo-LRU para decidir qué páginas desalojar ) - si bien puede saber mucho sobre la granularidad de almacenamiento en caché correcta y también tener una buena idea de los patrones de acceso futuros. El beneficio real del mmapalmacenamiento en caché es que simplemente reutiliza el caché de página existente que ya estará allí, de modo que obtiene esa memoria de forma gratuita y también se puede compartir entre los procesos.
BeeOnRope

2

Recuerdo mapear un archivo enorme que contiene una estructura de árbol en la memoria hace años. Me sorprendió la velocidad en comparación con la deserialización normal que implica mucho trabajo en la memoria, como la asignación de nodos de árbol y la configuración de punteros. De hecho, estaba comparando una sola llamada a mmap (o su contraparte en Windows) con muchas (MUCHAS) llamadas a llamadas de operador nuevas y de constructor. Para este tipo de tarea, mmap es inmejorable en comparación con la deserialización. Por supuesto, uno debe buscar en los punteros reubicables para esto.


Eso suena más como una receta para el desastre. ¿Qué haces si cambia el diseño del objeto? Si tiene funciones virtuales, todos los punteros vftbl probablemente estarán equivocados. ¿Cómo se controla dónde se asigna el archivo? Puede darle una dirección, pero es solo una pista y el núcleo puede elegir otra dirección base.
Jens

Esto funciona perfectamente cuando tiene un diseño de árbol estable y claramente definido. Luego, puede convertir todo a sus estructuras relevantes y seguir los punteros internos del archivo agregando un desplazamiento de "dirección de inicio de mmap" cada vez. Esto es muy similar a los sistemas de archivos que usan inodes y árboles de directorios
Mike76

1

Esto suena como un buen caso de uso para subprocesos múltiples ... Creo que podría configurar fácilmente un subproceso para leer datos mientras los otros lo procesan. Esa puede ser una forma de aumentar dramáticamente el rendimiento percibido. Solo un pensamiento.


Sí. He estado pensando en eso y probablemente lo probaré en una versión posterior. La única reserva que tengo es que el procesamiento es mucho más corto que la latencia de E / S, por lo que puede que no haya muchos beneficios.
jbl

1

Creo que lo mejor de mmap es el potencial para la lectura asincrónica con:

    addr1 = NULL;
    while( size_left > 0 ) {
        r = min(MMAP_SIZE, size_left);
        addr2 = mmap(NULL, r,
            PROT_READ, MAP_FLAGS,
            0, pos);
        if (addr1 != NULL)
        {
            /* process mmap from prev cycle */
            feed_data(ctx, addr1, MMAP_SIZE);
            munmap(addr1, MMAP_SIZE);
        }
        addr1 = addr2;
        size_left -= r;
        pos += r;
    }
    feed_data(ctx, addr1, r);
    munmap(addr1, r);

El problema es que no puedo encontrar el MAP_FLAGS correcto para dar una pista de que esta memoria debe sincronizarse desde el archivo lo antes posible. Espero que MAP_POPULATE dé la pista correcta para mmap (es decir, no intentará cargar todo el contenido antes de regresar de la llamada, pero lo hará de forma asincrónica con feed_data). Al menos, ofrece mejores resultados con este indicador, incluso en el manual, indica que no hace nada sin MAP_PRIVATE desde 2.6.23.


2
Desea posix_madvisecon laWILLNEED bandera prepoblaciones insinuantes.
ShadowRanger

@ShadowRanger, suena razonable. Aunque actualizaría la página de manual para indicar claramente que posix_madvisees una llamada asincrónica. También sería bueno hacer referencia mlockpara aquellos que desean esperar hasta que toda la región de memoria esté disponible sin fallas de página.
ony
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.