Общие шаблоны утечки памяти/ссылки Perl?

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

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

Ответы

Ответ 1

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

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, я предполагаю, что Брайан видит много больного кода у людей, которых он тренирует или претворяет целебные чудеса. Таким образом, его набор образцов легко намного больше и разнообразнее, чем у меня, но у него есть собственный выбор смещения.

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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