2010-06-04 8 views
7

Linux usa COW para mantener el uso de memoria bajo después de una bifurcación, pero la forma en que funcionan las variables Perl 5 en perl parece anular esta optimización. Por ejemplo, para la variable:¿Cómo puedo evitar que la memoria explote cuando los procesos secundarios tocan metadatos variables?

my $s = "1"; 

perl es realmente el almacenamiento:

SV = PV(0x100801068) at 0x1008272e8 
    REFCNT = 1 
    FLAGS = (POK,pPOK) 
    PV = 0x100201d50 "1"\0 
    CUR = 1 
    LEN = 16 

Cuando se utiliza esa cadena en un contexto numérico, modifica el C struct representación de los datos:

SV = PVIV(0x100821610) at 0x1008272e8 
    REFCNT = 1 
    FLAGS = (IOK,POK,pIOK,pPOK) 
    IV = 1 
    PV = 0x100201d50 "1"\0 
    CUR = 1 
    LEN = 16 

El puntero de cadena en sí no cambió (todavía es 0x100201d50), pero ahora está en un C struct diferente (a PVIV en lugar de PV). No modifiqué el valor en absoluto, pero de repente estoy pagando un costo de VACA. ¿Hay alguna manera de bloquear la representación de perl de una variable Perl 5 para que este ahorro de tiempo (perl no tenga que convertir "0" en 0 por segunda vez) hack no hace daño al uso de mi memoria?

Nota, las representaciones anteriormente fueron generados a partir de este código:

perl -MDevel::Peek -e '$s = "1"; Dump $s; $s + 0; Dump $s' 

Respuesta

4

La única solución que he encontrado hasta el momento, es asegurarse de fuerzo perl para hacer todas las conversiones que cabe esperar en el proceso padre . Y puede ver en el código a continuación, incluso eso solo ayuda un poco.

Resultados:

Useless use of addition (+) in void context at z.pl line 34. 
Useless use of addition (+) in void context at z.pl line 45. 
Useless use of addition (+) in void context at z.pl line 51. 
before eating memory 
used memory: 71 
after eating memory 
used memory: 119 
after 100 forks that don't reference variable 
used memory: 144 
after children are reaped 
used memory: 93 
after 100 forks that touch the variables metadata 
used memory: 707 
after children are reaped 
used memory: 93 
after parent has updated the metadata 
used memory: 109 
after 100 forks that touch the variables metadata 
used memory: 443 
after children are reaped 
used memory: 109 

Código:

#!/usr/bin/perl 

use strict; 
use warnings; 

use Parallel::ForkManager; 

sub print_mem { 
    print @_, "used memory: ", `free -m` =~ m{cache:\s+([0-9]+)}s, "\n"; 
} 

print_mem("before eating memory\n"); 

my @big = ("1") x (1_024 * 1024); 

my $pm = Parallel::ForkManager->new(100); 

print_mem("after eating memory\n"); 

for (1 .. 100) { 
    next if $pm->start; 
    sleep 2; 
    $pm->finish; 
} 

print_mem("after 100 forks that don't reference variable\n"); 

$pm->wait_all_children; 

print_mem("after children are reaped\n"); 

for (1 .. 100) { 
    next if $pm->start; 
    $_ + 0 for @big; #force an update to the metadata 
    sleep 2; 
    $pm->finish; 
} 

print_mem("after 100 forks that touch the variables metadata\n"); 

$pm->wait_all_children; 

print_mem("after children are reaped\n"); 

$_ + 0 for @big; #force an update to the metadata 

print_mem("after parent has updated the metadata\n"); 

for (1 .. 100) { 
    next if $pm->start; 
    $_ + 0 for @big; #force an update to the metadata 
    sleep 2; 
    $pm->finish; 
} 

print_mem("after 100 forks that touch the variables metadata\n"); 

$pm->wait_all_children; 

print_mem("after children are reaped\n"); 
+0

Bien, pero ¿sabes qué pasará cuando asignes unos cientos de MB de datos, ahorres pocos hijos y esos niños terminarán? GC te matará de todos modos. Es una historia triste, pero Perl es la herramienta equivocada para este tipo de trabajo.Lo solucionamos parcialmente usando el método END {kill 9 $$}, pero en este punto deberías buscar una herramienta mejor ;-) –

+1

El GC no me molesta, el código real está basado en 'mod_perl' y cada niño se reutiliza muchas veces . El problema es que los datos de configuración que se cargan en el elemento primario se copian en cada uno de los posibles cientos de niños aunque los niños nunca lo modifiquen (desde la perspectiva de Perl 5, 'perl' se está volcando con los metadatos). La otra solución que he considerado es mover los datos de configuración a un proceso separado y dejar que los niños hablen sobre los sockets de dominio. –

+0

También puede usar la memoria compartida, que será más rápida. –

2

De todos modos si se evita VACA en el arranque y durante la marcha no se debe olvidó fase final de la vida. En el apagado hay dos fases de GC cuando en el primero hay actualizaciones de recuento de ref que pueden matarte de una manera agradable. Puede en resolverlo fea:

END { kill 9, $$ } 
+0

Su respuesta no tiene mucho sentido. – Ether

+1

@Ether al final del programa todas las variables obtendrán sus campos 'REFCNT' decrementados por el GC. Esto causará un aumento repentino en el uso de la memoria ya que todas las variables se insertan en el proceso secundario. El código que está sugiriendo hará que el niño muera antes de que comience la fase de recolección de basura. –

2

Esto es evidente, pero vaca no sucederá en función de cada estructura, pero sobre una base de página de memoria. Por lo tanto, es suficiente que una cosa en una página de memoria completa se modifique de esta manera para que pueda pagar el costo de copiado.

En Linux se puede consultar el tamaño de página de esta manera:

getconf PAGESIZE 

En mi sistema que es de 4096 bytes. Puede caber muchas estructuras escalares de Perl en ese espacio. Si una de esas cosas se modifica, Linux tendrá que copiar todo.

Es por eso que usar arenas de memoria es una buena idea en general. Debe separar sus datos mutables e inmutables para que no tenga que pagar costos COW por datos inmutables solo porque residen en la misma página de memoria que los datos mutables.

+1

El problema es que 'perl' actualiza la 'estructura' aunque no estoy cambiando ninguno de los datos que me interesan, así que no puedo separar mis datos inmutables (porque no hay datos inmutables). –

+1

Además, no existe una manera fácil de separar los datos mutables de los inmutables (incluso si los hubiera). – Ether

Cuestiones relacionadas