Я преследую несколько потенциальных утечек памяти в кодовой базе Perl, и я хотел бы знать о распространенных ошибках относительно памяти (неправильное) управление в 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
также может вернуться в пул.
Обновление: мозг спросил, есть ли у меня данные, подтверждающие мое утверждение о том, что циклические ссылки являются обычным явлением. Нет, у меня нет статистики, показывающей, что циклические ссылки - обычное дело. Это наиболее часто обсуждаемая и наиболее документированная форма утечек памяти Perl.
Мой опыт показывает, что они действительно случаются. Вот краткое изложение утечек памяти, которые я наблюдал за десять лет работы с Perl.
У меня были проблемы с утечками приложений pTk. Некоторые утечки, которые мне удалось доказать, были связаны с циклическими ссылками, которые возникали, когда Tk передает ссылки на окна. Я также видел утечки pTk, причину которых я никогда не мог отследить.
Я видел, как люди неправильно понимают , ослабляют
и случайно заканчивают круговыми ссылками.
Я видел непреднамеренные циклы, когда слишком много плохо продуманных объектов собирались вместе в спешке.
Однажды я обнаружил утечку памяти из-за модуля XS, который создавал большие и глубокие структуры данных.Мне никогда не удавалось получить воспроизводимый тестовый пример меньше, чем вся программа. Но когда я заменил модуль другим сериализатором, утечки исчезли. Итак, я знаю, что утечки исходили от XS.
Итак, по моему опыту, циклы являются основным источником утечек.
К счастью, есть модуль , который поможет их выследить.
Что касается того, являются ли большие глобальные структуры, которые никогда не очищаются, «утечками», я согласен с Брайаном. Они крякают как утечки (у нас постоянно растет использование памяти процессами из-за ошибки), так что это утечки. Даже в этом случае я не припомню, чтобы когда-либо видел эту конкретную проблему в дикой природе.
Основываясь на том, что я вижу на сайте Стоунхенджа, я предполагаю, что Брайан видит много нездорового кода от людей, которых он тренирует или для которых готовит чудеса исцеления. Таким образом, его выборочный набор намного больше и разнообразен, чем мой, но он имеет свою собственную предвзятость отбора.
Какая причина утечек наиболее распространена? Я не думаю, что мы когда-нибудь действительно узнаем. Но мы все можем согласиться с тем, что циклические ссылки и глобальные свалки данных являются анти-шаблонами, которые необходимо устранять, где это возможно, и обращаться с ними осторожно и осторожно в тех немногих случаях, когда они имеют смысл.
Если проблема в коде Perl, у вас может быть ссылка, которая указывает на себя или родительский узел.
Обычно он имеет форму объекта, который ссылается на родительский объект.
{ 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 :: weaken .
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). В конце они потребуют, чтобы вы запустили какой-нибудь метод / процедуру разрушения. Просто прочтите документацию :)