2010-02-08 3 views
13

Я преследую пару потенциальных утечек памяти в базе кода Perl, и я хотел бы узнать о распространенных ошибках в отношении памяти (mis-) управления в Perl.Общие шаблоны утечки памяти/ссылки на Perl?

Каковы общие схемы утечки, которые вы наблюдали в коде Perl?

+0

Как вы поймали эти утечки памяти? Вы пробовали Perl Profiler (http://www.perlmonks.org/?node_id=472366)? –

ответ

18

Циркулярные ссылки на сегодняшний день являются наиболее распространенными канонической причиной утечек.

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 также может вернуться в бассейн.

Обновление: brain d foy спросил, есть ли у меня какие-либо данные для подтверждения моего утверждения о том, что циркулярные ссылки являются общими. Нет, у меня нет статистики, чтобы показать, что циркулярные ссылки являются общими. Они чаще всего обсуждаются и являются наиболее документированной формой утечек памяти perl.

Мое впечатление, что они происходят. Вот краткое описание утечек памяти, которые я видел более десяти лет работы с Perl.

У меня были проблемы с приложениями pTk, развивающими утечки. Некоторые утечки, которые я смог доказать, были вызваны круговыми ссылками, которые возникли, когда Tk передает ссылки на окна. Я также видел pTk утечки, причиной которых я никогда не мог отследить.

Я видел, как люди неправильно понимают weaken и заканчивают круговыми ссылками случайно.

Я видел непреднамеренные циклы, когда слишком много плохо продуманных объектов бросаются вместе в спешке.

Однажды я обнаружил утечки памяти, которые исходили от модуля XS, который создавал большие, глубокие структуры данных. Я никогда не мог получить воспроизводимый тестовый пример, который был меньше, чем вся программа. Но когда я заменил модуль другим сериализатором, утечки исчезли. Поэтому я знаю, что эти утечки исходили от XS.

Итак, по моему опыту, циклы являются основным источником утечек.

К счастью, there is a module, чтобы помочь отследить их.

Что касается больших глобальных структур, которые никогда не очищаются, это «утечки», я согласен с Брайаном. Они взлетают подобно утечкам (у нас постоянно растет использование памяти процесса из-за ошибки), поэтому они являются утечками. Тем не менее, я не помню, чтобы когда-либо видел эту проблему в дикой природе.

Основываясь на том, что я вижу на сайте Стоунхенджа, я предполагаю, что Брайан видит много больного кода от людей, которых он обучает или претворяет целебные чудеса. Таким образом, его набор образцов легко намного больше и разнообразнее, чем у меня, но у него есть свой собственный выбор.

Какая причина утечек наиболее распространена? Я не думаю, что мы когда-нибудь узнаем. Но мы все можем согласиться с тем, что циркулярные ссылки и глобальные свалки данных являются анти-шаблонами, которые необходимо устранить там, где это возможно, и обрабатывать с осторожностью и осторожностью в тех немногих случаях, когда они имеют смысл.

+2

Есть ли что-нибудь, что поддерживает круговые ссылки как наиболее распространенную форму? Я почти никогда не вижу проблемы, но я вижу людей с глобальными хэшами, которые они никогда не устраняют. –

+4

@brian d foy: Строго говоря, это не утечка памяти, а просто чрезмерное использование памяти. Это не память * утечка *, пока программа * не сможет * освободить память. – cjm

+1

Большинство людей не заботятся о том, почему их память постоянно растет медленно (или не так медленно). :) –

5

У меня были проблемы с XS в прошлом, как мои собственные ручные вещи, так и модули CPAN, где память просочилась из кода C, если она не управляется должным образом. Мне никогда не удавалось проследить утечки; проект был в сжатые сроки и имел фиксированный срок службы, поэтому я опубликовал этот вопрос с ежедневной перезагрузкой cron. cron действительно замечательно.

9

Если проблема в коде 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; 
    } 
} 

Я бы рекомендовал отказаться от ссылки на родительский объект, от ребенка, если это вообще возможно.

+2

Стоит отметить, что с объектами вы также можете использовать деструктор для разрыва любых круговых ссылок. У вашего класса Child может быть 'sub DESTROY {$ _ [0] -> {parent} = undef; } 'или даже' sub DESTROY {$ _ [0] = undef; } ', и нет необходимости в слабых ссылках. Слабые ссылки - лучший способ справиться с ситуациями, они делают правильное поведение автоматическим. Также стоит отметить, что у Moose есть автоматическое ослабление ссылок для атрибутов: http://search.cpan.org/dist/Moose/lib/Moose/Manual/Attributes.pod#Weak_references Это еще один способ, которым Moose делает OO Perl лучше. – daotoad

+4

@daotoad: вы не можете использовать деструктор для разрыва круговых ссылок, потому что деструктор не вызывается, пока счетчик ссылок не упадет до нуля. Если деструктор вызван, у вас нет проблемы с круговой ссылкой. Если у вас есть циклические ссылки, вам нужно либо использовать слабые ссылки, либо вручную вызвать метод деструктора, когда вы закончите с объектом (который подвержен ошибкам). – cjm

+1

@cjm, вы правы. Прошло много времени. «ослабить» - это действительно путь. Вы должны создать объект-контейнер для хранения циклической ссылки и позволить деструктору контейнера разбить цикл. Другой вариант - иметь явный вызов метода, который очищает виджет и прерывает любые циклы. Например, метод Perl Tk 'Widget :: destroy()'. – daotoad

2

Некоторые модули из CPAN используют круговые ссылки для выполнения своей работы, например. HTML::TreeBuilder (который представляет дерево HTML). Они потребуют от вас запускать какой-либо метод/процедуру уничтожения в конце. Просто прочитайте документы :)

Смежные вопросы