blob: 0f66243055535baca62bc8aba1145ce166542c3d [file] [log] [blame] [raw]
/*
Copyright: Boaz Segev, 2016-2017
License: MIT
Feel free to copy, use and enjoy according to the license provided.
*/
#ifndef MEMPOOL_H
/* *****************************************************************************
A simple `mmap` based localized memory pool (localized `malloc` alternative).
The memory pool is localized to the same object file (C file). See NOTICE.
The issue: objects that have a long life, such as Websocket / HTTP2 protocol
objects or server wide strings with reference counts, cause memory fragmentation
when allocated on the heap alongside objects that have a short life.
This is a common issue when using `malloc` in long running processes for all
allocations.
This issue effects long running processes (such as servers) while it's effect on
short lived proccesses are less accute and could often be ignored.
To circumvent this issue, a seperate memory allocation method is used for
long-lived objects.
This memory pool allocates large blocks of memory (~2Mb at a time), minimizing
small memory fragmentation by both reserving large memory blocks and seperating
memory locality between long lived objects and short lived objects.
The memory pool isn't expected to be faster than the system's `malloc`
(although, sometimes it might perform better). However, selective use of this
memory pool could improve concurrency (each pool has a seperate lock, unlike
`malloc`'s system lock') as well as help with memory fragmentation.
================================================================================
NOTICE:
The memory pool is attached to the specific file. in which `mempool.h` is
included.
The memory shoule NEVER be freed from a different file.
However, it's easy to work around this limitation by wrapping the `mempool_`
functions using proper `create` / `destroy` functions for any objects.
================================================================================
This file requires the "spnlock.h" library file as well. Together these files
can be used also seperately from the `facil.io` library.
*/
#define MEMPOOL_H
MEMPOOL_H
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef UNUSED_FUNC
#define UNUSED_FUNC __attribute__((unused))
#endif
/* *****************************************************************************
********************************************************************************
API Declerations
********************************************************************************
***************************************************************************** */
/** Allocates memory from the pool. */
static UNUSED_FUNC void *mempool_malloc(size_t size);
/**
* Frees the memory, releasing it back to the pool (or, sometimes, the system).
*/
static UNUSED_FUNC void mempool_free(void *ptr);
/**
* Behaves the same a the systems `realloc`, attempting to resize the memory
* when possible.
*
* On error returns NULL (the old pointer data remains allocated and valid)
* otherwise returns a new pointer (either equal to the old or after
* deallocating the old one).
*/
static UNUSED_FUNC void *mempool_realloc(void *ptr, size_t new_size);
#if defined(DEBUG) && DEBUG == 1
/** Tests the memory pool, both testing against issues / corruption and testing
* it's performance against the system's `malloc`.
*/
static UNUSED_FUNC void mempool_test(void);
#endif
/* *****************************************************************************
********************************************************************************
Implementation
********************************************************************************
***************************************************************************** */
/* *****************************************************************************
Memory block allocation
*/
#define MEMPOOL_BLOCK_SIZE (1UL << 21)
#define MEMPOOL_ORDERING_LIMIT 32
#define MEMPOOL_RETURN_MEM_TO_SYSTEM 1
/* Will we use mmap or malloc? */
// clang-format off
#ifdef __has_include
/* check for unix support */
# if __has_include(<unistd.h>) && __has_include(<sys/mman.h>)
# define HAS_UNIX_FEATURES
# include <unistd.h>
# endif
#endif
// clang-format on
/* *****************************************************************************
spnlock.h (can also be embeded instead of included)
*/
#include "spnlock.h"
/* *****************************************************************************
Memory slices, tree and helpers
*/
struct mempool_reserved_slice_s_offset { /** offset from this slice */
uint32_t reserved1; /* used to make the offset 16 bytes long */
uint32_t ahead;
uint32_t behind;
uint32_t reserved2; /* used to make the offset 16 bytes long */
};
typedef struct mempool_reserved_slice_s {
/** offset from this slice */
struct mempool_reserved_slice_s_offset offset;
/** Used for the free slices linked list. */
struct mempool_reserved_slice_s *next;
struct mempool_reserved_slice_s *prev;
} mempool_reserved_slice_s;
static struct {
mempool_reserved_slice_s *available;
spn_lock_i lock;
} mempool_reserved_pool = {.available = NULL, .lock = SPN_LOCK_INIT};
#define MEMPOOL_LOCK() spn_lock(&mempool_reserved_pool.lock)
#define MEMPOOL_UNLOCK() spn_unlock(&mempool_reserved_pool.lock)
#define MEMPOOL_USED_MARKER ((uint32_t)(~0UL << 21))
#define MEMPOOL_INDI_MARKER ((uint32_t)0xF7F7F7F7UL)
#define MEMPOOL_SIZE_MASK (MEMPOOL_BLOCK_SIZE - 1)
#define MEMPOOL_SLICE2PTR(slice) \
((void *)(((uintptr_t)(slice)) + \
(sizeof(struct mempool_reserved_slice_s_offset))))
#define MEMPOOL_PTR2SLICE(ptr) \
((mempool_reserved_slice_s *)(((uintptr_t)(ptr)) - \
(sizeof( \
struct mempool_reserved_slice_s_offset))))
/* *****************************************************************************
Memory Block Allocation / Deallocation
*/
#ifdef HAS_UNIX_FEATURES
#include <sys/mman.h>
#define MEMPOOL_ALLOC_SPECIAL(target, size) \
do { \
target = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, \
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); \
if (target == MAP_FAILED) \
target = NULL; \
} while (0);
#define MEMPOOL_DEALLOC_SPECIAL(target, size) munmap((target), (size))
#else
#define MEMPOOL_ALLOC_SPECIAL(target, size) \
do { \
target = malloc(size); \
} while (0);
#define MEMPOOL_DEALLOC_SPECIAL(target, size) free((target));
#endif
/* *****************************************************************************
Helpers: Memory block slicing and memory pool list maintanence.
*/
/* *****************************************************************************
API implementation
*/
static UNUSED_FUNC void *mempool_malloc(size_t size) {
if (!size)
return NULL;
if (size & 15) {
size = (size & (~15)) + 16;
}
size += sizeof(struct mempool_reserved_slice_s_offset);
mempool_reserved_slice_s *slice = NULL;
if (size > (MEMPOOL_BLOCK_SIZE - (sizeof(mempool_reserved_slice_s) << 1)))
goto alloc_indi;
MEMPOOL_LOCK();
slice = mempool_reserved_pool.available;
while (slice && slice->offset.ahead < size)
slice = slice->next;
if (slice) {
/* remove slice from available memory list */
if (slice->next)
slice->next->prev = slice->prev;
if (slice->prev)
slice->prev->next = slice->next;
else
mempool_reserved_pool.available = slice->next;
slice->next = NULL;
slice->prev = NULL;
} else {
MEMPOOL_ALLOC_SPECIAL(slice, MEMPOOL_BLOCK_SIZE);
// fprintf(stderr, "Allocated Block at %p\n", (void *)slice);
slice->offset.behind = 0;
slice->offset.ahead =
MEMPOOL_BLOCK_SIZE - (sizeof(struct mempool_reserved_slice_s_offset));
mempool_reserved_slice_s *tmp =
(mempool_reserved_slice_s
*)(((uintptr_t)(slice)) + MEMPOOL_BLOCK_SIZE -
(sizeof(struct mempool_reserved_slice_s_offset)));
tmp->offset.ahead = 0;
tmp->offset.behind = slice->offset.ahead;
}
if (!slice) {
MEMPOOL_UNLOCK();
fprintf(stderr, "mempool: no memory\n");
return NULL;
}
if (slice->offset.ahead & MEMPOOL_USED_MARKER) {
fprintf(stderr, "mempool ERROR: allocating an allocated slice!\n");
}
if (slice->offset.ahead > (size + sizeof(mempool_reserved_slice_s))) {
/* cut the slice in two */
mempool_reserved_slice_s *tmp =
(mempool_reserved_slice_s *)(((uintptr_t)slice) + size);
tmp->offset.behind = size;
tmp->offset.ahead = slice->offset.ahead - size;
slice->offset.ahead = size;
/* inform higher neighbor about any updates */
((mempool_reserved_slice_s *)(((uintptr_t)tmp) + tmp->offset.ahead))
->offset.behind = tmp->offset.ahead;
/* place the new slice in the available memory list */
uint16_t limit = MEMPOOL_ORDERING_LIMIT;
tmp->next = NULL;
tmp->prev = NULL;
mempool_reserved_slice_s **pos = &mempool_reserved_pool.available;
while (limit && *pos && ((*pos)->offset.ahead < tmp->offset.ahead)) {
tmp->prev = *pos;
pos = &(*pos)->next;
--limit;
}
if (*pos) {
tmp->next = *pos;
tmp->next->prev = tmp;
*pos = tmp;
} else {
*pos = tmp;
}
if (tmp->prev)
tmp->prev->next = tmp;
else
mempool_reserved_pool.available = tmp;
}
slice->offset.ahead |= MEMPOOL_USED_MARKER;
slice->next = NULL;
slice->prev = NULL;
MEMPOOL_UNLOCK();
// mempool_reserved_slice_s *tmp =
// (void *)((uintptr_t)slice + (slice->offset.ahead &
// MEMPOOL_SIZE_MASK));
// fprintf(stderr, "Allocated %lu bytes at: %u <- %p -> %u."
// "next: %u <- %p -> %u\n ",
// size, slice->offset.behind, slice,
// (uint32_t)(slice->offset.ahead & MEMPOOL_SIZE_MASK),
// tmp->offset.behind, tmp,
// (uint32_t)(tmp->offset.ahead & MEMPOOL_SIZE_MASK));
return MEMPOOL_SLICE2PTR(slice);
alloc_indi:
MEMPOOL_ALLOC_SPECIAL(slice, size);
if (slice) {
slice->offset.ahead = size;
slice->offset.behind = MEMPOOL_INDI_MARKER;
}
return MEMPOOL_SLICE2PTR(slice);
}
/**
* Frees the memory, releasing it back to the pool (or, sometimes, the
* system).
*/
static UNUSED_FUNC void mempool_free(void *ptr) {
if (!ptr)
return;
mempool_reserved_slice_s **pos, *slice = MEMPOOL_PTR2SLICE(ptr), *tmp;
if (slice->offset.behind == MEMPOOL_INDI_MARKER)
goto alloc_indi;
if ((slice->offset.ahead & MEMPOOL_USED_MARKER) != MEMPOOL_USED_MARKER)
goto error;
MEMPOOL_LOCK();
slice->offset.ahead &= MEMPOOL_SIZE_MASK;
/* merge slice with upper boundry */
while ((tmp = (mempool_reserved_slice_s *)(((uintptr_t)slice) +
slice->offset.ahead))
->offset.ahead &&
(tmp->offset.ahead & MEMPOOL_USED_MARKER) == 0) {
/* extract merged slice from list */
if (tmp->next)
tmp->next->prev = tmp->prev;
if (tmp->prev)
tmp->prev->next = tmp->next;
else
mempool_reserved_pool.available = tmp->next;
tmp->next = NULL;
tmp->prev = NULL;
slice->offset.ahead += tmp->offset.ahead;
}
/* merge slice with lower boundry */
while (slice->offset.behind &&
((tmp = (mempool_reserved_slice_s *)(((uintptr_t)slice) -
slice->offset.behind))
->offset.ahead &
MEMPOOL_USED_MARKER) == 0) {
/* extract merged slice from list */
if (tmp->next)
tmp->next->prev = tmp->prev;
if (tmp->prev)
tmp->prev->next = tmp->next;
else
mempool_reserved_pool.available = tmp->next;
tmp->next = NULL;
tmp->prev = NULL;
tmp->offset.ahead += slice->offset.ahead;
slice = tmp;
}
/* return memory to system, if the block is no longer required. */
if (MEMPOOL_RETURN_MEM_TO_SYSTEM && mempool_reserved_pool.available &&
slice->offset.behind == 0 &&
((mempool_reserved_slice_s *)(((uintptr_t)slice) + slice->offset.ahead))
->offset.ahead == 0) {
MEMPOOL_UNLOCK();
/* fprintf(
stderr, "DEALLOCATED BLOCK %p, size review %u == %lu %s\n",
(void *)slice, slice->offset.ahead,
MEMPOOL_BLOCK_SIZE - sizeof(struct mempool_reserved_slice_s_offset),
(slice->offset.ahead ==
MEMPOOL_BLOCK_SIZE - sizeof(struct mempool_reserved_slice_s_offset))
? "passed."
: "FAILED."); */
MEMPOOL_DEALLOC_SPECIAL(slice, MEMPOOL_BLOCK_SIZE);
return;
}
// if (slice->offset.behind == 0 &&
// ((mempool_reserved_slice_s *)(((uintptr_t)slice) +
// slice->offset.ahead))
// ->offset.ahead == 0)
// fprintf(stderr, "All memory in pool is free\n");
/* inform higher neighbor about any updates */
// fprintf(stderr, "slice: %p -> %u\n", slice, slice->offset.ahead);
((mempool_reserved_slice_s *)(((uintptr_t)slice) + slice->offset.ahead))
->offset.behind = slice->offset.ahead;
/* place slice in list */
uint8_t limit = MEMPOOL_ORDERING_LIMIT;
slice->next = NULL;
slice->prev = NULL;
pos = &mempool_reserved_pool.available;
while (limit && *pos && ((*pos)->offset.ahead < slice->offset.ahead)) {
slice->prev = *pos;
pos = &(*pos)->next;
--limit;
}
if (*pos) {
slice->next = *pos;
slice->next->prev = slice;
*pos = slice;
} else {
*pos = slice;
}
if (slice->prev)
slice->prev->next = slice;
else
mempool_reserved_pool.available = slice;
MEMPOOL_UNLOCK();
return;
alloc_indi:
MEMPOOL_DEALLOC_SPECIAL(slice, slice->offset.ahead);
return;
error:
if ((slice->offset.ahead & MEMPOOL_USED_MARKER) == 0)
fprintf(stderr, "mempool: memory being freed is already free.\n");
else
fprintf(stderr, "mempool: memory allocation data corrupted. possible "
"buffer overflow?\n");
MEMPOOL_UNLOCK();
errno = EFAULT;
raise(SIGSEGV); /* support longjmp rescue */
exit(EFAULT);
}
/**
* Behaves the same a the systems `realloc`, attempting to resize the memory
* when possible. On error returns NULL (the old pointer data remains allocated
* and valid) otherwise returns a new pointer (either equal to the old or after
* deallocating the old one).
*/
static UNUSED_FUNC void *mempool_realloc(void *ptr, size_t size) {
if (!size)
return NULL;
if (size & 15) {
size = (size & (~16)) + 16;
}
size += sizeof(struct mempool_reserved_slice_s_offset);
mempool_reserved_slice_s *tmp = NULL, *slice = MEMPOOL_PTR2SLICE(ptr);
if (slice->offset.behind == MEMPOOL_INDI_MARKER)
goto realloc_indi;
if ((slice->offset.ahead & MEMPOOL_USED_MARKER) != MEMPOOL_USED_MARKER)
goto error;
MEMPOOL_LOCK();
slice->offset.ahead &= MEMPOOL_SIZE_MASK;
/* merge slice with upper boundry */
while ((tmp = (mempool_reserved_slice_s *)(((uintptr_t)slice) +
slice->offset.ahead))
->offset.ahead &&
(tmp->offset.ahead & MEMPOOL_USED_MARKER) == 0) {
/* extract merged slice from list */
if (tmp->next)
tmp->next->prev = tmp->prev;
if (tmp->prev)
tmp->prev->next = tmp->next;
else
mempool_reserved_pool.available = tmp->next;
tmp->next = NULL;
tmp->prev = NULL;
slice->offset.ahead += tmp->offset.ahead;
}
/* inform higher neighbor about any updates */
((mempool_reserved_slice_s *)(((uintptr_t)slice) + slice->offset.ahead))
->offset.behind = slice->offset.ahead;
if ((slice->offset.ahead) > size + sizeof(mempool_reserved_slice_s)) {
/* cut the slice in two */
tmp = (mempool_reserved_slice_s *)(((uintptr_t)slice) + size);
tmp->offset.behind = size;
tmp->offset.ahead = slice->offset.ahead - size;
slice->offset.ahead = size;
/* inform higher neighbor about any updates */
((mempool_reserved_slice_s *)(((uintptr_t)tmp) + tmp->offset.ahead))
->offset.behind = tmp->offset.ahead;
/* place the new slice in the available memory list */
tmp->next = NULL;
tmp->prev = NULL;
mempool_reserved_slice_s **pos = &mempool_reserved_pool.available;
uint8_t limit = MEMPOOL_ORDERING_LIMIT;
while (limit && *pos && ((*pos)->offset.ahead < tmp->offset.ahead)) {
tmp->prev = *pos;
pos = &(*pos)->next;
--limit;
}
if (*pos) {
tmp->next = *pos;
tmp->next->prev = tmp;
*pos = tmp;
} else {
*pos = tmp;
}
if (tmp->prev)
tmp->prev->next = tmp;
else
mempool_reserved_pool.available = tmp;
slice->offset.ahead |= MEMPOOL_USED_MARKER;
MEMPOOL_UNLOCK();
return ptr;
}
slice->offset.ahead |= MEMPOOL_USED_MARKER;
MEMPOOL_UNLOCK();
if ((slice->offset.ahead & MEMPOOL_SIZE_MASK) < size) {
void *new_mem =
mempool_malloc(size - sizeof(struct mempool_reserved_slice_s_offset));
if (!new_mem)
return NULL;
memcpy(new_mem, ptr, slice->offset.ahead & MEMPOOL_SIZE_MASK);
mempool_free(ptr);
ptr = new_mem;
}
return ptr;
realloc_indi:
/* indi doesn't shrink */
if (slice->offset.ahead > size)
return ptr;
/* reallocate indi */
void *new_mem =
mempool_malloc(size - sizeof(struct mempool_reserved_slice_s_offset));
if (!new_mem)
return NULL;
memcpy(new_mem, ptr, slice->offset.ahead & MEMPOOL_SIZE_MASK);
mempool_free(ptr);
return new_mem;
error:
errno = EFAULT;
raise(SIGSEGV); /* support longjmp rescue */
exit(EFAULT);
}
/* *****************************************************************************
********************************************************************************
TESTING
********************************************************************************
***************************************************************************** */
#if defined(DEBUG) && DEBUG == 1
#define MEMTEST_SLICE 32
#include <time.h>
#include <inttypes.h>
#include <pthread.h>
static void mempool_stats(void) {
fprintf(stderr, "* Pool object: %lu bytes\n"
"* Alignment: %lu \n"
"* Minimal Allocation Size (including header): %lu\n"
"* Minimal Allocation Space (no header): %lu\n"
"* Header size: %lu\n",
sizeof(mempool_reserved_pool),
sizeof(struct mempool_reserved_slice_s_offset),
sizeof(mempool_reserved_slice_s),
sizeof(mempool_reserved_slice_s) -
sizeof(struct mempool_reserved_slice_s_offset),
sizeof(struct mempool_reserved_slice_s_offset));
}
static void mempool_speedtest(size_t memtest_repeats, void *(*mlk)(size_t),
void (*fr)(void *),
void *(*ralc)(void *, size_t)) {
void **pntrs = mlk(memtest_repeats * sizeof(*pntrs));
clock_t start, end, mlk_time, fr_time, zr_time;
mlk_time = 0;
fr_time = 0;
zr_time = 0;
struct timespec start_test, end_test;
clock_gettime(CLOCK_MONOTONIC, &start_test);
start = clock();
for (size_t i = 0; i < memtest_repeats; i++) {
__asm__ volatile("" ::: "memory");
}
end = clock();
mlk_time = end - start;
fprintf(stderr, "* Doing nothing: %lu CPU cycles.\n", mlk_time);
start = clock();
for (size_t i = 0; i < memtest_repeats; i++) {
// fprintf(stderr, "malloc %lu\n", i);
pntrs[i] = mlk(MEMTEST_SLICE);
*((uint8_t *)pntrs[i]) = 1;
}
end = clock();
mlk_time = end - start;
fprintf(stderr,
"* Allocating %lu consecutive blocks %d each: %lu CPU cycles.\n",
memtest_repeats, MEMTEST_SLICE, mlk_time);
start = clock();
for (size_t i = 0; i < memtest_repeats; i += 2) {
fr(pntrs[i]);
}
end = clock();
fr_time = end - start;
start = clock();
for (size_t i = 0; i < memtest_repeats; i += 2) {
pntrs[i] = mlk(MEMTEST_SLICE);
}
end = clock();
mlk_time = end - start;
fprintf(stderr,
"* Freeing %lu Fragmented (single space) blocks %d each: %lu CPU "
"cycles.\n",
memtest_repeats / 2, MEMTEST_SLICE, fr_time);
fprintf(stderr, "* Allocating %lu Fragmented (single space) blocks %d "
"bytes each: %lu CPU "
"cycles.\n",
memtest_repeats / 2, MEMTEST_SLICE, mlk_time);
mlk_time = 0;
fr_time = 0;
for (size_t xtimes = 0; xtimes < 100; xtimes++) {
start = clock();
for (size_t i = 0; i < memtest_repeats; i += 7) {
fr(pntrs[i]);
}
end = clock();
fr_time += end - start;
start = clock();
for (size_t i = 0; i < memtest_repeats; i += 7) {
pntrs[i] = mlk(MEMTEST_SLICE);
}
end = clock();
mlk_time += end - start;
}
fprintf(stderr,
"* 100X Freeing %lu Fragmented (7 spaces) blocks %d each: %lu CPU "
"cycles.\n",
memtest_repeats / 7, MEMTEST_SLICE, fr_time);
fprintf(stderr, "* 100X Allocating %lu Fragmented (7 spaces) blocks %d "
"bytes each: %lu CPU "
"cycles.\n",
memtest_repeats / 7, MEMTEST_SLICE, mlk_time);
start = clock();
for (size_t i = 0; i < memtest_repeats; i++) {
memset(pntrs[i], 170, MEMTEST_SLICE);
}
end = clock();
zr_time = end - start;
fprintf(stderr, "* Set bits (0b10) for %lu consecutive blocks %dB "
"each: %lu CPU cycles.\n",
memtest_repeats, MEMTEST_SLICE, zr_time);
start = clock();
for (size_t i = 0; i < memtest_repeats; i++) {
fr(pntrs[i]);
}
end = clock();
fr_time = end - start;
fprintf(stderr, "* Freeing %lu consecutive blocks %d each: %lu CPU cycles.\n",
memtest_repeats, MEMTEST_SLICE, fr_time);
start = clock();
for (size_t i = 0; i < memtest_repeats; i++) {
pntrs[i] = mlk(MEMTEST_SLICE);
}
end = clock();
start = clock();
for (size_t i = 0; i < memtest_repeats; i += 2) {
fr(pntrs[i]);
}
end = clock();
mlk_time = end - start;
fprintf(stderr,
"* Freeing every other block %dB X %lu blocks: %lu CPU cycles.\n",
MEMTEST_SLICE, memtest_repeats >> 1, mlk_time);
start = clock();
for (size_t i = 1; i < memtest_repeats; i += 2) {
pntrs[i] = ralc(pntrs[i], MEMTEST_SLICE << 1);
if (pntrs[i] == NULL)
fprintf(stderr, "REALLOC RETURNED NULL - Memory leaked during test\n");
}
end = clock();
mlk_time = end - start;
fprintf(
stderr,
"* Reallocating every other block %dB X %lu blocks: %lu CPU cycles.\n",
MEMTEST_SLICE, memtest_repeats >> 1, mlk_time);
start = clock();
for (size_t i = 1; i < memtest_repeats; i += 2) {
fr(pntrs[i]);
}
end = clock();
mlk_time = end - start;
fprintf(stderr,
"* Freeing every other block %dB X %lu blocks: %lu CPU cycles.\n",
MEMTEST_SLICE, memtest_repeats >> 1, mlk_time);
start = clock();
for (size_t i = 0; i < memtest_repeats; i++) {
pntrs[i] = mlk(MEMTEST_SLICE);
}
end = clock();
mlk_time = end - start;
fprintf(stderr,
"* Allocating %lu consecutive blocks %d each: %lu CPU cycles.\n",
memtest_repeats, MEMTEST_SLICE, mlk_time);
start = clock();
for (size_t i = 0; i < memtest_repeats; i++) {
fr(pntrs[i]);
}
end = clock();
fr_time = end - start;
fprintf(stderr, "* Freeing %lu consecutive blocks %d each: %lu CPU cycles.\n",
memtest_repeats, MEMTEST_SLICE, fr_time);
fprintf(stderr, "* Freeing pointer array %p.\n", (void *)pntrs);
fr(pntrs);
clock_gettime(CLOCK_MONOTONIC, &end_test);
uint64_t msec_for_test =
(end_test.tv_nsec < start_test.tv_nsec)
? ((end_test.tv_sec -= 1), (start_test.tv_nsec - end_test.tv_nsec))
: (end_test.tv_nsec - start_test.tv_nsec);
uint64_t sec_for_test = end_test.tv_sec - start_test.tv_sec;
fprintf(stderr,
"Finished test in %" PRIu64 "m, %" PRIu64 "s %" PRIu64 " mili.sec.\n",
sec_for_test / 60, sec_for_test - (((sec_for_test) / 60) * 60),
msec_for_test / 1000000);
}
static UNUSED_FUNC void *mempool_concurrency_test(void *arg) {
void **pt_list = mempool_malloc(sizeof(void *) * 1024);
for (size_t i = 0; i < 16384; i++) {
fprintf(stderr, "alloc rep %lu\n", i);
pt_list[i] = mempool_malloc((uintptr_t)arg);
}
for (size_t i = 0; i < 16384; i++) {
fprintf(stderr, "free rep %lu\n", i);
mempool_free(pt_list[i]);
}
mempool_free(pt_list);
return NULL;
}
static UNUSED_FUNC void mempool_test(void) {
fprintf(stderr, "*****************************\n");
fprintf(stderr, "mempool implementation details:\n");
mempool_stats();
fprintf(stderr, "*****************************\n");
fprintf(stderr, "Concurrency memory test\n");
{
pthread_t threads[8];
clock_t start, end;
start = clock();
for (size_t i = 0; i < 8; i++) {
mempool_concurrency_test((void *)((16 * i) + MEMTEST_SLICE));
fprintf(stderr, "Allocation cycle %lu - done.\n", i + 1);
}
end = clock();
fprintf(stderr, "Non-concurrent allocation finished in %lu CPU cycles\n",
end - start);
for (size_t i = 0; i < 8; i++) {
pthread_create(threads + i, NULL, mempool_concurrency_test,
(void *)((16 * i) + MEMTEST_SLICE));
}
for (size_t i = 0; i < 8; i++) {
pthread_join(threads[i], NULL);
}
end = clock();
fprintf(stderr, "Concurrent allocation finished in %lu CPU cycles\n",
end - start);
}
fprintf(stderr, "*****************************\n");
fprintf(stderr, "System memory test for ~2Mb\n");
mempool_speedtest((2 << 20) / MEMTEST_SLICE, malloc, free, realloc);
fprintf(stderr, "*****************************\n");
fprintf(stderr, " mempool memory test for ~2Mb\n");
mempool_speedtest((2 << 20) / MEMTEST_SLICE, mempool_malloc, mempool_free,
mempool_realloc);
fprintf(stderr, "*****************************\n");
fprintf(stderr, "System memory test for ~4Mb\n");
mempool_speedtest((2 << 21) / MEMTEST_SLICE, malloc, free, realloc);
fprintf(stderr, "*****************************\n");
fprintf(stderr, " mempool memory test for ~4Mb\n");
mempool_speedtest((2 << 21) / MEMTEST_SLICE, mempool_malloc, mempool_free,
mempool_realloc);
fprintf(stderr, "*****************************\n");
fprintf(stderr, "System memory test for ~8Mb\n");
mempool_speedtest((2 << 22) / MEMTEST_SLICE, malloc, free, realloc);
fprintf(stderr, "*****************************\n");
fprintf(stderr, " mempool memory test for ~8Mb\n");
mempool_speedtest((2 << 22) / MEMTEST_SLICE, mempool_malloc, mempool_free,
mempool_realloc);
fprintf(stderr, "*****************************\n");
fprintf(stderr, "System memory test for ~16Mb\n");
mempool_speedtest((2 << 23) / MEMTEST_SLICE, malloc, free, realloc);
fprintf(stderr, "*****************************\n");
fprintf(stderr, " mempool memory test for ~16Mb\n");
mempool_speedtest((2 << 23) / MEMTEST_SLICE, mempool_malloc, mempool_free,
mempool_realloc);
fprintf(stderr, "*****************************\n");
fprintf(stderr, "*****************************\n");
fprintf(stderr, "Stressing the system\n");
fprintf(stderr, "*****************************\n");
size_t repeat = 1024 * 1024 * 16;
size_t unit = 16;
struct timespec start_test, end_test;
clock_t start, end;
fprintf(stderr, "Stress allocation/deallocation using "
"1:5 fragmentation of ~134Mb:\n");
while (repeat >= 1024) {
fprintf(stderr, " * %lu X %lu bytes", repeat, unit);
clock_gettime(CLOCK_MONOTONIC, &start_test);
start = clock();
void **ptrs = mempool_malloc(repeat * sizeof(void *));
for (size_t i = 0; i < repeat; i++) {
ptrs[i] = mempool_malloc(unit);
}
for (size_t i = 0; i < repeat; i += 5) {
mempool_free(ptrs[i]);
}
for (size_t i = 1; i < repeat; i += 5) {
mempool_free(ptrs[i]);
}
for (size_t i = 2; i < repeat; i += 5) {
mempool_free(ptrs[i]);
}
for (size_t i = 3; i < repeat; i += 5) {
mempool_free(ptrs[i]);
}
for (size_t i = 4; i < repeat; i += 5) {
mempool_free(ptrs[i]);
}
for (size_t i = 0; i < repeat; i++) {
ptrs[i] = mempool_malloc(unit);
}
for (size_t i = 0; i < repeat; i++) {
mempool_free(ptrs[i]);
}
mempool_free(ptrs);
end = clock();
clock_gettime(CLOCK_MONOTONIC, &end_test);
uint64_t msec_for_test =
(end_test.tv_nsec < start_test.tv_nsec)
? ((end_test.tv_sec -= 1), (start_test.tv_nsec - end_test.tv_nsec))
: (end_test.tv_nsec - start_test.tv_nsec);
uint64_t sec_for_test = end_test.tv_sec - start_test.tv_sec;
fprintf(stderr,
" %" PRIu64 "m, %" PRIu64 "s %" PRIu64 " mili.sec. ( %lu CPU)\n",
sec_for_test / 60, sec_for_test - (((sec_for_test) / 60) * 60),
msec_for_test / 1000000, end - start);
unit <<= 1;
repeat >>= 1;
}
}
#undef MEMTEST_SLICE
#endif
/* *****************************************************************************
Cleanup
*/
#undef MEMPOOL_BLOCK_SIZE
#undef MEMPOOL_ALLOC_SPECIAL
#undef MEMPOOL_DEALLOC_SPECIAL
#undef MEMPOOL_SIZE_MASK
#undef MEMPOOL_USED_MARKER
#undef MEMPOOL_INDI_MARKER
#undef MEMPOOL_SLICE2PTR
#undef MEMPOOL_PTR2SLICE
#undef MEMPOOL_LOCK
#undef MEMPOOL_UNLOCK
#undef MEMPOOL_ORDERING_LIMIT
#endif