diff --git a/src/lib/mpool.c b/src/lib/mpool.c index eafa2d8b3..8a40f3682 100644 --- a/src/lib/mpool.c +++ b/src/lib/mpool.c @@ -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) diff --git a/src/lib/mpool.h b/src/lib/mpool.h index f70915827..18c34dca8 100644 --- a/src/lib/mpool.h +++ b/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: +*/ diff --git a/tests/library/test-mpool.c b/tests/library/test-mpool.c index f2d66349d..8e3bcd8a4 100644 --- a/tests/library/test-mpool.c +++ b/tests/library/test-mpool.c @@ -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