2

In C program I face transactions that require to have alot of memory chunks, I need to know if there is an algorithm or best practice teqnique used to handle all these malloc/free, I've used arrays to store these memory chunks but at some point the array itself gets full and reallocating the array is just more waste, what is the elegant way to handle this issue?

2 Answers2

2

The best algorithm in this case would be free list allocator + binary search tree.

You are asking one big memory chunk from system, and then allocating memory blocks of fixed size from this chunk. When chunk is full you are allocating another one, and link them into red-black or AVL binary search interval tree (otherwise searching for a chunk during free by iterating over the chunks list becomes a bottleneck)

And in the same time, multi-threading and thread synchronization becomes important. If you will simply use mutexes or trivial spin-locks, you will found you libc malloc works much faster than your custom memory allocator. Solution can be found in Hazard pointer, i.e. each thread have it's own memory allocator (or one allocator per CPU core). This will add another problem - when one thread allocates memory and another releasing it, this will require searching for the exact allocator during free function, and strick locking or lock-free data structures.

The best option - you can use jemalloc, tcmalloc or any another generic propose fast memory allocator to replace your libc (i.e. pdmalloc) default allocator completely or partially.

Victor Gubin
  • 2,374
  • 7
  • 18
2

If you need to track these various buffers due to the functionality you are delivering then you are going to need to have some kind of buffer management functionality which includes allocating memory for the buffer management.

However if you are worried about memory fragmentation, that is a different question.

I have seen a lot written about how using malloc() and free() leads to memory fragmentation along with various ways to reduce or eliminate this problem.

I suspect years ago, it may have been a problem. However these days I question whether most programmers can do as good a job at managing memory as the people who develop the run time of modern compilers. I am sure there are special applications but compiler runtime development is very much aware of memory fragmentation and there are a number of approaches they use to help mitigate the problem.

For instance here is an older article from 2010 describing a modern compiler runtime implements malloc() and free(). A look at how malloc works on the Mac

Here is a bit about the GNU allocator.

This article from Nov of 2004 from IBM describes some of the considerations for memory management, Inside memory management and provides what they call "code for a simplistic implementation of malloc and free to help demonstrate what is involved with managing memory." Notice that this is a simplistic example meant to illustrate some of the issues and is not a demonstration of current practice.

I did a quick console application with Visual Studio 2015 that called a C function in a C source file that interspersed malloc() and free() calls of various sizes. I ran this while watching the process in Windows Task Manager. Peak Working Set (Memory) maxed out at 34MB. Watching the Memory (Private Working Set) measurement, I saw it rise and fall as the program ran.

#include <malloc.h>
#include <stdio.h>

void funcAlloc(void)
{
    char *p[50000] = { 0 };
    int   i;

    for (i = 0; i < 50000; i+= 2) {
        p[i] = malloc(32 + (i % 1000));
    }

    for (i = 0; i < 50000; i += 2) {
        free(p[i]); p[i] = 0;
    }
    for (i = 1; i < 50000; i += 2) {
        p[i] = malloc(32 + (i % 1000));
    }
    for (i = 1; i < 50000; i += 2) {
        free(p[i]); p[i] = 0;
    }

    for (i = 0; i < 50000; i++) {
        p[i] = malloc(32 + (i % 1000));
    }

    for (i = 0; i < 50000; i += 3) {
        free(p[i]); p[i] = 0;
    }
    for (i = 1; i < 50000; i += 3) {
        free(p[i]); p[i] = 0;
    }
    for (i = 2; i < 50000; i += 3) {
        free(p[i]); p[i] = 0;
    }
}

void funcMain(void)
{
    for (int i = 0; i < 5000; i++) {
        funcAlloc();
    }
}

Probably the only consideration that a programmer should practice to help the memory allocator under some conditions of churning memory with malloc() and free() is to use a set of standard buffer sizes for varying lengths of data.

For example, if you are creating temporary buffers for several different data structs which are of varying sizes, use a standard buffer size of the largest struct for all of the buffers so that the memory manager is working with equal sized chunks of memory so it is able to more efficiently reuse chunks of free memory. I have seen some dynamic data structure functionality use this approach such as allocating a dynamic string with a minimum length of 32 characters or rounding up a buffer request to a multiple of four or eight.

Richard Chambers
  • 14,509
  • 3
  • 62
  • 86