Saturday, January 1, 2011

Gerenciamento de memória em aplicações Windows

Ao contrário do que muitos pensam, um aplicativo feito em C/C++ não tem obrigatoriamente que ser mais rápido que outro feito em Java ou C#, pois essas plataformas, por já serem concebidas para serem mais *lentas*, normalmente já trazem embutidos os melhores métodos para a maioria das tarefas comuns dos aplicativos. Entre esses métodos, um dos mais importantes a destacar é o gerenciamento de memória através de heap space.

English version of this article: Memory management in Windows applications.

Chamadas intensivas de alocação de memória feitas diretamente para o sistema operacional podem ser considerávelmente lentas a medida que em sistemas multitarefa você concorre por essas chamadas com o restante dos aplicativos como em uma fila de espera, onde o sistema tenta localizar blocos contínuos disponíveis para cada aplicativo que precisa armazenar alguma informação, um de cada vez. Não é um tempo humanamente considerável, porém pode fazer toda diferença em um aplicativo que precisa receber e processar um grande volume de mensagens da rede, por exemplo.

Low-fragmentation heap, ou simplesmente LFH, é uma política de alocação de memória disponível na Win32API que se utiliza de blocos de tamanhos pré-determinados, onde ele sempre procura o menor bloco possível que seja grande o suficiente para comportar o tamanho solicitado no método HeapAlloc. Toda aplicação Windows possui um espaço de memória heap próprio que tenta absorver a carga de alocações, e esse espaço de memória também pode ser configurado para utilizar LFH ao invés da política padrão. Mesmo chamadas para malloc/free em aplicativos feitos com o Visual C++ utilizam HeapAlloc, de fato malloc/free existem somente para compatibilidade com o ANSI C.

Existem outros médotos conhecidos para otimizar memória, como o Circular Buffer disponível na biblioteca Boost, que não é um método de alocação, mas uma espécie de algoritimo bastante rápido de processamento de filas, porém esse método funciona melhor quando os blocos a serem alocados são do mesmo tamanho e lidos em sequência, além de que, como o próprio nome sugere, o buffer circular começa a sobrepor dados antigos quando os espaços se esgotam. Também podemos citar os gerenciadores de alocação multiplataforma como o nedmalloc, por exemplo, que tem uma implementação bastante simples até mesmo para aplicativos já prontos.

Veja a seguir uma comparação de performance entre os métodos de alocação utilizando o próprio aplicativo de teste que acompanha o nedmalloc:


Nesse gráfico é possível observar que a tanto a memória heap do processo, através de chamadas malloc/free, como as chamadas de private heap utilizando HeapAlloc/HeapFree, apresentam performance semelhantes estando com ou sem LFH ativado. Os dados também mostram que, apesar do nedmalloc conseguir resultado considerável em relação a alocação padrão, ele é superado com folga pelo LFH. Lembrando que esse teste simula uma condição artificial de estresse forçado que pode não representar a realidade de outros aplicativos, é sempre recomendado testes específicos de performance em aplicativos onde o tempo de resposta for crítico.

Para ativar o algoritimo LFH no espaço de memória padrão do seu aplicativo pode se utilizar o seguinte trecho de código no início do programa:

intptr_t hCrtHeap = _get_heap_handle();
ULONG ulEnableLFH = 2;
HeapSetInformation((PVOID)hCrtHeap, HeapCompatibilityInformation, &ulEnableLFH, sizeof(ulEnableLFH));

Mas a história não acaba por aí. Em uma aplicação multitarefa as threads também podem concorrer entre sí para ter acesso aos recursos da memória heap padrão. Nesses casos é interessante separar a memória em partes independentes conhecidas como private heaps o que, através do método HeapCreate, cria blocos de alocação independentes do restante da memória do aplicativo e do sistema. Além de poder utilizar o LFH, esses blocos podem ser configurados com tamanho inicial, tamanho máximo ou até mesmo deixar para que ela aumente o espaço automaticamente conforme a demanda. Dessa forma cada fila de mensagens, cada tarefa ou qualquer outro tipo de organização pode ter seu próprio espaço de memória independente, como em um particionamento de disco, a fim de controlar o seu acesso e a sua concorrência. Essa técnica promove um grande alívio na espera por alocação em ambientes multitarefa.

A seguir um modelo de classe C++ que pode ser usada para facilitar o trabalho com private heaps:

#include <windows.h>

class HeapSpace
{
public:
    HeapSpace(void);
    HeapSpace(unsigned long InitialSize);
    HeapSpace(unsigned long InitialSize, unsigned long MaximumSize);
    ~HeapSpace(void);

private:
    HANDLE HeapHandler;

private:
    void initialize(unsigned long InitialSize, unsigned long MaximumSize);

public:
    void* alloc(unsigned long Size);
public:
    void* reAlloc(void* MemoryPointer, unsigned long NewSize);
public:
    void free(void* MemoryPointer);
};

HeapSpace::HeapSpace(void)
{
    this->initialize(0, 0);
}

HeapSpace::HeapSpace(unsigned long InitialSize)
{
    this->initialize(InitialSize, 0);
}

HeapSpace::HeapSpace(unsigned long InitialSize, unsigned long MaximumSize)
{
    this->initialize(InitialSize, MaximumSize);
}

HeapSpace::~HeapSpace(void)
{
    HeapDestroy(this->HeapHandler);
}

void HeapSpace::initialize(unsigned long InitialSize, unsigned long MaximumSize)
{
    //initialize the heap
    this->HeapHandler = HeapCreate(0, InitialSize, MaximumSize);

    //set low-fragmentation algorithmic
    ULONG  HeapFragValue = 2;
    HeapSetInformation(this->HeapHandler, HeapCompatibilityInformation, &HeapFragValue, sizeof(HeapFragValue));
}

void* HeapSpace::alloc(unsigned long Size)
{
    return HeapAlloc(this->HeapHandler, 0, Size);
}

void* HeapSpace::reAlloc(void* MemoryPointer, unsigned long NewSize)
{
    return HeapReAlloc(this->HeapHandler, 0, MemoryPointer, NewSize);
}

void HeapSpace::free(void* MemoryPointer)
{
    HeapFree(this->HeapHandler, 0, MemoryPointer);
}

Essa classe pode ser utilizada como no exemplo a seguir:

//cria heap com 10MB
HeapSpace* MessageHeap = new HeapSpace(10485760);

//aloca 256 bytes
void* HeapAllocated = MessageHeap->alloc(256);

//libera espaço alocado
MessageHeap->free(HeapAllocated);

//apaga heap inteira
delete MessageHeap;

No comments:

Post a Comment