2009-06-27 17 views
9

Estoy tratando de optimizar el manejo de grandes conjuntos de datos usando mmap. Un conjunto de datos está en el rango de gigabytes. La idea era mapear todo el archivo en la memoria, lo que permite que varios procesos funcionen simultáneamente en el conjunto de datos (solo lectura). Sin embargo, no está funcionando como se esperaba.Linux/perl mmap performance

Como una simple prueba simplemente mmap el archivo (utilizando el módulo Sys :: Mmap de perl, usando el sub "mmap" que creo que se asigna directamente a la función C subyacente) y hago que el proceso duerma. Al hacer esto, el código pasa más de un minuto antes de que regrese de la llamada mmap, a pesar de esta prueba que no hace nada, ni siquiera una lectura, del archivo mmap.

Adivinando, tal vez Linux requirió que se leyera todo el archivo cuando se mmap'ed por primera vez, así que después de que el archivo se mapeó en el primer proceso (mientras dormía), invoqué una prueba simple en otro proceso que intenté leer los primeros megabytes del archivo.

Sorprendentemente, parece que el segundo proceso también pasa mucho tiempo antes de regresar de la llamada mmap, casi al mismo tiempo que mmap'ing el archivo la primera vez.

Me he asegurado de que se utiliza MAP_SHARED y de que el proceso que asignó el archivo por primera vez todavía está activo (que no ha terminado y que el mapa no se ha desasignado).

Esperaba que un archivo mmapped me permitiera dar a los procesos múltiples de trabajo un acceso aleatorio efectivo al archivo grande, pero si cada llamada mmap requiere leer todo el archivo primero, es un poco más difícil. No he probado utilizando procesos de larga ejecución para ver si el acceso es rápido después de la primera demora, pero esperaba usar MAP_SHARED y otro proceso separado sería suficiente.

Mi teoría era que mmap regresaría más o menos de inmediato, y que linux cargaría los bloques más o menos según demanda, pero el comportamiento que estoy viendo es el opuesto, lo que indica que es necesario leer todo el archivo en cada llama a mmap

¿Alguna idea de lo que estoy haciendo mal, o si he entendido mal cómo se supone que mmap funciona?

Respuesta

15

Ok, encontró el problema. Como se sospecha, ni Linux ni Perl tenían la culpa. Para abrir y acceder al archivo que hago algo como esto:

#!/usr/bin/perl 
# Create 1 GB file if you do not have one: 
# dd if=/dev/urandom of=test.bin bs=1048576 count=1000 
use strict; use warnings; 
use Sys::Mmap; 

open (my $fh, "<test.bin") 
    || die "open: $!"; 

my $t = time; 
print STDERR "mmapping.. "; 
mmap (my $mh, 0, PROT_READ, MAP_SHARED, $fh) 
    || die "mmap: $!"; 
my $str = unpack ("A1024", substr ($mh, 0, 1024)); 
print STDERR " ", time-$t, " seconds\nsleeping.."; 

sleep (60*60); 

Si el resultado es que el código, no hay retrasos como los que encontré en mi código original, y después de la creación de la muestra mínima (siempre hacer eso, a la derecha !) la razón de repente se hizo obvia.

El error fue que en mi código trata el $mh escalar como un mango, algo que es ligero y se puede mover fácilmente (leer: pasar por valor). Resulta que en realidad es una cadena larga de GB, definitivamente no es algo que quieras mover sin crear una referencia explícita (lingua de perl para un valor de "puntero"/manejar).Por lo tanto, si necesita almacenar en un hash o similar, asegúrese de almacenar \$mh, y eliminarlo cuando necesite usarlo como ${$hash->{mh}}, generalmente como el primer parámetro en un substr o similar.

+3

+1 para dar seguimiento con una explicación detallada. – RichieHindle

+3

Utilice la forma 3 arg de abrir(). –

0

Eso suena sorprendente. ¿Por qué no probar una versión C pura?

O pruebe su código en una versión diferente de OS/perl.

+0

He mirado la interfaz del sistema operativo Perl, y llama a la versión C más o menos directamente, pero a menos que lo averigüe, probablemente también probaré una versión C. En cuanto a la versión OS/perl, he probado en dos sistemas, ambos x86_64. Uno es Ubuntu 8.04.2 (Linux 2.6.24-22, Perl 5.8.8) y el otro Ubuntu 9.04 (Linux 2.6.28-13, Perl 5.10.0). Mismo comportamiento.El segundo sistema era una computadora portátil, y definitivamente puedo confirmar que hay un disco serio involucrado cuando se llama a mmap desde mis pruebas. –

8

Si tiene una versión relativamente reciente de Perl, no debería utilizar Sys :: Mmap. Debería utilizar la capa mmap de PerlIO.

¿Se puede publicar el código que está utilizando?

+0

De acuerdo, la capa mmap de PerlIO es probablemente preferible, ya que también permitiría que el mismo código se ejecute con/sin mmap'ing simplemente agregando/eliminando el atributo mmap. De todos modos, encontré el problema, publiqué el código, resolví el problema. –

+0

Solucione ese problema hasta 2 GB. Para archivos más grandes perl aún tiene problemas, vea mi otra respuesta relacionada con esto. –

+0

¿Funciona la capa mmap de PerlIO para acceder a un fragmento de/dev/mem lectura/escritura? – donaldh

3

En sistemas de 32 bits, el espacio de direcciones para mmap() s es bastante limitado (y varía de sistema operativo a sistema operativo). Tenga en cuenta eso si está usando archivos de varios gigabytes y solo está probando en un sistema de 64 bits. (yo hubiera preferido escribir esto en un comentario pero no tienen suficientes puntos de reputación aún)

