Continuous realloc()

A colleague raised a question that realloc does better than free + malloc because allocated memory block is never being actually shrunk and reallocations to smaller size following by reallocations to larger (but still not larger than one of the previous) do not lead to heap locks and actual underlying heap memory block reallocations.

While this is technically possible within the contract declared by the API, it does not seem to be likely that the runtime will stay reluctant to release unused memory. And what is also highly probable, that heap managers implement advanced tricks to decrease impact of heap locks while doing memory allocations. In the same time, realloc must move the payload data in full to the new memory location in case the reallocated block is moved itself. If this is not required and the block is large, there is an unwanted performance impact to take place.

The details of the API operation are likely to be described somewhere, and another related question might be how to do the measurement programmatically and get a hint of what is going on internally.

PSAPI offers GetProcessMemoryInfo function to obtain process memory metrics, and returned PROCESS_MEMORY_COUNTERS_EX::PrivateUsage field is showing private memory in use. malloc allocated memory is eventually mapped onto process private memory, so the API is good for seeing approximate (because of fragmentation, process memory use is always higher than sum of actually allocated block sizes) memory usage.

If we are going to allocate 1 MB blocks, then reallocate to 1 KB, then allocate additional memory, observing the process private memory usage we will be able to see if realloc does release unused memory.

The code is as simple as:

VOID* ppvItemsA[256];
static const SIZE_T g_nSizeA1 = 1 << 20; // 1 MB
_tprintf(_T("Allocating %d MB\n"), (_countof(ppvItemsA) * g_nSizeA1) >> 20);
for(SIZE_T nIndex = 0; nIndex < _countof(ppvItemsA); nIndex++)
    ppvItemsA[nIndex] = malloc(g_nSizeA1);
static const SIZE_T g_nSizeA2 = 4 << 10; // 4 KB
_tprintf(_T("Reallocating to %d MB\n"), (_countof(ppvItemsA) * g_nSizeA2) >> 20);
for(SIZE_T nIndex = 0; nIndex < _countof(ppvItemsA); nIndex++)
    ppvItemsA[nIndex] = realloc(ppvItemsA[nIndex], g_nSizeA2);
VOID* ppvItemsB[256];
static const SIZE_T g_nSizeB1 = 16 << 10; // 16 MB
_tprintf(_T("Allocating %d MB more\n"), (_countof(ppvItemsB) * g_nSizeB1) >> 20);
for(SIZE_T nIndex = 0; nIndex < _countof(ppvItemsB); nIndex++)
    ppvItemsB[nIndex] = malloc(g_nSizeB1);

And the output is:

PrivateUsage: 0 MB
Allocating 256 MB
PrivateUsage: 258 MB
Reallocating to 1 MB
PrivateUsage: 3 MB // <<--- (*)
Allocating 4 MB more
PrivateUsage: 7 MB

Which shows that reallocating to smaller size involves freeing unused space.

Download links:

Leave a Reply