Утечки памяти в программах на Perl::Журнал СА 5.2004
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

 Читать далее...

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

 Читать далее...

Разбор полетов  

Ошибок опыт трудный

Как часто мы легко повторяем, что не надо бояться совершать ошибки, мол,

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6142
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6854
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4137
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 2976
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3781
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3789
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6282
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3134
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3433
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7246
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10616
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12335
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 13966
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9098
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7052
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5361
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4593
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3400
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3127
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3378
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 2999
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Утечки памяти в программах на Perl

Архив номеров / 2004 / Выпуск №5 (18) / Утечки памяти в программах на Perl

Рубрика: Программирование /  Анализ данных

АЛЕКСЕЙ МИЧУРИН

Утечки памяти в программах на Perl

Все программисты знают, сколько неприятностей могут доставить утечки памяти, как сложно бывает локализовать и устранить их причины. И любой, кто работал с Perl, смог по достоинству оценить систему сборки мусора, которой оснащён интерпретатор этого языка. Она автоматически заботится об удалении данных, которые больше не понадобятся, и защищает таким образом от утечек памяти. У программиста может появиться некая иллюзия неограниченной мощи этого механизма. Но так ли он всемогущ?

Perl – прекрасный язык (и мы сегодня в этом ещё убедимся), позволяющий программисту весьма кратко выразить достаточно ёмкие мысли. Приведу пример программы, состоящей из одной строки и демонстрирующей утечку памяти в Perl:

# ОСТОРОЖНО! УТЕЧКА ПАМЯТИ!

while (1) { my ($a,$b); $a=$b; $b=$a }

Если вы решите испытать эту нехитрую программу, то приготовьтесь к тому, что события будут развиваться стремительно. На моей станции она «съедает» всю память за доли минуты.

В ней, как вы видите, реализован бесконечный цикл. В теле цикла созданы две локальные переменные $a и $b. Ожидается, что, как и положено локальным переменным, они будут уничтожаться каждый раз по окончании выполнения тела цикла, но этого не происходит. Почему?

Причина кроется в устройстве системы сборки мусора. Последняя просто ведёт учёт всех ссылок, сделанных на переменную. Если переменная вышла из области видимости, но ссылки на неё остались, то данные не удаляются (хотя имя переменной становится недоступным). Это обеспечивает работоспособность всех ссылок, но может ввести в заблуждение систему сборки мусора. Обратите внимание, ссылки не анализируются на предмет, будут они удалены в ближайшее время или не будут. Они подсчитываются все без исключения.

Иллюстрацией к сказанному может послужить абсолютно «бытовая» ситуация, встречающаяся сплошь и рядом:

my $ref;

{ my $var=1975; $ref=$var; }

# print $var # неверно, имя $var уже не доступно

print $$ref;

# будет напечатано "1975", данные не исчезли

Как видите, за пределами блока имя $var уже не доступно, но данные, ранее ассоциированные с этим именем, сохранились и будут в сохранности, пока не будет удалена последняя ссылка на них

В нашем однострочном примере переменные $a и $b содержат ссылки друг на друга. Несмотря на то, что их имена становятся не видны сразу по окончании выполнения блока, система сборки мусора не удаляет данные, ассоциированные с ними, неуклонно следуя своему алгоритму: данные считаются «мусором» только в том случае, если ссылок на них не осталось.

Может быть, кому-то покажется, что подобная ситуация попахивает надуманностью и встречается редко? Отнюдь. Например, у вас может быть массив структур[2], описывающих товары, и другой массив структур, описывающих магазины. Структуры, описывающие товары, могут содержать указатели на магазины, а структуры, описывающие магазины, могут ссылаться на элементы списка товаров. Вот мы и получили две конструкции, ссылающиеся друг на друга. Больше того, реляционные базы данных просто-таки сами подсказывают подобные решения.

Давайте разберёмся, как же решить проблему утилизации ненужных данных.

Наименее громоздким мне представляется следующий объект для исследований:

01: #!/usr/bin/perl -w

02:

03: # ОСТОРОЖНО! УТЕЧКА ПАМЯТИ!

04:

05: sub create_ring {

06:     my ($length)=@_;

07:     my @ring=({"value" => 0})x$length;

08:     for (my $i=0; $i<$length; $i++) {

09:         $ring[$i]{"next_ref"}=$ring[($i+1)%$length];

10:     }

11:     return @ring;

12: }

13:

14: while (1) { my @ring=create_ring(10) }

Процедура create_ring создаёт и возвращает массив хэшей, каждый из которых имеет два ключа. Под именем «value» хранится некая величина, под именем «next_ref» – ссылка на следующий элемент массива. Последний элемент несёт ссылку на первый элемент (номер ноль), зацикливая конструкцию.

Вызывая её в бесконечном цикле и следя за процессами в системе, легко убедиться, что в приведённой программе есть утечка памяти. Элементы списка, создаваемого create_ring, не удаляются по той же причине, что и $a и $b из первого примера: когда выполнение блока доходит до конца, остаются неудалённые ссылки на элементы списка (как вы помните, эти ссылки содержатся в хэшах, составляющих список, под именами «next_ref»).