+0

+1. Parece una respuesta válida que me responde la pregunta, así que gracias por no publicarla como comentario. –

+0

Como he publicado en mi otra respuesta, incluso en sistemas de 64 bits, todavía hay problemas para archivos más grandes (> 2 GB). Tu respuesta es correcta sin embargo. Ya tengo 64 bits en todas mis máquinas, incluso en la computadora portátil, así que no es un problema para mí. –

0

Ver Wide Finder para un rendimiento Perl con mmap. Pero hay una gran trampa. Si su conjunto de datos estará en HD clásico y leerá de múltiples procesos, puede caer fácilmente en el acceso aleatorio y su IO caerá a valores inaceptables (20 ~ 40 veces).

+0

Lo que trato de hacer es el acceso aleatorio por diseño a partir de procesos múltiples, asegurándome de que solo las partes del archivo a las que se accede con más frecuencia permanecen en la memoria todo el tiempo. ¿Qué patrón sugeriría si se requiere un acceso aleatorio de múltiples procesos y un gran archivo? –

+0

Si * realmente * necesita acceso aleatorio a un archivo enorme, no hay una mejor solución. –

+0

Una mejor solución es poner en cola un montón de solicitudes de lectura, limitadas por un corto tiempo de espera, luego leer los bloques del disco en el orden óptimo, para minimizar los tiempos de búsqueda. No estoy seguro de si algún sistema de archivos ya lo hace, no creo que ZFS lo haga. Funcionaría mejor con muchos procesos simultáneos en ejecución, p. un servidor web, o con una API de IO diferente. –

1

Una cosa que puede ayudar al rendimiento es el uso de 'madvise (2)'. probablemente sea más fácil hecho a través de Inline :: C. 'madvise' le permite decirle al kernel cómo será su patrón de acceso (por ejemplo, secuencial, aleatorio, etc.).

0

Ok, aquí hay otra actualización. El uso del atributo ": mmap" de Sys :: Mmap o PerlIO funciona bien en Perl, pero solo en archivos de hasta 2 GB (el límite mágico de 32 bits). Una vez que el archivo es más de 2 GB, aparecen los siguientes problemas:

Usando Sys :: Mmap y substr para acceder al archivo, parece que substr solo acepta un int de 32 bits para el parámetro de posición, incluso en sistemas donde perl admite 64 bits. Hay al menos un fallo publicado al respecto:

#62646: Maximum string length with substr

usando open(my $fh, "<:mmap", "bigfile.bin"), una vez que el archivo es mayor que 2 GB, parece Perl o bien colgar/o insistir en leer todo el archivo en la primera lectura (no seguro que, nunca lo ejecuté lo suficiente para ver si se completaba), lo que llevó a un rendimiento lento y lento.

No he encontrado ninguna solución a ninguno de estos, y actualmente estoy atascado con operaciones de archivos lentos (no mmap'ed) para trabajar en estos archivos. A menos que encuentre una solución alternativa, es posible que tenga que implementar el procesamiento en C u otro lenguaje de nivel superior que admita mejor la creación de mapas de grandes archivos.

+1

intente utilizar mmap de Sys :: Mmap directamente para crear una ventana deslizante en el escalar. –

+0

Gracias, eso es ciertamente una solución. Sería necesario hacer un seguimiento del puntero en el archivo y mapear/eliminar el mapeo cuando sea necesario, lo que probablemente afecte al rendimiento. Pero probablemente sea aún más rápido que el archivo directo IO. –

+0

Hice algunos benchmarking, confirmando que dinámicamente mapea/desempareja usando un tamaño de segmento de 2 GB, y suponiendo que los interruptores de segmento son bastante infrecuentes, la velocidad es un 30-40% más rápida usando mmap con unmap/mapping que IO de archivo recto en un 3 GB archivo. En un archivo de 2 GB, las diferencias son menores, pero sospecho que esto se debe a que mi computadora portátil almacena en caché la mayor parte del archivo durante los accesos aleatorios de todos modos. Entonces, al menos, tengo una solución que funciona, aunque no tan limpiamente como esperaba. Sin embargo, no es necesario realizar más optimizaciones en esta etapa. –

0

Si puedo conectar mi propio módulo: aconsejaría usar File::Map en lugar de Sys::Mmap. Es mucho más fácil de usar y es menos propenso a los bloqueos que Sys :: Mmap.

+0

Aquí hay una sugerencia para una nueva característica muy útil, basada en mi observación de Perl descrita en este hilo (archivos mapeados en memoria que solo funcionan hasta 2 GB); si el usuario asigna un archivo de más de 2 GB, utilice un enfoque segmentado con una función de lectura "personalizada" que automáticamente desasigna mapas según sea necesario. Al menos hasta que se corrija el 2 GB perl "error". –

0

Su acceso a ese archivo debe ser mejor al azar para justificar un mmap completo. Si su uso no se distribuye de manera uniforme, probablemente sea mejor con una búsqueda, lea en un área recién mallada y procese, libere, enjuague y repita. Y trabaja con fragmentos de múltiplos de 4k, digamos 64k más o menos.

Una vez analicé muchos algoritmos de coincidencia de patrones de cadenas. mmaping todo el archivo fue lento y sin sentido. Leer en un buffer estático de 32kish fue mejor, pero aún no es particularmente bueno. Leer a trozos recién malloced, procesar eso y luego dejarlo ir permite que kernel haga maravillas bajo el capó. La diferencia en la velocidad fue enorme, pero de nuevo la coincidencia de patrones es muy rápida en cuanto a la complejidad y se debe poner más énfasis en la eficiencia de manejo de lo que normalmente se necesita.