Creating a Custom Memory Pool Allocator in C for High-performance Applications

In high-performance applications, managing memory efficiently is crucial. Custom memory pool allocators can significantly improve performance by reducing fragmentation and overhead associated with standard memory allocation methods. This article guides you through creating a simple yet effective custom memory pool allocator in C.

Understanding the Need for Custom Memory Pools

Standard functions like malloc and free are versatile but can introduce overhead and fragmentation, especially in applications with frequent allocations and deallocations of small objects. Custom memory pools pre-allocate large blocks of memory and manage allocations internally, leading to faster performance and predictable memory usage.

Designing a Simple Memory Pool

A basic memory pool consists of a large contiguous block of memory and a free list that tracks available chunks. When a request arrives, the allocator provides a chunk from the free list. When memory is freed, it is returned to the free list for reuse.

Defining the Data Structures

We define a simple structure for free blocks and the memory pool:

typedef struct FreeBlock {
    struct FreeBlock* next;
} FreeBlock;

typedef struct MemoryPool {
    size_t block_size;
    size_t total_blocks;
    void* pool;
    FreeBlock* free_list;
} MemoryPool;

Initializing the Pool

Initialization involves allocating a large block of memory and linking all free blocks into the free list:

void init_pool(MemoryPool* mp, size_t block_size, size_t total_blocks) {
    mp->block_size = block_size;
    mp->total_blocks = total_blocks;
    mp->pool = malloc(block_size * total_blocks);
    mp->free_list = (FreeBlock*)mp->pool;

    FreeBlock* current = mp->free_list;
    for (size_t i = 1; i < total_blocks; i++) {
        current->next = (FreeBlock*)((char*)mp->pool + i * block_size);
        current = current->next;
    }
    current->next = NULL;
}

Allocating and Freeing Memory

Allocation retrieves a block from the free list, and freeing returns it to the list:

void* pool_alloc(MemoryPool* mp) {
    if (mp->free_list == NULL) {
        return NULL; // Pool exhausted
    }
    void* block = mp->free_list;
    mp->free_list = mp->free_list->next;
    return block;
}

void pool_free(MemoryPool* mp, void* block) {
    FreeBlock* fb = (FreeBlock*)block;
    fb->next = mp->free_list;
    mp->free_list = fb;
}

Usage Example

Here’s how to initialize the pool and allocate/free memory:

int main() {
    MemoryPool mp;
    init_pool(&mp, sizeof(int), 100);

    int* p = (int*)pool_alloc(&mp);
    if (p != NULL) {
        *p = 42;
        printf("Allocated value: %d\n", *p);
        pool_free(&mp, p);
    }

    // Cleanup
    free(mp.pool);
    return 0;
}

By customizing the block size and total blocks, you can tailor this memory pool to your application’s needs, achieving higher performance and predictable memory management.