Thursday, February 24, 2011

Memory management in Windows applications

Contrary to what a lot of people thinks, an application written in C/C++ does not necessarily have to be faster than one made in Java or C#, because these platforms, since they were already designed to be *slower*, usually already has built-in the best methods for most commons application tasks. Among these methods, one of the most important to highlight is the memory management through heap space.

Versão em português desse artigo: Gerenciamento de memória em aplicações Windows.

Intensive calls for memory allocation that are made directly to operating system may be considerably slower in multitasking systems as you are competing in this call with the rest of applications like in a queue, where the system tries to find contiguous blocks available for each application that need to store some information, one at a time. This is not a humanly significant time, but it can make the difference in an application that needs to receive and process a large message volume from the network, for example.

Low-fragmentation heap, or simply LFH, is an allocation policy available in Win32API that uses blocks with predetermined sizes, where it always seeks the smaller block that fits the size asked with HeapAlloc method. All Windows applications has their own heap space that try to absorb the allocation load, and this space is also able to be configured to use LFH instead of the default allocator. Even calls to malloc/free in applications made with Visual C++ uses HeapAlloc, indeed malloc/free exists only for ANSI C compatibility.

There are another known methods to optimize memory, like the Circular Buffer available at Boost library, that is a not an allocation method, but a kind of a faster algorithmic to process queues, however it works better when all blocks will have the same size and will be read in sequence, moreover, as its name suggests, the circular buffer starts to overwrite old data when running out of space. We can also point the multiplatform allocation managers like nedmalloc, for example, which has a very simple implementation even for already made applications.

Look the following table that compares the performance between allocation methods using the same test application that comes with nedmalloc:


On this chart you can see that both the process heap, by calling malloc/free, or the private heap calls using HeapAlloc/HeapFree, have similar performance with or without LFH activated. These data also shows that, despite the nedmalloc achieve considerable results regarding to default allocator, it is overcome by far by LFH. Remembering that this test just simulates an artificial condition of forced stress that may not represent the truth for another applications, it's always recommended specific performance tests in applications with a critical response time.

To activate the LFH algorithmic for default process heap you can utilize the following code snippet at the beginning of the program.

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

But the history does not end here. In a multitasking application the threads can also compete among themselves for the resources of the default heap. In such cases it's interesting to separate the memory into independent pieces known as private heaps what, through HeapCreate method, creates allocation blocks independent of application and system memory. Besides being able to use the LFH, these blocks can be configured with an initial size, maximum size or even leave it to grow automatically as space demand. In this way each message queue, each job or any other kind of data may have it's own separated memory area, like a disk partition, to control their access and concurrency. This technique promotes a greater relief on allocation wait time in multitasking environment.

Following a model of C++ class that can be used to implement 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);
}

This class can be used like in the following example:

//create a 10MB heap
HeapSpace* MessageHeap = new HeapSpace(10485760);

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

//free space
MessageHeap->free(HeapAllocated);

//delete entire heap
delete MessageHeap;

No comments:

Post a Comment