Общие образцы утечки памяти / эталона Perl? - PullRequest
14 голосов
/ 08 февраля 2010

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

Какие распространенные шаблоны утечек вы наблюдали в коде Perl?

Ответы [ 4 ]

19 голосов
/ 08 февраля 2010

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

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, причину которых я никогда не мог отследить.

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

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

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

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

К счастью, есть модуль , который поможет отследить их.

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

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

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

9 голосов
/ 08 февраля 2010

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

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

5 голосов
/ 08 февраля 2010

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

2 голосов
/ 09 февраля 2010

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

...