2009-12-29 13 views
13

Tengo algunos archivos enormes que necesito analizar, y la gente ha estado recomendando mmap porque esto debería evitar tener que asignar todo el archivo en la memoria.problema mmap, asigna grandes cantidades de memoria

Pero al mirar 'arriba' parece que estoy abriendo todo el archivo en la memoria, así que creo que debo estar haciendo algo mal. 'top shows> 2.1 gig'

Este es un fragmento de código que muestra lo que estoy haciendo.

Gracias

#include <stdio.h> 
#include <stdlib.h> 
#include <err.h> 
#include <fcntl.h> 
#include <sysexits.h> 
#include <unistd.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <sys/mman.h> 
#include <cstring> 
int main (int argc, char *argv[]) { 
    struct stat sb; 
    char *p,*q; 
    //open filedescriptor 
    int fd = open (argv[1], O_RDONLY); 
    //initialize a stat for getting the filesize 
    if (fstat (fd, &sb) == -1) { 
    perror ("fstat"); 
    return 1; 
    } 
    //do the actual mmap, and keep pointer to the first element 
    p =(char *) mmap (0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); 
    q=p; 
    //something went wrong 
    if (p == MAP_FAILED) { 
    perror ("mmap"); 
    return 1; 
    } 
    //lets just count the number of lines 
    size_t numlines=0; 
    while(*p++!='\0') 
    if(*p=='\n') 
     numlines++; 
    fprintf(stderr,"numlines:%lu\n",numlines); 
    //unmap it 
    if (munmap (q, sb.st_size) == -1) { 
    perror ("munmap"); 
    return 1; 
    } 
    if (close (fd) == -1) { 
    perror ("close"); 
    return 1; 
    } 
    return 0; 
} 
+0

@monkeyking, el cierre adecuado para el código pre es/pre/código, no para la publicación :-) Se corrigieron las etiquetas de código por usted. – paxdiablo

+0

¡Ahh gracias un millón! ¿Qué pasa con #include No pude poner estos en el ejemplo de código – monkeyking

+0

Marque todo el bloque y luego use CTRL-K, esto sangrará por cuatro espacios. Ya lo hice y deberías poder ver un stdio incluido. – paxdiablo

Respuesta

39

no, lo que está haciendo es mapeo el archivo en memoria. Esto es diferente a leer el archivo en la memoria.

Si lo leyeras, tendrías que transferir todo el contenido a la memoria. Al mapearlo, permite que el sistema operativo lo maneje. Si intenta leer o escribir en una ubicación en esa área de memoria, el SO cargará primero la sección relevante para usted. Va a no cargar el archivo completo a menos que se necesite todo el archivo.

Ahí es donde obtiene su ganancia de rendimiento. Si mapea el archivo completo, pero solo cambia un byte y luego lo desasigna, encontrará que no hay mucho E/S de disco en absoluto.

Por supuesto, si tocas cada byte en el archivo, entonces sí, todo se cargará en algún punto pero no necesariamente en la RAM física de una sola vez. Pero ese es el caso incluso si carga todo el archivo por adelantado. El sistema operativo intercambiará partes de sus datos si no hay suficiente memoria física para contenerlo, junto con los demás procesos del sistema.

Las principales ventajas de la asignación de memoria son:

  • que diferir la lectura de las secciones de archivo hasta que se necesitan (y, si no son necesarios, no se cargan). Por lo tanto, no hay un gran costo inicial al cargar todo el archivo. Amortiza el costo de la carga.
  • Las escrituras son automáticas, no es necesario que escriba cada byte. Simplemente ciérrelo y el SO escribirá las secciones modificadas. Creo que esto también ocurre cuando la memoria también se intercambia (en situaciones de poca memoria física), ya que el buffer es simplemente una ventana al archivo.

Tenga en cuenta que probablemente exista una desconexión entre el uso del espacio de direcciones y el uso de la memoria física. Puede asignar un espacio de direcciones de 4G (idealmente, aunque puede haber limitaciones de sistema operativo, BIOS o hardware) en una máquina de 32 bits con solo 1G de RAM. El SO maneja la paginación hacia y desde el disco.

Y para responder a su petición posterior de aclaración:

sólo para aclarar. Entonces, si necesito el archivo completo, ¿mmap cargará realmente el archivo completo?

Sí, pero puede que no sea en memoria física a la vez. El sistema operativo intercambiará bits de vuelta al sistema de archivos para traer nuevos bits.

Pero también lo hará si ha leído todo el archivo de forma manual. La diferencia entre esas dos situaciones es la siguiente.

Con el archivo leído en la memoria de forma manual, el sistema operativo intercambiará partes del espacio de direcciones (puede incluir los datos o no) al archivo de intercambio. Y tendrá que volver a escribir manualmente el archivo cuando haya terminado con él.

