blob: 9235b8577df80aeb8b9aa2e8ff0542bb690c2d6f [file] [log] [blame]
/*
* Example memory allocator with pool allocation for small sizes and
* fallback into malloc/realloc/free for larger sizes or when the pools
* are exhausted.
*
* Useful to reduce memory churn or work around a platform allocator
* that doesn't handle a lot of small allocations efficiently.
*/
#include "duktape.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* Define to enable some debug printfs. */
/* #define DUK_ALLOC_HYBRID_DEBUG */
typedef struct {
size_t size;
int count;
} pool_size_spec;
static pool_size_spec pool_sizes[] = {
{ 32, 1024 },
{ 48, 2048 },
{ 64, 2048 },
{ 128, 2048 },
{ 256, 512 },
{ 1024, 64 },
{ 2048, 32 }
};
#define NUM_POOLS (sizeof(pool_sizes) / sizeof(pool_size_spec))
/* This must fit into the smallest pool entry. */
struct pool_free_entry;
typedef struct pool_free_entry pool_free_entry;
struct pool_free_entry {
pool_free_entry *next;
};
typedef struct {
pool_free_entry *free;
char *alloc_start;
char *alloc_end;
size_t size;
int count;
} pool_header;
typedef struct {
pool_header headers[NUM_POOLS];
size_t pool_max_size;
char *alloc_start;
char *alloc_end;
} pool_state;
#define ADDR_IN_STATE_ALLOC(st,p) \
((char *) (p) >= (st)->alloc_start && (char *) (p) < (st)->alloc_end)
#define ADDR_IN_HEADER_ALLOC(hdr,p) \
((char *) (p) >= (hdr)->alloc_start && (char *) (p) < (hdr)->alloc_end)
#ifdef DUK_ALLOC_HYBRID_DEBUG
static void dump_pool_state(pool_state *st) {
pool_free_entry *free;
int free_len;
int i;
printf("=== Pool state: st=%p\n", (void *) st);
for (i = 0; i < (int) NUM_POOLS; i++) {
pool_header *hdr = st->headers + i;
for (free = hdr->free, free_len = 0; free != NULL; free = free->next) {
free_len++;
}
printf("[%d]: size %ld, count %ld, used %ld, free list len %ld\n",
i, (long) hdr->size, (long) hdr->count,
(long) (hdr->count - free_len),
(long) free_len);
}
}
#else
static void dump_pool_state(pool_state *st) {
(void) st;
}
#endif
void *duk_alloc_hybrid_init(void) {
pool_state *st;
size_t total_size, max_size;
int i, j;
char *p;
st = (pool_state *) malloc(sizeof(pool_state));
if (!st) {
return NULL;
}
memset((void *) st, 0, sizeof(pool_state));
st->alloc_start = NULL;
st->alloc_end = NULL;
for (i = 0, total_size = 0, max_size = 0; i < (int) NUM_POOLS; i++) {
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("Pool %d: size %ld, count %ld\n", i, (long) pool_sizes[i].size, (long) pool_sizes[i].count);
#endif
total_size += pool_sizes[i].size * pool_sizes[i].count;
if (pool_sizes[i].size > max_size) {
max_size = pool_sizes[i].size;
}
}
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("Total size %ld, max pool size %ld\n", (long) total_size, (long) max_size);
#endif
st->alloc_start = (char *) malloc(total_size);
if (!st->alloc_start) {
free(st);
return NULL;
}
st->alloc_end = st->alloc_start + total_size;
st->pool_max_size = max_size;
memset((void *) st->alloc_start, 0, total_size);
for (i = 0, p = st->alloc_start; i < (int) NUM_POOLS; i++) {
pool_header *hdr = st->headers + i;
hdr->alloc_start = p;
hdr->alloc_end = p + pool_sizes[i].size * pool_sizes[i].count;
hdr->free = (pool_free_entry *) (void *) p;
hdr->size = pool_sizes[i].size;
hdr->count = pool_sizes[i].count;
for (j = 0; j < pool_sizes[i].count; j++) {
pool_free_entry *ent = (pool_free_entry *) (void *) p;
if (j == pool_sizes[i].count - 1) {
ent->next = (pool_free_entry *) NULL;
} else {
ent->next = (pool_free_entry *) (void *) (p + pool_sizes[i].size);
}
p += pool_sizes[i].size;
}
}
dump_pool_state(st);
/* Use 'st' as udata. */
return (void *) st;
}
void *duk_alloc_hybrid(void *udata, duk_size_t size) {
pool_state *st = (pool_state *) udata;
int i;
void *new_ptr;
#if 0
dump_pool_state(st);
#endif
if (size == 0) {
return NULL;
}
if (size > st->pool_max_size) {
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("alloc fallback: %ld\n", (long) size);
#endif
return malloc(size);
}
for (i = 0; i < (int) NUM_POOLS; i++) {
pool_header *hdr = st->headers + i;
if (hdr->size < size) {
continue;
}
if (hdr->free) {
#if 0
printf("alloc from pool: %ld -> pool size %ld\n", (long) size, (long) hdr->size);
#endif
new_ptr = (void *) hdr->free;
hdr->free = hdr->free->next;
return new_ptr;
} else {
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("alloc out of pool entries: %ld -> pool size %ld\n", (long) size, (long) hdr->size);
#endif
break;
}
}
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("alloc fallback (out of pool): %ld\n", (long) size);
#endif
return malloc(size);
}
void *duk_realloc_hybrid(void *udata, void *ptr, duk_size_t size) {
pool_state *st = (pool_state *) udata;
void *new_ptr;
int i;
#if 0
dump_pool_state(st);
#endif
if (ADDR_IN_STATE_ALLOC(st, ptr)) {
/* 'ptr' cannot be NULL. */
for (i = 0; i < (int) NUM_POOLS; i++) {
pool_header *hdr = st->headers + i;
if (ADDR_IN_HEADER_ALLOC(hdr, ptr)) {
if (size <= hdr->size) {
/* Still fits, no shrink support. */
#if 0
printf("realloc original from pool: still fits, size %ld, pool size %ld\n",
(long) size, (long) hdr->size);
#endif
return ptr;
}
new_ptr = duk_alloc_hybrid(udata, size);
if (!new_ptr) {
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("realloc original from pool: needed larger size, failed to alloc\n");
#endif
return NULL;
}
memcpy(new_ptr, ptr, hdr->size);
((pool_free_entry *) ptr)->next = hdr->free;
hdr->free = (pool_free_entry *) ptr;
#if 0
printf("realloc original from pool: size %ld, pool size %ld\n", (long) size, (long) hdr->size);
#endif
return new_ptr;
}
}
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("NEVER HERE\n");
#endif
return NULL;
} else if (ptr != NULL) {
if (size == 0) {
free(ptr);
return NULL;
} else {
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("realloc fallback: size %ld\n", (long) size);
#endif
return realloc(ptr, size);
}
} else {
#if 0
printf("realloc NULL ptr, call alloc: %ld\n", (long) size);
#endif
return duk_alloc_hybrid(udata, size);
}
}
void duk_free_hybrid(void *udata, void *ptr) {
pool_state *st = (pool_state *) udata;
int i;
#if 0
dump_pool_state(st);
#endif
if (!ADDR_IN_STATE_ALLOC(st, ptr)) {
if (ptr == NULL) {
return;
}
#if 0
printf("free out of pool: %p\n", (void *) ptr);
#endif
free(ptr);
return;
}
for (i = 0; i < (int) NUM_POOLS; i++) {
pool_header *hdr = st->headers + i;
if (ADDR_IN_HEADER_ALLOC(hdr, ptr)) {
((pool_free_entry *) ptr)->next = hdr->free;
hdr->free = (pool_free_entry *) ptr;
#if 0
printf("free from pool: %p\n", ptr);
#endif
return;
}
}
#ifdef DUK_ALLOC_HYBRID_DEBUG
printf("NEVER HERE\n");
#endif
}