Ситуация не изменится даже в том случае, если чуть изменить наш код (привожу только важные и изменённые строки):

...

03: # ОСТОРОЖНО! УТЕЧКА ПАМЯТИ!

...

11:     return $ring[0];

...

14: while (1) { my $ring=create_ring(10) }

Не поможет и такое изменение:

...

03: # ОСТОРОЖНО! УТЕЧКА ПАМЯТИ!

...

11:     return @ring;

...

14: while (1) { my $ring=create_ring(10) }

В двух последних примерах по окончании выполнения блока мы будем терять ссылку на элемент массива (в первом) или ссылку на сам массив (во втором), но данные массива, уже никому не доступные, будут бережно сохраняться системой сборки мусора.

Один из способов освободить память – разорвать этот круг (вернее, ring) вручную, добавив в конец блока соответствующую инструкцию:

...

14: while (1) {

15:     my $ring=create_ring(10);

16:     $ring->[0]{"next_ref"}=undef;

17: }

Одного разрыва вполне достаточно, чтобы система сборки мусора один за другим удалила все элементы структуры, но этот способ не выглядит изящно. Мы просто взяли на себя труд убрать мусор, но так и не заставили работать над этим интерпретатор Perl. Неужели всё так безнадёжно? Нет! В Perl есть механизмы, позволяющие «растолковать» ему все детали нашего замысла, я говорю об объектно-ориентированном подходе.

Первый пример будет основан на связывании переменной с классом

Я буду рассчитывать на то, что читатель знает об устройстве модулей Perl и знаком с механизмом связывания объектов с переменными. Вкратце скажу, что Perl позволяет ассоциировать переменную с классом с помощью оператора tie. После такого связывания переменная начинает жить по собственным законам, при любой манипуляции с ней вызываются соответствующие методы объекта, с которым она связана.

Вот код, реализующий обозначенный подход:

01: #!/usr/bin/perl -w

02:

03: package MyRing;

04:

05: sub create_ring {

06:     my ($length)=@_;

07:     my @ring=({"value" => 0})x$length;

08:     for (my $i=0; $i<$length; $i++) {

09:         $ring[$i]{"next_ref"}=$ring[($i+1)%$length];

10:     }

11:     return @ring;

12: }

13:

14: sub TIESCALAR {

15:     my ($class, $length)=@_;

16:     return bless create_ring($length), $class;

17: }

18:

19: sub FETCH { return $_[0]; }

20:

21: sub STORE { die "писать в MyRing нельзя. " }

22:

23: sub DESTROY {

24:     my ($self)=shift;

25:     $self->[0]{"next_ref"}=undef;

26: }

27:

28: package main;

29:

30: # демонстрация работоспособности

31: {

32:    my $a;

33:    {

34:       tie my $ring, "MyRing", 10;

35:       $ring->[1]{"value"}="test";

36:       $a=$ring;

37:    }

38:    # $ring больше не видна, но данные целы

39:    print $a->[0]{"next_ref"}{"value"}." ";

40:    # здесь вызывается DESTROY

41: }

42: # $ring=7; # это вызовет метод STORE

43:

44: while (1) { tie my $ring, "MyRing", 10; }

Для упрощения я не стал выделять модуль, описывающий класс, в отдельный файл. Связывать объект будем со скалярной переменной, это тоже, наверное, не лучшее решение: естественнее было бы выбрать массив, а если бы мы захотели сделать нашу разработку более масштабируемой и развиваемой, то логичнее было бы выбрать хэш. Но для связывания таких «сложных» конструкций нам пришлось бы реализовать множество методов, для скаляра же достаточно четырёх, эта компактность и наглядность лучше всего подойдёт для обсуждения в статье.

Итак, в строках 3-27 описан модуль MyRing. Он содержит уже знакомую нам функцию создания и инициализации массива хэшей (create_ring) и методы, необходимые для обеспечения работоспособности связанной переменной.

Метод TIESCALAR (строка 14) вызывается в момент связывания, он получает от оператора tie список дополнительных параметров, в котором у нас будет только одна величина – длина требуемого массива. Получив параметры, метод создаёт наш массив (вызов create_ring($length)), ассоциирует его с классом (вызов bless) и возвращает «новоиспечённый» объект.

Метод FETCH (строка 19) вызывается, когда выполняется чтение значения связанной переменной. Ему передаётся один параметр – сам объект. Наша реализация FETCH не делает ничего, просто возвращает то, что получила без изменений.

Метод STORE (строка 21) отвечает за запись в переменную. Я не придумал, что в него написать, он просто выдаёт грозное сообщение и «убивает» программу.

И наконец, метод DESTROY (строка 23) – это то, что нам нужно. Он вызывается автоматически всегда, когда переменная выходит за область видимости или становится недоступна по другим причинам (например, когда программа завершается). Наш метод DESTROY разрывает кольцевую структуру (строка 25 выглядит знакомо, не правда ли?), позволяя системе сборки мусора довести свою работу до конца.

Со строки 28 начинается основная программа, где мы сейчас и воспользуемся нашим классом MyRing.

