Com frequência vejo pessoas tentando medir o consumo de memória de um programa feito em Delphi usando o Gerenciador de Tarefas do Windows. Por motivos técnicos, essa não é a melhor forma de medir o consumo de memória do programa.
A grosso modo, o Gerenciador de Tarefas mede o máximo de memória seu programa alocou, não o quanto ele está usando; para piorar a memória que é liberada de volta para o Windows mas que ainda não foi utilizada por outro aplicativo é contabilizada nesse total pois essa memória disponível é considerada pelo Windows como disponível para uso pelo seu programa, embora ela possa ser usada por qualquer programa.
Gerenciador de Memória
Um dos motivos que a informação do Gerenciador de Tarefas estar errada é que um programa em Delphi aloca indiretamente memória do Windows, através de um gerenciador de memória do runtime do Delphi.
Quando o programa solicita memória para o gerenciador, ele aloca blocos de memória para o programa. Os blocos tem tamanho fixo de modo que para cada pedido de memória o gerenciador aloca a quantidade necessária de blocos contínuos para satisfazer o pedido do programa. Logo, um bloco é a menor quantidade de bytes que o gerenciador aloca. Por exemplo, imagine que cada bloco tem 32 bytes; se o programa pede 4 bytes, o gerenciador irá alocar um bloco; se o programa pedir 40 bytes, o gerenciador irá alocar dois blocos.
Pode parecer desperdício, mas isso é feito para evitar a fragmentação da memória. A fragmentação ocorre quando, apesar de haver memória livre para atender a um pedido de memória, não existem blocos contínuos suficientes para atender à solicitação. Caso o gerenciador fique sem blocos disponíveis para atender uma solicitação do programa, ele irá solicitar mais memória ao Windows.
Quando o programa devolve memória para o gerenciador, ele não devolve totalmente ao Windows a memória que você alocou porque ele imagina que o programa vai requisitar novamente mais memória. Isso é feito para otimizar os tempos de alocação de memória.
Vamos imaginar que inicialmente o programa alocou 100 blocos de memória do gerenciador. O programa cria algum form e precisa de mais 20 blocos de memória; o gerenciador irá providenciar 20 blocos de memória do Windows para poder atender a solicitação; nesse momento, o programa tem alocado 120 blocos de memória.
Quando o form é destruído e a memória devolvida ao gerenciador, ele irá marcar os 20 blocos de memória que foram usados pelo form como sendo disponíveis. O programa continua usando 120 blocos. Imagine que o programa crie outro form que precise de 10 blocos; como tem 20 disponíveis, o gerenciador não pede memória para o Windows, ele usa 10 dos 20 disponíveis.
Delphi 2006 e FastMM
Até o Delphi 2005, todas as versões do Delphi utilizaram o mesmo gerenciador de memória (eventualmente, com pequenas alterações). Mas o modo como é implementado o gerenciador de memória sempre permitiu o surgimento de ferramentas de terceiros para depurar a alocação de memória (localizar vazamentos/leaks, sobrescrita, etc) e até mesmo gerenciadores de memória alternativos.
Um desses gerenciadores de memória alternativos é o FastMM. Esse gerenciador é um dos mais rápidos e baseia-se na idéia de que um programa costuma alocar com mais frequencia pequenas quantidades de memória do que gransdes quantidades. Partindo dessa idéia, o FastMM usa três diferentes estratégias de gerenciamento de memória, dependendo da quantidade de memória que o programa pede. O resultado é que programas que criam/destroem muitos objetos tem desempenho melhor com o FastMM.
A Borland reconheceu a qualidade do FastMM e adotou o FastMM como gerenciador padrão de memória do Delphi 2006. Para quem não tem o Delphi 2006 mas gostaria de experimentar, visite a página do FastMM no SourceForge.net (baixe a versão 4.x mais recente, as versões antigas 2.x e 3.x estão lá apenas para quem mantem programas que usam essas versões)
Medir a memória utilizada
Para quem usa Delphi 2005 ou anterior é simples saber quantos blocos o gerenciador de memória pegou do Windows e quanto de memória seu programa está consumindo através do gerenciador de memória:
1 var
2 Blocos: Integer;
3 Bytes: Integer;
4 ...
5 Blocos := AllocMemCount;
6 Bytes := AllocMemSize;
Para quem usa Delphi 2006 ou usa o FastMM ao invés do gerenciador de memória padrão, o cálculo é um pouco mais trabalhoso:
1 var
2 Estado: TMemoryManagerState;
3 Blocos: Integer;
4 Bytes: Integer;
5 I: Integer;
6 ...
7 Blocos := 0;
8 Bytes := 0;
9
10 GetMemoryManagerState(Estado);
11
12 for I := 0 to High(Estado.SmallBlockTypeStates) do begin
13
14 Inc(Blocos, Estado.SmallBlockTypeStates[I].AllocatedBlockCount);
15 Inc(Bytes, Estado.SmallBlockTypeStates[I].AllocatedBlockCount
16 * Estado.SmallBlockTypeStates[I].UseableBlockSize);
17 end;
18
19 Inc(Blocos, Estado.AllocatedMediumBlockCount);
20 Inc(Bytes, Estado.TotalAllocatedMediumBlockSize);
21
22 Inc(Blocos, Estado.AllocatedLargeBlockCount);
23 Inc(Bytes, Estado.TotalAllocatedLargeBlockSize);
24 ...
Quem estiver usando o FastMM, deve incluir no uses da unit que contem esse código a unit FastMM4 (ou FastMM3, se for o caso).
Uma diferença do FastMM para o gerenciador de memória usado até o Delphi 2005 é que o FastMM tem diferentes estratégias de gerenciamento da memória conforme o tamanho do bloco de memória a ser alocado. Por isso, o código para calcular o consumo de memória é um pouco mais complicado.
Em ambos os casos, Blocos é o total de blocos de memória alocados pelo gerenciador e Bytes é a quantidade de memória que o programa está usando.
Links relacionados