Я преследую пару потенциальных утечек памяти в базе кода Perl, и я хотел бы узнать о распространенных ошибках в отношении управления памятью (mis-) в Perl.
Каковы общие шаблоны утечки, которые вы наблюдали в коде Perl?
Я преследую пару потенциальных утечек памяти в базе кода Perl, и я хотел бы узнать о распространенных ошибках в отношении управления памятью (mis-) в Perl.
Каковы общие шаблоны утечки, которые вы наблюдали в коде Perl?
Циркулярные ссылки являются наиболее распространенной канонической причиной утечек.
sub leak {
my ($foo, $bar);
$foo = \$bar;
$bar = \$foo;
}
Perl использует подсчет ссылок на сбор мусора. Это означает, что perl хранит подсчет того, какие указатели на любую переменную существуют в данный момент времени. Если переменная выходит за пределы области действия, а число равно 0, переменная очищается.
В приведенном выше примере код $foo
и $bar
никогда не собираются, и копия сохраняется после каждого вызова leak()
, поскольку обе переменные имеют счетчик ссылок 1.
Самый простой способ предотвратить эту проблему - использовать слабые ссылки. Слабые ссылки - это ссылки, которые вы используете для доступа к данным, но не учитываете сбор мусора.
use Scalar::Util qw(weaken);
sub dont_leak {
my ($foo, $bar);
$foo = \$bar;
$bar = \$foo;
weaken $bar;
}
В dont_leak()
, $foo
имеет счетчик ссылок 0, $bar
имеет счетчик ссылок 1. Когда мы покидаем область действия подпрограммы, в пул возвращается $foo
, а его ссылка на $bar
очищается. Это уменьшает количество ссылок на $bar
до 0, что означает, что $bar
также может вернуться в пул.
Update: мозг d foy спросил, есть ли у меня какие-либо данные для подтверждения моего утверждения о том, что циркулярные ссылки являются общими. Нет, у меня нет статистики, чтобы показать, что циркулярные ссылки являются общими. Они чаще всего обсуждаются и являются наиболее документированной формой утечек памяти perl.
Мой опыт в том, что они происходят. Вот краткое описание утечек памяти, которые я видел более десяти лет работы с Perl.
У меня возникли проблемы с запуском приложений pTk. Некоторые утечки, которые я смог доказать, были вызваны круговыми ссылками, которые возникли, когда Tk передает ссылки на окна. Я также видел pTk утечки, причиной которых я никогда не мог отследить.
Я видел, как люди неправильно понимают weaken
и случайно заканчивают круговыми ссылками.
Я видел непреднамеренные циклы, когда слишком много плохо продуманных объектов бросаются вместе в спешке.
В одном случае я обнаружил утечки памяти, которые исходили от модуля XS, который создавал большие, глубокие структуры данных. Я никогда не мог получить воспроизводимый тестовый пример, который был меньше, чем вся программа. Но когда я заменил модуль другим сериализатором, утечки исчезли. Поэтому я знаю, что эти утечки произошли от XS.
Итак, по моему опыту, циклы являются основным источником утечек.
К счастью, есть модуль, чтобы отслеживать их.
Что касается того, что большие глобальные структуры, которые никогда не очищаются, составляют "утечки", я согласен с Брайаном. Они взлетают подобно утечкам (у нас постоянно растет использование памяти процесса из-за ошибки), поэтому они являются утечками. Тем не менее, я не помню, чтобы когда-либо видел эту проблему в дикой природе.
Основываясь на том, что я вижу на сайте Stonehenge, я предполагаю, что Брайан видит много больного кода у людей, которых он тренирует или претворяет целебные чудеса. Таким образом, его набор образцов легко намного больше и разнообразнее, чем у меня, но у него есть собственный выбор смещения.
Какая причина утечек наиболее распространена? Я не думаю, что мы когда-нибудь узнаем. Но мы все можем согласиться с тем, что циркулярные ссылки и глобальные свалки данных являются анти-шаблонами, которые необходимо устранить там, где это возможно, и обрабатывать с осторожностью и осторожностью в тех немногих случаях, когда они имеют смысл.
Если проблема в коде Perl, у вас может быть ссылка, указывающая на себя или родительский node.
Обычно он приходит в виде объекта, ссылающегося на родительский объект.
{ package parent;
sub new{ bless { 'name' => $_[1] }, $_[0] }
sub add_child{
my($self,$child_name) = @_;
my $child = child->new($child_name,$self);
$self->{$child_name} = $child; # saves a reference to the child
return $child;
}
}
{ package child;
sub new{
my($class,$name,$parent) = @_;
my $self = bless {
'name' => $name,
'parent' => $parent # saves a reference to the parent
}, $class;
return $self;
}
}
{
my $parent = parent->new('Dad');
my $child = parent->add_child('Son');
# At this point both of these are true
# $parent->{Son}{parent} == $parent
# $child->{parent}{Son} == $child
# Both of the objects **would** be destroyed upon leaving
# the current scope, except that the object is self-referential
}
# Both objects still exist here, but there is no way to access either of them.
Лучший способ исправить это - использовать Scalar:: Util:: ослаблять.
use Scalar::Util qw'weaken';
{ package child;
sub new{
my($class,$name,$parent) = @_;
my $self = bless {
'name' => $name,
'parent' => $parent
}, $class;
weaken ${$self->{parent}};
return $self;
}
}
Я бы рекомендовал удалить ссылку на родительский объект из дочернего элемента, если это вообще возможно.
У меня были проблемы с XS в прошлом, как мои собственные ручные вещи, так и модули CPAN, где память просочилась из кода C, если она не управляется должным образом. Мне никогда не удавалось проследить утечки; проект был в сжатые сроки и имел фиксированный срок службы, поэтому я опубликовал эту проблему с ежедневной перезагрузкой cron
. cron
действительно замечательно.
Некоторые модули из CPAN используют круговые ссылки для выполнения своей работы, например. HTML:: TreeBuilder (который представляет дерево HTML). Они потребуют от вас запускать какой-либо метод/процедуру уничтожения в конце. Просто прочитайте документы:)