mpool: completion
fix tests, add some benchmarks finished documentation, cosmetics, cleanup
This commit is contained in:
parent
92271ba1c1
commit
58f1fbe3a8
3 changed files with 279 additions and 65 deletions
|
|
@ -58,6 +58,11 @@ struct mpoolnode_struct
|
|||
llist node;
|
||||
};
|
||||
|
||||
|
||||
MPool
|
||||
mpool_cluster_alloc_ (MPool self);
|
||||
|
||||
|
||||
#define MPOOL_BITMAP_SIZE(elements_per_cluster) \
|
||||
(((elements_per_cluster) + sizeof(uintptr_t)*CHAR_BIT - 1) \
|
||||
/ (sizeof(uintptr_t) * CHAR_BIT) * sizeof (uintptr_t))
|
||||
|
|
@ -453,12 +458,6 @@ mpool_free (MPool self, void* element)
|
|||
}
|
||||
|
||||
|
||||
unsigned
|
||||
mpool_available (MPool self)
|
||||
{
|
||||
return self->elements_free;
|
||||
}
|
||||
|
||||
|
||||
MPool
|
||||
mpool_reserve (MPool self, unsigned nelements)
|
||||
|
|
|
|||
127
src/lib/mpool.h
127
src/lib/mpool.h
|
|
@ -29,12 +29,10 @@
|
|||
//mpool ------------
|
||||
//mpool
|
||||
//mpool This memory pools are implemented as clusters of fixed sized elements. New clusters
|
||||
//mpool are allocated on demand or manually preallocated with a `reserve()` operation. The pool
|
||||
//mpool can be optimized by a `optimize()` operation which sorts the freelist by the addresses of
|
||||
//mpool free elements and optionally moves elements (when a move function is provided) to lower
|
||||
//mpool to fill up holes and improve cache locality.
|
||||
//mpool Clusters can be reclaimed with a `collect()` operation. Optimization and Collection are
|
||||
//mpool optional and are never automatically called.
|
||||
//mpool are allocated on demand or manually preallocated with a `reserve()` operation.
|
||||
//mpool Some efforts are taken to ensure (cache) locality of the provided memory.
|
||||
//mpool All functions are reentrant but not threadsafe, if this is desired it is advised to
|
||||
//mpool care for proper locking elsewhere.
|
||||
//mpool
|
||||
*/
|
||||
|
||||
|
|
@ -63,12 +61,7 @@ typedef void (*mpool_destroy_fn)(void* self);
|
|||
//mpool typedef mpool* MPool
|
||||
//mpool typedef const mpool* const_MPool
|
||||
//mpool
|
||||
//mpool This structure should be considered opaque except for the documented members
|
||||
//mpool which may be read.
|
||||
//mpool
|
||||
//mpool `unsigned mpool.elements_free`::
|
||||
//mpool number of free elements in the pool
|
||||
//mpool
|
||||
//mpool This structure should be considered opaque.
|
||||
*/
|
||||
typedef struct mpool_struct mpool;
|
||||
typedef mpool* MPool;
|
||||
|
|
@ -88,10 +81,12 @@ struct mpool_struct
|
|||
|
||||
|
||||
/*
|
||||
//index.mpool_init xref:mpool_init[mpool_init]:: initialize a new memory pool
|
||||
//index.mpool_init xref:mpool_init[mpool_init()]:: initialize a new memory pool
|
||||
//mpool [[mpool_init]]
|
||||
//mpool .mpool_init
|
||||
//mpool Memory pools must be initialized before being used.
|
||||
//mpool Initialize a memory pool, memory pools must be initialized before being used. One can supply
|
||||
//mpool an optional destructor function for elements, this will be used to destroy elements which are still
|
||||
//mpool in the pool when it gets destroyed itself. The destructor is _NOT_ called when elemented are freed.
|
||||
//mpool
|
||||
//mpool MPool mpool_init (MPool self, size_t elem_size, unsigned elements_per_cluster, mpool_move_fn mv, mpool_destroy_fn dtor)
|
||||
//mpool
|
||||
|
|
@ -112,12 +107,14 @@ mpool_init (MPool self, size_t elem_size, unsigned elements_per_cluster, mpool_d
|
|||
|
||||
|
||||
/*
|
||||
//index.mpool_destroy xref:mpool_destroy[mpool_destroy]:: destroy a memory pool
|
||||
//index.mpool_destroy xref:mpool_destroy[mpool_destroy()]:: destroy a memory pool
|
||||
//mpool [[mpool_destroy]]
|
||||
//mpool .mpool_destroy
|
||||
//mpool A memory pool is not used anymore it should be destroyed. This frees all memory allocated with it.
|
||||
//mpool When a destructor was provided at construction time, then this destructor is used on all non free elements
|
||||
//mpool before before the memory is freed. If no destructor was given then the memory is just freed.
|
||||
//mpool before before the clusters are freed. If no destructor was given then the clusters are just freed.
|
||||
//mpool The destroyed memory pool behaves as if it was freshly initialized and can be used again, this is some kindof
|
||||
//mpool exceptional behaviour.
|
||||
//mpool
|
||||
//mpool MPool mpool_destroy (MPool self)
|
||||
//mpool
|
||||
|
|
@ -133,43 +130,101 @@ mpool_destroy (MPool self);
|
|||
|
||||
|
||||
/*
|
||||
query how much free elements are available
|
||||
//index.mpool_available xref:mpool_available[mpool_available()]:: query number of free elements
|
||||
//mpool [[mpool_available]]
|
||||
//mpool .mpool_available
|
||||
//mpool One can check how much elements are available without a new cluster allocation in a memory pool.
|
||||
//mpool
|
||||
//mpool unsigned mpool_available (MPool self)
|
||||
//mpool
|
||||
//mpool `self`::
|
||||
//mpool pointer to the memory pool to be queried
|
||||
//mpool return::
|
||||
//mpool number of available elements
|
||||
//mpool
|
||||
*/
|
||||
unsigned
|
||||
mpool_available (MPool self);
|
||||
static inline unsigned
|
||||
mpool_available (MPool self)
|
||||
{
|
||||
return self->elements_free;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
resize the pool that at least nelements become available without cluster reallocations
|
||||
//index.mpool_reserve xref:mpool_reserve[mpool_reserve()]:: preallocate elements
|
||||
//mpool [[mpool_reserve]]
|
||||
//mpool .mpool_reserve
|
||||
//mpool Resize the pool that at least nelements become available without cluster reallocations
|
||||
//mpool
|
||||
//mpool unsigned mpool_reserve (MPool self, unsigned nelements)
|
||||
//mpool
|
||||
//mpool `self`::
|
||||
//mpool pointer to the memory pool
|
||||
//mpool `nelements`::
|
||||
//mpool minimum number of elements to preallocate
|
||||
//mpool return::
|
||||
//mpool self on success or NULL on error
|
||||
//mpool
|
||||
*/
|
||||
MPool
|
||||
mpool_reserve (MPool self, unsigned nelements);
|
||||
|
||||
|
||||
/*
|
||||
allocate and initialize a new cluster (internal)
|
||||
*/
|
||||
MPool
|
||||
mpool_cluster_alloc_ (MPool self);
|
||||
|
||||
|
||||
/*
|
||||
alloc one element from the pool
|
||||
//index.mpool_alloc xref:mpool_alloc[mpool_alloc()]:: allocate one element
|
||||
//mpool [[mpool_alloc]]
|
||||
//mpool .mpool_alloc
|
||||
//mpool Allocates on element from a mpool. To improve cache locality allocations
|
||||
//mpool are grouped close together to recent allocations.
|
||||
//mpool
|
||||
//mpool void* mpool_alloc (MPool self)
|
||||
//mpool
|
||||
//mpool `self`::
|
||||
//mpool pointer to the memory pool
|
||||
//mpool return::
|
||||
//mpool pointer to the allocated memory on success or NULL on error
|
||||
//mpool will never fail when enough space was preallocated
|
||||
//mpool
|
||||
*/
|
||||
void*
|
||||
mpool_alloc (MPool self);
|
||||
|
||||
/*
|
||||
alloc one element from the pool
|
||||
|
||||
should be near 'near'
|
||||
/*
|
||||
//index.mpool_alloc_near xref:mpool_alloc_near[mpool_alloc_near()]:: allocate one element, w/ locality
|
||||
//mpool [[mpool_alloc_near]]
|
||||
//mpool .mpool_alloc_near
|
||||
//mpool Allocates on element from a mpool. To improve cache locality the allocation
|
||||
//mpool tries to get an element close to another.
|
||||
//mpool
|
||||
//mpool void* mpool_alloc_near (MPool self, void* near)
|
||||
//mpool
|
||||
//mpool `self`::
|
||||
//mpool pointer to the memory pool
|
||||
//mpool `near`::
|
||||
//mpool reference to another element which should be close to the returned element (hint only)
|
||||
//mpool return::
|
||||
//mpool pointer to the allocated memory on success or NULL on error
|
||||
//mpool will never fail when enough space was preallocated
|
||||
//mpool
|
||||
*/
|
||||
void*
|
||||
mpool_alloc_near (MPool self, void* near);
|
||||
|
||||
|
||||
/*
|
||||
put a element back on the pool
|
||||
//index.mpool_free xref:mpool_free[mpool_free()]:: free one element
|
||||
//mpool [[mpool_free]]
|
||||
//mpool .mpool_free
|
||||
//mpool Frees the given element and puts it back into the pool for furhter allocations.
|
||||
//mpool
|
||||
//mpool void mpool_free (MPool self, void* element)
|
||||
//mpool
|
||||
//mpool `self`::
|
||||
//mpool pointer to the memory pool
|
||||
//mpool `element`::
|
||||
//mpool element to be freed
|
||||
//mpool
|
||||
*/
|
||||
void
|
||||
mpool_free (MPool self, void* element);
|
||||
|
|
@ -182,3 +237,11 @@ nobug_mpool_dump (const_MPool self,
|
|||
const char* file,
|
||||
const int line,
|
||||
const char* func);
|
||||
|
||||
/*
|
||||
// Local Variables:
|
||||
// mode: C
|
||||
// c-file-style: "gnu"
|
||||
// indent-tabs-mode: nil
|
||||
// End:
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -22,27 +22,33 @@
|
|||
#include "tests/test.h"
|
||||
#include "lib/mpool.h"
|
||||
|
||||
struct teststruct
|
||||
{
|
||||
llist node;
|
||||
void* ptr[2];
|
||||
};
|
||||
|
||||
|
||||
static inline uint32_t mpool_fast_prng ()
|
||||
{
|
||||
static uint32_t rnd=0xbabeface;
|
||||
return rnd = rnd<<1 ^ ((rnd >> 30) & 1) ^ ((rnd>>2) & 1);
|
||||
}
|
||||
|
||||
static void
|
||||
dtor (void* o)
|
||||
{
|
||||
ECHO("%x @%p", *(int*)o, o);
|
||||
ECHO("%d @%p", *(int*)o, o);
|
||||
}
|
||||
|
||||
static void
|
||||
move (void* a, void* b)
|
||||
{
|
||||
ECHO ("%p (%x) to %p ", b, *(int*)b, a);
|
||||
*(int*)a = *(int*)b;
|
||||
*(void**)b = NULL;
|
||||
}
|
||||
|
||||
|
||||
TESTS_BEGIN
|
||||
|
||||
TEST ("basic")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, sizeof(void*), 10, move, dtor);
|
||||
mpool_init (&mypool, sizeof(void*), 10, dtor);
|
||||
ECHO ("initialized");
|
||||
|
||||
void* element;
|
||||
|
|
@ -61,10 +67,11 @@ TEST ("basic")
|
|||
ECHO ("destroyed");
|
||||
}
|
||||
|
||||
|
||||
TEST ("destroy")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, sizeof(void*), 10, move, dtor);
|
||||
mpool_init (&mypool, sizeof(void*), 10, dtor);
|
||||
ECHO ("initialized");
|
||||
|
||||
void* element;
|
||||
|
|
@ -78,10 +85,11 @@ TEST ("destroy")
|
|||
ECHO ("destroyed");
|
||||
}
|
||||
|
||||
|
||||
TEST ("clusters")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, sizeof(void*), 2, move, dtor);
|
||||
mpool_init (&mypool, sizeof(void*), 2, dtor);
|
||||
ECHO ("initialized");
|
||||
|
||||
for (int i = 1; i <= 5; ++i)
|
||||
|
|
@ -102,7 +110,7 @@ TEST ("clusters")
|
|||
TEST ("clusters_big")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, sizeof(void*), 200, move, dtor);
|
||||
mpool_init (&mypool, sizeof(void*), 200, dtor);
|
||||
ECHO ("initialized");
|
||||
|
||||
for (int i = 1; i <= 700; ++i)
|
||||
|
|
@ -120,10 +128,10 @@ TEST ("clusters_big")
|
|||
}
|
||||
|
||||
|
||||
TEST ("collect")
|
||||
TEST ("alloc_free")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, 24, 4, move, dtor);
|
||||
mpool_init (&mypool, 24, 4, dtor);
|
||||
ECHO ("initialized");
|
||||
|
||||
void* elem[32];
|
||||
|
|
@ -142,40 +150,33 @@ TEST ("collect")
|
|||
ECHO ("freed some");
|
||||
DUMP(NOBUG_ON, mpool, &mypool, 4);
|
||||
|
||||
mpool_collect (&mypool, 0);
|
||||
ECHO ("collected");
|
||||
|
||||
DUMP(NOBUG_ON, mpool, &mypool, 4);
|
||||
|
||||
mpool_destroy (&mypool);
|
||||
ECHO ("destroyed");
|
||||
}
|
||||
|
||||
TEST ("collect_no_move")
|
||||
|
||||
TEST ("alloc_free_big")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, 24, 4, NULL, dtor);
|
||||
mpool_init (&mypool, 24, 4, dtor);
|
||||
ECHO ("initialized");
|
||||
|
||||
void* elem[32];
|
||||
void* elem[2000];
|
||||
|
||||
for (int i = 1; i <= 15; ++i)
|
||||
for (int i = 1; i <= 2000; ++i)
|
||||
{
|
||||
elem[i] = mpool_alloc (&mypool);
|
||||
*(int*)(elem[i]) = i;
|
||||
}
|
||||
ECHO ("allocated");
|
||||
|
||||
for (int i = 1; i <= 15; i+=3)
|
||||
for (int i = 1; i <= 2000; i+=3)
|
||||
{
|
||||
mpool_free (&mypool, elem[i]);
|
||||
}
|
||||
ECHO ("freed some");
|
||||
DUMP(NOBUG_ON, mpool, &mypool, 4);
|
||||
|
||||
mpool_collect (&mypool, 0);
|
||||
ECHO ("collected");
|
||||
|
||||
DUMP(NOBUG_ON, mpool, &mypool, 4);
|
||||
|
||||
mpool_destroy (&mypool);
|
||||
|
|
@ -183,9 +184,160 @@ TEST ("collect_no_move")
|
|||
}
|
||||
|
||||
|
||||
|
||||
TEST ("reserve")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
benchmark mpool vs malloc, first only the allocation/free itself with some necessary llist ops
|
||||
*/
|
||||
TEST ("bench_mpool")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, sizeof(struct teststruct), 2000, NULL);
|
||||
ECHO ("initialized");
|
||||
|
||||
llist list;
|
||||
llist_init (&list);
|
||||
|
||||
for (int j = 1; j<=100; ++j)
|
||||
{
|
||||
for (int i = 1; i <= 50000; ++i)
|
||||
{
|
||||
struct teststruct* element = mpool_alloc (&mypool);
|
||||
llist_insert_tail (&list, llist_init (&element->node));
|
||||
}
|
||||
|
||||
LLIST_WHILE_HEAD (&list, element)
|
||||
{
|
||||
llist_unlink_fast_ (element);
|
||||
mpool_free (&mypool, element);
|
||||
}
|
||||
}
|
||||
|
||||
mpool_destroy (&mypool);
|
||||
ECHO ("destroyed");
|
||||
}
|
||||
|
||||
|
||||
TEST ("bench_malloc")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, sizeof(llist), 2000, NULL);
|
||||
ECHO ("initialized");
|
||||
|
||||
llist list;
|
||||
llist_init (&list);
|
||||
|
||||
for (int j = 100; j; --j)
|
||||
{
|
||||
for (int i = 1; i <= 50000; ++i)
|
||||
{
|
||||
struct teststruct* element = malloc (sizeof(*element));
|
||||
llist_insert_tail (&list, llist_init (&element->node));
|
||||
}
|
||||
|
||||
LLIST_WHILE_HEAD (&list, element)
|
||||
{
|
||||
llist_unlink_fast_ (element);
|
||||
free (element);
|
||||
}
|
||||
}
|
||||
|
||||
mpool_destroy (&mypool);
|
||||
ECHO ("destroyed");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
benchmark mpool vs malloc, try to simulate some slightly more realistic application usage
|
||||
- allocate list nodes which have 2 data members as payload
|
||||
- there is a 25% chance at each alloc that the head of the list gets deleted
|
||||
*/
|
||||
TEST ("bench_mpool_sim")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, sizeof(struct teststruct), 2000, NULL);
|
||||
ECHO ("initialized");
|
||||
|
||||
llist list;
|
||||
llist_init (&list);
|
||||
|
||||
for (int j = 1; j<=100; ++j)
|
||||
{
|
||||
for (int i = 1; i <= 50000; ++i)
|
||||
{
|
||||
struct teststruct* element = mpool_alloc (&mypool);
|
||||
llist_insert_tail (&list, llist_init (&element->node));
|
||||
element->ptr[0] = malloc(100+(mpool_fast_prng()%500));
|
||||
element->ptr[1] = malloc(100+(mpool_fast_prng()%500));
|
||||
|
||||
if (!(mpool_fast_prng()%4))
|
||||
{
|
||||
struct teststruct* element = (struct teststruct*)llist_head (&list);
|
||||
llist_unlink_fast_ (&element->node);
|
||||
free(element->ptr[0]);
|
||||
free(element->ptr[1]);
|
||||
mpool_free (&mypool, element);
|
||||
}
|
||||
}
|
||||
|
||||
LLIST_WHILE_HEAD (&list, element)
|
||||
{
|
||||
llist_unlink_fast_ (element);
|
||||
free(((struct teststruct*)element)->ptr[0]);
|
||||
free(((struct teststruct*)element)->ptr[1]);
|
||||
mpool_free (&mypool, element);
|
||||
}
|
||||
}
|
||||
|
||||
mpool_destroy (&mypool);
|
||||
ECHO ("destroyed");
|
||||
}
|
||||
|
||||
|
||||
TEST ("bench_malloc_sim")
|
||||
{
|
||||
mpool mypool;
|
||||
mpool_init (&mypool, sizeof(llist), 2000, NULL);
|
||||
ECHO ("initialized");
|
||||
|
||||
llist list;
|
||||
llist_init (&list);
|
||||
|
||||
for (int j = 100; j; --j)
|
||||
{
|
||||
for (int i = 1; i <= 50000; ++i)
|
||||
{
|
||||
struct teststruct* element = malloc (sizeof(*element));
|
||||
llist_insert_tail (&list, llist_init (&element->node));
|
||||
element->ptr[0] = malloc(100+(mpool_fast_prng()%500));
|
||||
element->ptr[1] = malloc(100+(mpool_fast_prng()%500));
|
||||
|
||||
if (!(mpool_fast_prng()%4))
|
||||
{
|
||||
struct teststruct* element = (struct teststruct*)llist_head (&list);
|
||||
llist_unlink_fast_ (&element->node);
|
||||
free(element->ptr[0]);
|
||||
free(element->ptr[1]);
|
||||
free (element);
|
||||
}
|
||||
}
|
||||
|
||||
LLIST_WHILE_HEAD (&list, element)
|
||||
{
|
||||
llist_unlink_fast_ (element);
|
||||
free(((struct teststruct*)element)->ptr[0]);
|
||||
free(((struct teststruct*)element)->ptr[1]);
|
||||
free (element);
|
||||
}
|
||||
}
|
||||
|
||||
mpool_destroy (&mypool);
|
||||
ECHO ("destroyed");
|
||||
}
|
||||
|
||||
|
||||
TESTS_END
|
||||
|
|
|
|||
Loading…
Reference in a new issue