Con la asignación de memoria, le ha indicado efectivamente que utilice el archivo original como un área de intercambio adicional para ese archivo/memoria solamente. Y, cuando los datos se escriben en ese área de intercambio, afecta el archivo real inmediatamente. Así que no tiene que reescribir manualmente nada cuando haya terminado y no afectará el intercambio normal (generalmente).

lo que realmente es sólo una ventana para el archivo:

                                        memory mapped file image

+0

Solo para aclarar. Entonces, si necesito el archivo completo, ¿mmap cargará realmente el archivo completo? – monkeyking

+0

Sí, mira la actualización. – paxdiablo

+0

@paxdiablo, ¿podría aclarar esto también? "Con el archivo leído en la memoria de forma manual, el sistema operativo intercambiará partes del espacio de direcciones (puede incluir los datos o no) al archivo de intercambio". Quiere decir que si leemos (2) todo el archivo en la memoria -> escriba (2) algunos datos -> cierre (2) (fsync (2) si es necesario) el archivo no contendrá los últimos cambios ? ¿O se debe utilizar el siguiente esquema? leer (2) -> algunos cambios -> escribir (2) todo el archivo. – dshil

0

El sistema sin duda tratará de poner todos sus datos en la memoria física. Lo que conservarás es intercambio.

+0

mal. la VM usará RAM para hacer que el archivo esté disponible; pero se intercambiará tan pronto como haya algo de presión en la memoria. Es casi exactamente como usar RAM como caché para el archivo. – Javier

+0

Incorrecto. Nunca usará el espacio de intercambio para una asignación de solo lectura. Hará E/S para intercambiarlo, pero no usará espacio. – bmargulies

3

top tiene muchas columnas relacionadas con la memoria. La mayoría de ellos se basan en el tamaño del espacio de memoria asignado al proceso; incluidas las bibliotecas compartidas, la memoria RAM intercambiada y el espacio mmapped.

Compruebe la columna RES, esto está relacionado con la RAM física actualmente en uso. Creo (pero no estoy seguro) que incluiría la memoria RAM utilizada para "almacenar en caché" el archivo mmap'ped

1

"asignar el archivo completo en la memoria" combina dos problemas. Una es la cantidad de memoria virtual que asigna; el otro es qué partes del archivo se leen del disco a la memoria. Aquí está asignando espacio suficiente para contener todo el archivo. Sin embargo, solo las páginas que toque se cambiarán realmente en el disco. Y, se cambiarán correctamente sin importar lo que ocurra con el proceso, una vez que haya actualizado los bytes en la memoria asignada por mmap. Puede asignar menos memoria asignando solo una sección del archivo a la vez utilizando los parámetros "tamaño" y "desplazamiento" de mmap. Luego, usted debe administrar una ventana en el archivo usted mismo mediante el mapeo y el mapeo, tal vez moviendo la ventana a través del archivo. Asignar un gran trozo de memoria lleva un tiempo apreciable. Esto puede introducir un retraso inesperado en la aplicación. Si su proceso ya es intensivo en memoria, la memoria virtual puede haberse fragmentado y puede ser imposible encontrar un trozo suficientemente grande para un archivo grande en el momento en que lo solicite. Por lo tanto, es necesario tratar de hacer el mapeo tan pronto como sea posible, o utilizar alguna estrategia para mantener un trozo de memoria lo suficientemente grande disponible hasta que lo necesite.

Sin embargo, dado que especifica que necesita analizar el archivo, ¿por qué no evitarlo por completo organizando su analizador para operar en una secuencia de datos? Entonces lo máximo que necesitará es un vistazo anticipado y un poco de historia, en lugar de tener que asignar trozos discretos del archivo a la memoria.

2

Es posible que le hayan ofrecido un consejo equivocado.

Los archivos mapeados en la memoria (mmap) utilizarán más y más memoria a medida que los analice. Cuando la memoria física se reduce, el kernel desasignará secciones del archivo de la memoria física en función de su algoritmo LRU (utilizado menos recientemente). Pero el LRU también es global.La LRU también puede obligar a otros procesos a intercambiar páginas en el disco y reducir la memoria caché del disco. Esto puede tener un efecto muy negativo en el rendimiento de otros procesos y del sistema en general.

Si lee linealmente archivos, como contar el número de líneas, mmap es una mala opción, ya que llenará la memoria física antes de volver a liberar la memoria en el sistema. Sería mejor utilizar métodos de E/S tradicionales que transmiten o leen en un bloque a la vez. De esa forma, la memoria puede liberarse inmediatamente después.

