Análise e correção de vazamentos de memória

Wallace Reis
Publicado em 01/01/2010

Análise e correção de vazamentos de memória

Um vazamento de memória ocorre quando um programa aloca memória e não é capaz de liberar esta de volta ao sistema operational, ocasionando diminuição de desempenho da aplicação (ou até mesmo do sistema computacional como um todo). Muitas pessoas acreditam que qualquer crescimento inesperado do uso de memória seja um sintoma de vazamento, o que não é sempre uma verdade.

Perl usa contagem de referências para gerênciar o uso de memória, isto significa que quando se cria uma estrutura circular causa-se vazamento, como também um XS prendendo uma referência. Deste modo, não é tão difícil saber quando e em qual ordem a desalocação vai acontecer, e então pode-se ser pró-ativo (ou paranóico :) chamando undef($object) da mesma forma que os programadores em C, free(object).

Vazamentos de memória podem não ser fácilmente detectáveis ou nem mesmo sérios em alguns casos, como por exemplo em scripts simples e/ou de curta duração. Em outros casos, pode-se fazer uso de alguns dos vários módulos existentes no CPAN como:

Devel::Size

Investiga o uso de memória de variáveis.

Devel::Cycle

Útil para encontrar referências circulares se você sabe ou suspeita quais estruturas são prováveis de se ter ciclos.

Devel::LeakTrace::Fast

Indica onde estão as variáveis com vazamento.

Test::LeakTrace e Test::LeakTrace::Script

Fornecem várias funções para monitoramento de vazamentos de memória.

Devel::Events::Objects

Permite o monitoramento de objetos ao framework Devel::Events, facilitando a construção de ferramentas de relatório de ciclos.

Devel::Peek

Pode ser usado para encontrar ciclos em objetos existentes em códigos que fazem programação XS.

Test::Valgrind

Valgrind é um framework para construção de ferramentas de análise dinâmica que contém vários utilitários prontos para detecção de falhas no gerenciamento de memória e threading. Muito útil se há suspeita de vazamento de memória em código XS.

Ciclos são contornáveis na prática. Porém, por vezes faz-se necessário o uso de referências de retorno como uma simplicação na modelagem de relacionamentos, neste caso, pode-se usar a função weaken do Scalar::Util para assim marcar uma referência fraca permitindo ao gerenciador coletar o objeto como lixo e liberar a memória.

Exemplo:

	1. my $parent = { name => 'Foo' };
	2. my $child = {
	       name => 'Foo Jr',
	       parent => $parent,
           };
	3. $parent->{'children'} = [$child];

Usando a função find_cycle do Devel::Cycle

	4. find_cycle($parent);

teríamos como saída

	Cycle (1):

		$A->{'children'} => \@B
		$B->[0] => \%C
		$C->{'parent'} => \%A

onde $A é $parent e $C é $child. Assim, obtém-se o ponto chave da circularidade, o qual neste exemplo fica óbvio desde o ínicio onde seria, pois é sua modelagem é bem simples e a profundidade dos relacionamentos é pouca, facilitando ao desenvolvedor a correção.

	1. use Scalar::Util 'weaken';
	2. my $parent = { name => 'Foo' };
	3. weaken($parent);
	4. my $child = {
	       name => 'Foo Jr',
	       parent => $parent,
           };
	5. $parent->{'children'} = [$child];

Com Moose, basta declarar o atributo como weak_ref => 1.

	package Child;
	use Moose;

	has parent => (
	    isa => 'Parent', is => 'rw',
	    weak_ref => 1, required => 1,
	);

	1;

Se você tem uma aplicação web desenvolvida em Catalyst, existem dois componentes que permitem geração de relatórios sobre vazamentos de forma rápida e indolor.

Catalyst::Plugin::LeakTracker

Reliza o monitoramento de objetos que causaram vazamentos no durante um ciclo de requisição da aplicação.

Catalyst::Controller::LeakTracker

Gera relatórios sobre os vazamentos de memória encontrados pelo Catalyst::Plugin::LeakTracker.

Tudo o que se precisa fazer é carregar o plugin na classe da aplicação

	package MyApp;

        use Catalyst qw(LeakTracker);

e criar um controller (por exemplo Leaks)

	package MyApp::Controller::Leaks;

	use Moose;
	BEGIN { extends 'Catalyst::Controller::LeakTracker' }

	1;

desta forma, acessando http://localhost:3000/list_requests obtém-se um relatório semelhante a este:

/list_requests

Os resultados demonstrados são por requisição, incluem as ações/URIs que tiveram vazamento e quanto de memória cada uma delas custou, etc. Acessando cada registro pode-se ver um relatório mais detalhado sobre quais classes de objetos estão envolvidas nos vazamentos:

/request/1

refinando novamente, tem-se o stacktrace da origem e a lista de ciclos capturados:

/leak/1/13

ADVERTÊNCIA

Sabe-se que podem ocorrer falsos positivos nos resultados, pois objetos cacheados ou singletons são considerados tecnicamente como vazamentos, assim não há problema contanto que cada um não se repita a cada requisição. Este cenário é tipicamente encontrado em grandes aplicações em suas primeiras requisições.

AUTHOR

Wallace Reis Wallace Reis <wreis@cpan.org>

blog comments powered by Disqus