2010-06-04 10 views
7

Linux używa COW, aby zmniejszyć zużycie pamięci po rozwidleniu, ale sposób, w jaki zmienne Perl 5 działają w perl, zdaje się ominąć tę optymalizację. Na przykład, dla zmiennej:W jaki sposób mogę zapobiec uszkodzeniu pamięci podczas przetwarzania potomków przez zmienne metadane?

my $s = "1"; 

perl jest naprawdę przechowywania:

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

Podczas korzystania z tego ciągu w kontekście numerycznym, to modyfikuje C struct reprezentujący dane:

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

Sam wskaźnik łańcucha nie zmienił się (nadal jest to 0x100201d50), ale teraz jest w innym C struct (a PVIV zamiast PV). Nie zmieniłem w ogóle wartości, ale nagle płacę koszt COW. Czy istnieje sposób na zablokowanie reprezentacji zmiennej Perl 5, tak aby oszczędzanie czasu (perl nie musiało po raz drugi konwertować "0" na 0) nie rani mojego użycia pamięci?

Uwaga, reprezentacje powyżej zostały wygenerowane z tego kodu:

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

Odpowiedz

4

Jedynym rozwiązaniem znalazłem do tej pory, jest upewnienie się wymusić perl zrobić wszystkie konwersje się spodziewać w procesie macierzystym . Z kodu poniżej widać, że nawet to tylko trochę pomaga.

Wyniki:

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 

Kod:

#!/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

Dobrze, ale czy wiesz, co się stanie, gdy przydzielisz kilkaset MB danych, rozwiążesz kilka dzieci i że dzieci się skończą? GC i tak cię zabije. To smutna historia, ale Perl to niewłaściwe narzędzie do tego rodzaju pracy.Rozwiązaliśmy go częściowo, stosując podejście END {kill 9 $$}, ale w tym momencie powinieneś poszukać lepszego narzędzia ;-) –

+1

GC mi nie przeszkadza, prawdziwy kod to 'mod_perl' i każde dziecko jest wielokrotnie używane . Problem polega na tym, że dane konfiguracyjne załadowane do rodzica są kopiowane do każdego z setek dzieci, mimo że dzieci nigdy go nie modyfikują (z punktu widzenia Perla 5, "perl" zawiera metadane). Drugim rozwiązaniem, które rozważałem, jest przeniesienie danych konfiguracyjnych do osobnego procesu i umożliwienie dzieciom rozmawiania z nimi przez gniazda domeny. –

+0

Możesz także użyć pamięci współdzielonej, która będzie szybsza. –

2

Zresztą jeśli uniknąć krowa na starcie i podczas biegu nie należy zapomniał końcowej fazie życia. Podczas zamykania są dwie fazy GC, gdy w pierwszej jest liczba nowych aktualizacji, które mogą cię zabić w miły sposób. Można to rozwiązać w brzydki:

END { kill 9, $$ } 
+0

Twoja odpowiedź nie ma większego sensu. – Ether

+1

@Inne na końcu programu wszystkie zmienne otrzymają pola "REFCNT" zmniejszone przez GC. Spowoduje to nagły wzrost wykorzystania pamięci, ponieważ wszystkie zmienne są wciągane do procesu potomnego. Kod, który sugeruje, spowoduje śmierć dziecka przed rozpoczęciem fazy zbierania śmieci. –

2

to oczywiste, ale krowa nie zdarza się na zasadzie per-struktury, ale na podstawie widoku pamięci. Więc wystarczy, że jedna rzecz na całej stronie pamięci zostanie zmodyfikowana w taki sposób, abyś mógł ponieść koszty kopiowania.

W systemie Linux można wyszukać rozmiar strony tak:

getconf PAGESIZE 

w moim systemie, który jest 4096 bajtów. Możesz dopasować wiele skalarnych struktur Perla w tej przestrzeni. Jeśli jedna z tych rzeczy zostanie zmodyfikowana, Linux będzie musiał skopiować całą rzecz.

Dlatego używanie aren pamięci jest ogólnie dobrym pomysłem. Powinieneś oddzielić dane zmienne i niezmienne, aby nie musieć płacić za dane stałe za niezmienne dane tylko dlatego, że znajdowały się na tej samej stronie pamięci, co zmienne dane.

+1

Problem polega na tym, że 'perl' aktualizuje' struct', nawet jeśli nie zmieniam żadnych danych, na których mi zależy, więc nie mogę oddzielić moich niezmiennych danych (ponieważ nie ma niezmiennych danych). –

+1

Ponadto, nie ma łatwego sposobu na oddzielenie zmiennej od niezmiennych danych (nawet jeśli były jakieś). – Ether

Powiązane problemy