Si tiene acceso aleatorio a un archivo, mmap es una buena opción. Pero no es óptimo, ya que seguiría confiando en el algoritmo LRU general del kernel, pero es más rápido de usar que escribir su mecanismo de almacenamiento en caché.

En general, nunca recomendaría que alguien use mmap, excepto en algunos casos extremos de rendimiento extremo, como acceder al archivo desde múltiples procesos o hilos al mismo tiempo, o cuando el archivo es pequeño en relación con la cantidad de memoria disponible.

+1

Meh. Puede hacer aproximadamente 10 búsquedas de árbol con mmap en el tiempo que le lleva preadmitir una estructura de árbol B + bloque por bloque. –

+0

No necesariamente cierto. El rendimiento de la primera IO de lectura será casi idéntico (para todos los fines prácticos) entre mmap y pread, ambos tienen que leerlo desde los medios. El problema es con las lecturas posteriores. Mmap utilizará el algoritmo LRU de desalojo de memoria del kernel para decidir qué páginas asignar. Con Pread, el subsistema IO decidirá qué bloques eliminar del caché (si hay alguno). Ninguno de los enfoques es altamente eficiente en términos de liberar recursos de memoria no utilizados. Por lo tanto, la aplicación que confía en mmap puede reducir el rendimiento y la eficacia de todo el sistema al evitar los recursos de memoria. – tgiphil

+1

No está contando los varios miles de ciclos de CPU desperdiciados por cada llamada al sistema. mmap se carga más rápido. –

0

Debe especificar un tamaño inferior al tamaño total del archivo en la llamada mmap, si no desea que todo el archivo se asigne en la memoria a la vez. Usando el parámetro de compensación, y un tamaño más pequeño, puede asignar en "ventanas" del archivo más grande, una pieza a la vez.

Si su análisis sintáctico es una sola pasada por el archivo, con un mínimo de retroceso o anticipación, entonces en realidad no obtendrá nada utilizando mmap en lugar de la biblioteca estándar de E/S con búfer. En el ejemplo que dio de contar las nuevas líneas en el archivo, sería igual de rápido hacerlo con fread(). Supongo que su análisis real es más complejo, sin embargo.

Si necesita leer más de una parte del archivo a la vez, tendrá que administrar varias regiones de mmap, lo que puede complicarse rápidamente.

0

Un poco fuera de tema.

No estoy del todo de acuerdo con la respuesta de Mark. En realidad, mmap es más rápido que fread.

A pesar de aprovechar el búfer de disco del sistema, fread también tiene un búfer interno, y además, los datos se copiarán en el búfer proporcionado por el usuario como se le llama.

Por el contrario, mmap simplemente devuelve un puntero al búfer del sistema. Por lo tanto, hay un con dos copias de memoria para guardar.

Pero usar mmap es un poco peligroso. Debe asegurarse de que el puntero nunca salga del archivo, o habrá un error de segmento . Mientras que en este caso fread simplemente devuelve cero.

+0

De hecho, he hecho benchmarking que muestra que (en Mac OS X, de todos modos) casi no hay diferencia en el rendimiento entre mmap y fread para una lectura directa. Sí, utilizando la biblioteca de alto nivel, los datos se copian (hasta tres veces), pero el tiempo para copiar los datos es insignificante en comparación con el tiempo de E/S real. Usualmente uso la interfaz de más alto nivel que sea apropiada. –

+1

@Mark: está de acuerdo con usted cuando el archivo se lee por primera vez. Sin embargo, si el programa lee el archivo más de una vez, o el programa se ejecuta repetidamente (servidor web, por ejemplo), habrá una gran diferencia. (Cambiar 'fread' a' mmap' hizo que todo el programa sea un 50% más rápido en mi experiencia) – iamamac

+0

Especialmente cuando se considera que 'fseek' +' fread' siempre lee el tamaño completo del buffer para cualquier tamaño dado. –

4

También puede usar fadvise (2) (y madvise (2), ver también posix_fadvise & posix_madvise) para marcar el archivo mapeado (o sus partes) como leído una vez.

#include <sys/mman.h> 

int madvise(void *start, size_t length, int advice); 

El consejo se indica en el parámetro consejos que puede ser

MADV_SEQUENTIAL 

Esperar referencias a páginas en orden secuencial. (Por lo tanto, las páginas en el rango dado se pueden leer agresivamente adelante, y pueden liberarse poco después de que se acceda a ellas.)

Portabilidad: posix_madvise y forma parte posix_fadvise opción avanzada en tiempo real del IEEE Std 1003.1, 2004. Y constantes será POSIX_MADV_SEQUENTIAL y POSIX_FADV_SEQUENTIAL de.

Cuestiones relacionadas