В строках 30-42 приведён небольшой фрагмент кода, демонстрирующий работоспособность нашей «кухни».

В строке 32 мы создаём локальную переменную $a, которая будет существовать только в пределах блока, находящегося в строках 31-41. Во вложенном блоке (строки 33-37) мы создаём локальную переменную $ring, которую сразу же связываем с классом MyRing (строка 35). Для проверки корректности структуры $ring записываем строку «test» в её первый узел (строка 36). Для проверки корректности сборки мусора сохраняем копию $ring в $a. Что произойдёт, когда вложенный блок закончится? Оказывается, метод DESTROY не будет вызван! Это и понятно, мы сохранили указатель на нашу структуру (помните? $ring является указателем на массив) в переменной $a, а она всё ещё существует. Переменной $ring не стало, но пропало только имя, данные целы. Мы убеждаемся в этом в строке 39. Здесь же мы убеждаемся в корректности структуры $ring (теперь $a), получив доступ к первому узлу как к узлу, следующему после нулевого. А метод DESTROY будет вызван только тогда, когда исчезнет переменная $a. Таким образом, всё работает правильно.

Обратите внимание и на то, что при выполнении манипуляций с $ring (строка 35) вызывается метод FETCH, отвечающий за чтение переменной, а не STORE, ответственный за запись. Действительно, чтобы проделать операцию, описанную в строке 35, нам пришлось считать значение указателя $ring, а не записать его.

А вот строка 42 не случайно закомментирована. Операция присвоения автоматически вызывала бы метод STORE, а он бы аварийно остановил выполнение программы.

Наконец в строке 44 реализован всё тот же бесконечный цикл. На каждом проходе в его теле вновь создаётся локальная переменная $ring, но теперь мы связываем её с классом MyRing. Благодаря этому по завершении блока Perl передаёт управление методу DESTROY, который корректно освобождает память.

Таким образом, нам больше не приходится удалять кольцевую структуру «руками». Мы научили систему сборки мусора обращаться с нашими данными, и Perl теперь сам (...почти) защищает нас от утечек памяти.

Я продемонстрировал предельно мудрёную схему, можно ли обойтись меньшей кровью? Конечно! В Perl грань объектно-ориентированного программирования весьма размыта. Взгляните на следующий код, в нём нет почти ничего, напоминающего о его объектно-ориентированности:

01: #!/usr/bin/perl -w

02:

03: package MyRing;

04:

05: sub main::create_ring {

06:     my ($length)=@_;

07:     my @ring=({"value" => 0})x$length;

08:     for (my $i=0; $i<$length; $i++) {

09:         $ring[$i]{"next_ref"}=$ring[($i+1)%$length];

10:     }

11:     return bless @ring, __PACKAGE__;

12: }

13:

14: sub DESTROY {

15:     my ($self)=@_;

16:     $self->[0]{"next_ref"}=undef;

17: }

18:

19: package main;

20:

21: while (1) { my $ring=create_ring(10); }

Обратите внимание, вызов create_ring в основной программе вообще ни чем не выдаёт объектно-ориентированную природу переменной $ring. Правда, саму create_ring нам пришлось чуть доработать, сделав её каким-никаким, а всё-таки конструктором (вызов bless в строке 11), и «экспортировав»[5] её в модуль main (имя main::create_ring в строке 5).

Метод DESTROY остался без изменений. Вызываться он будет в тех же случаях и обеспечит такие же функции, как и в предыдущем примере.

Я бы не стал говорить, что какой-то из двух приведённых методов решения проблемы лучше, а другой хуже. У каждого есть свои преимущества и недостатки. Первый длинноват, но код последователен и легко читается. Второй компактен, но менее универсален и его логику понять сложнее (вернее, сложнее разглядеть в нём объектно-ориентированный подход). Первый код работает чуть медленнее, поскольку переменная связана. Второй работает чуть быстрее, но допускает выполнение бессмысленных действий, например:

 $ring=7;

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

Гораздо интереснее задать другой вопрос: насколько надёжна наша защита от утечек памяти? Не будем наивны, она тоже небезупречна и болеет теми же болезнями, что и однострочная программа из самого первого листинга.

Не будем далеко ходить за примером и чуть модифицируем тело цикла из последнего листинга:

...

21: # ОСТОРОЖНО! УТЕЧКА ПАМЯТИ!

22: while (1) {

23:     my $ring=create_ring(10);

24:     my $a;

25:     $ring->[0]{"value"}=$a;

26:     $a=$ring;

27: }

Утечка произошла по той же самой причине, что и обычно. Когда выполнение блока подходит к концу и наступает время собрать мусор, система сборки обнаруживает, что на кольцевую структуру ссылаются две переменных: $ring и $a. Вторую уничтожить не получается, потому что на неё имеется ссылка. Где эта ссылка находится, система сборки мусора уже не разбирается, но мы-то знаем, что она содержится в недрах структуры $ring. Круг замкнулся, система сборки мусора снова не заметила наш мусор.

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

Одним словом, при создании сложных структур данных всегда надо помнить, что Perl не сможет угадать ваши мысли, и чётко представлять, каким образом он интерпретирует ваши команды.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru