Как я могу предотвратить взрыв памяти, когда дочерние процессы касаются переменных метаданных? - PullRequest
7 голосов
/ 04 июня 2010

Linux использует COW , чтобы поддерживать низкое потребление памяти после разветвления, но способ, которым переменные Perl 5 работают в perl, кажется, побеждает эту оптимизацию. Например, для переменной:

my $s = "1";

perl действительно хранит:

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

Когда вы используете эту строку в числовом контексте, она модифицирует C struct, представляющий данные:

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

Сам указатель строки не изменился (он все еще 0x100201d50), но теперь он находится в другом C struct (PVIV вместо PV). Я не изменил значение вообще, но вдруг я плачу стоимость COW. Есть ли способ заблокировать представление perl переменной Perl 5, чтобы сэкономить время (perl не нужно преобразовывать "0" в 0 второй раз), хак не повредит использованию моей памяти

Обратите внимание, что представленные выше представления были сгенерированы из этого кода:

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

Ответы [ 3 ]

4 голосов
/ 04 июня 2010

Единственное решение, которое я нашел до сих пор, - это заставить perl выполнить все преобразования, которые я ожидаю в родительском процессе. Из приведенного ниже кода видно, что это даже немного помогает.

Результаты:

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

Код:

#!/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");
2 голосов
/ 04 июня 2010

Это само собой разумеется, но COW происходит не на основе структуры, а на основе страницы памяти. Поэтому достаточно, чтобы одна вещь на целой странице памяти была изменена таким образом, чтобы вы оплачивали стоимость копирования.

В Linux вы можете запросить размер страницы следующим образом:

getconf PAGESIZE

В моей системе это 4096 байт. Вы можете разместить много скалярных структур Perl в этом пространстве. Если что-то из этого будет изменено, Linux придется скопировать все это.

Вот почему использование арен памяти является хорошей идеей в целом. Вы должны разделить свои изменяемые и неизменяемые данные, чтобы вам не приходилось платить COW за неизменные данные только потому, что они находились на той же странице памяти, что и изменяемые данные.

2 голосов
/ 04 июня 2010

В любом случае, если вы избегаете COW при запуске и во время выполнения, вы не должны забывать об окончании фазы жизни. В выключении есть две фазы GC, когда в первую очередь происходят обновления счетчиков ссылок, так что это может убить вас хорошим способом. Вы можете решить это безобразно:

END { kill 9, $$ }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...