diff --git a/src/include/logging.h b/src/include/logging.h index 9a07cbabf..c472bca39 100644 --- a/src/include/logging.h +++ b/src/include/logging.h @@ -99,6 +99,7 @@ NOBUG_CPP_DEFINE_FLAG_PARENT ( gui_dbg, debugging); NOBUG_CPP_DEFINE_FLAG_PARENT ( library_dbg, debugging); NOBUG_CPP_DEFINE_FLAG_PARENT ( mpool_dbg, library_dbg); NOBUG_CPP_DEFINE_FLAG_PARENT ( psplay_dbg, library_dbg); +NOBUG_CPP_DEFINE_FLAG_PARENT ( priqueue, library_dbg); NOBUG_CPP_DEFINE_FLAG_PARENT ( resourcecollector_dbg, library_dbg); NOBUG_CPP_DEFINE_FLAG_PARENT ( mutex_dbg, library_dbg); NOBUG_CPP_DEFINE_FLAG_PARENT ( cond_dbg, library_dbg); diff --git a/src/lib/priqueue.c b/src/lib/priqueue.c index 3db1034b5..8e957b967 100644 --- a/src/lib/priqueue.c +++ b/src/lib/priqueue.c @@ -1,8 +1,8 @@ /* - priqueue - priority queue + PriQueue - simple heap based priority queue - Copyright (C) - 2011 Christian Thaeter + Copyright (C) Lumiera.org + 2011, Christian Thaeter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -17,16 +17,18 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ -#include "priqueue.h" +* *****************************************************/ -#include +#include "lib/priqueue.h" +#include "include/logging.h" + #include +#include + -NOBUG_DEFINE_FLAG (priqueue); PriQueue @@ -36,7 +38,6 @@ priqueue_init (PriQueue self, priqueue_copy_fn copyfn, priqueue_resize_fn resizefn) { - NOBUG_INIT_FLAG (priqueue); TRACE (priqueue, "%p", self); REQUIRE (element_size); @@ -78,6 +79,7 @@ priqueue_destroy (PriQueue self) } + PriQueue priqueue_reserve (PriQueue self, unsigned elements) { @@ -102,9 +104,7 @@ priqueue_reserve (PriQueue self, unsigned elements) } -/* - supplied/default resize function based on realloc -*/ + PriQueue priqueue_clib_resize (PriQueue self) { diff --git a/src/lib/priqueue.h b/src/lib/priqueue.h index b74062d80..3f6bba002 100644 --- a/src/lib/priqueue.h +++ b/src/lib/priqueue.h @@ -1,8 +1,8 @@ /* - priqueue - priority queue + PRIQUEUE.h - simple heap based priority queue - Copyright (C) - 2011 Christian Thaeter + Copyright (C) Lumiera.org + 2011, Christian Thaeter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -17,54 +17,67 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ -#ifndef PRIQUEUE_H -#define PRIQUEUE_H - -/* - This is a very minimalistic priority queue implementation based on a binary heap. Only 'insert' 'remove' and 'peek' operations are supported - Memory is dynamically managed through an optionally user supplied 'resize' function. - Elements in the queue have a user-define size but should kept as small as possible. This is only intended to associate lightweight data such as - a key and a pointer, storing the key in the element can save dereferencing cost and thus improve cache locality. It must be noted - that elements in the queue get moved in memory, so referencing them is invalid. - - There are API's (yet) to change the priority of an arbitary element or remove any but the topmost element. The idea is to let expired elements - sink to the top and detect that and then remove them. -*/ +/** @file priqueue.h + ** Simple priority queue implementation based on a binary heap. + ** Only 'insert' 'remove' and 'peek' operations are supported. + ** Memory is dynamically managed through an optionally user supplied 'resize' function. + ** Elements in the queue have a user-define size but should kept as small as possible. + ** This is only intended to associate lightweight data such as a key and a pointer, + ** storing the key in the element can save dereferencing cost and thus improve + ** cache locality. + ** + ** @warning elements in the queue get moved in memory, so referencing them is not allowed. + ** + ** @todo we might add operations to change the priority of an arbitrary element or remove + ** any but the topmost element. The idea is to let expired elements sink to the top + ** and just detect and remove them on next access. + ** + ** @see backend::engine::SchedulerFrontend + ** + */ -#include +#ifndef LIB_PRIQUEUE_H +#define LIB_PRIQUEUE_H + + +#include "lib/error.h" + typedef struct priqueue_struct priqueue; typedef priqueue* PriQueue; -/* function to compare 2 keys, mandatory */ +/** function to compare 2 keys, mandatory */ typedef int (*priqueue_cmp_fn)(void*, void*); -/* function to copy elements, optional. Has the same prototype as memcpy which is used by default */ +/** function to copy elements, optional. + * Has the same prototype as memcpy which is used by default */ typedef void *(*priqueue_copy_fn)(void *dest, const void *src, size_t n); -/* called when used hits the high or low water marks and initially by priqueue_init() (with queue==NULL) - or at priqueue_destroy (with queue != NULL, used elements == 0), - optional. - - Must be aware of resizes by more than just incrementing the queue by one +/** called when used hits the high or low water marks and initially by priqueue_init() (with queue==NULL) + * or at priqueue_destroy (with queue != NULL, used elements == 0), optional. + * + * @note must be aware of resizes by more than just incrementing the queue by one */ typedef PriQueue (*priqueue_resize_fn) (PriQueue); -/* - this structure is not opaque to make it possible to implement a low level resize operation which has to reallocate - the queue and update the high and low water marks. +/** + * @remarks this structure is not opaque to make it possible + * to implement a low level resize operation + * which has to reallocate the queue and update + * the high and low water marks. */ struct priqueue_struct { void* queue; size_t element_size; unsigned used; - unsigned high_water; // elements in the queue - unsigned low_water; // size for shrinking the queue + unsigned high_water; ///< elements in the queue + unsigned low_water; ///< size for shrinking the queue priqueue_cmp_fn cmpfn; priqueue_copy_fn copyfn; @@ -90,40 +103,41 @@ PriQueue priqueue_destroy (PriQueue self); -/* - calls resize to match for at least 'elements' in the queue and then sets low_water to 0, disabling shrinking - note that on overflow resize will re-enable low_water if it is not aware of this -*/ +/** + * calls resize to match for at least 'elements' in the queue + * and then sets low_water to 0, disabling shrinking + * @note on overflow resize will re-enable low_water if it is not aware of this + */ PriQueue priqueue_reserve (PriQueue self, unsigned elements); -/* - supplied/default resize function based on realloc - initially allocates an array for 64 elements, - doubles this when the high water mark is hit, - shrinks at high_water/8-8 (that is, 64 is the minimum size) -*/ +/** + * supplied/default resize function based on realloc + * initially allocates an array for 64 elements, + * doubles this when the high water mark is hit, + * shrinks at high_water/8-8 (that is, 64 is the minimum size) + */ PriQueue priqueue_clib_resize (PriQueue self); -/* - insert a new element into the priority queue - the element will be copied - returns NULL on error -*/ +/** + * insert a new element into the priority queue + * the element will be copied + * @return \c NULL on error + */ PriQueue priqueue_insert (PriQueue self, void* element); -/* - returns a pointer to the topmost element - note that this pointer is only valid as long no insert or remove is called - returns NULL when the queue is empty -*/ +/** + * @return pointer to the topmost element, \NULL on empty queue + * @note returned pointer is only valid as long + * as no insert or remove is called + */ static inline void* priqueue_peek (PriQueue self) { @@ -134,14 +148,13 @@ priqueue_peek (PriQueue self) } -/* - removes the topmost element - returns NULL on error (queue emtpy, resize failure) +/** + * removes the topmost element + * @return \c NULL on error (empty queue, resize failure) */ PriQueue priqueue_remove (PriQueue self); -#endif - +#endif/*LIB_PRIQUEUE_H*/ diff --git a/tests/library/c-lib/test-priqueue.c b/tests/library/c-lib/test-priqueue.c new file mode 100644 index 000000000..8e957b967 --- /dev/null +++ b/tests/library/c-lib/test-priqueue.c @@ -0,0 +1,390 @@ +/* + PriQueue - simple heap based priority queue + + Copyright (C) Lumiera.org + 2011, Christian Thaeter + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +#include "lib/priqueue.h" +#include "include/logging.h" + +#include +#include + + + + + +PriQueue +priqueue_init (PriQueue self, + size_t element_size, + priqueue_cmp_fn cmpfn, + priqueue_copy_fn copyfn, + priqueue_resize_fn resizefn) +{ + TRACE (priqueue, "%p", self); + + REQUIRE (element_size); + REQUIRE (cmpfn); + + if (self) + { + self->queue = NULL; + self->element_size = element_size; + self->used = self->high_water = self->low_water = 0; + self->cmpfn = cmpfn; + + if (!copyfn) + copyfn = memcpy; + self->copyfn = copyfn; + + if (!resizefn) + resizefn = priqueue_clib_resize; + self->resizefn = resizefn; + + self = self->resizefn (self); + } + return self; +} + + + +PriQueue +priqueue_destroy (PriQueue self) +{ + TRACE (priqueue, "%p", self); + if (self) + { + WARN_IF (self->used, priqueue, "queue was not empty"); + self->used = 0; + self = self->resizefn (self); + } + return self; +} + + + +PriQueue +priqueue_reserve (PriQueue self, unsigned elements) +{ + TRACE (priqueue, "%p %d", self, elements); + if (self) + { + if (self->used+elements >= self->high_water) + { + self->used += elements; + self = self->resizefn (self); + if (!self) + { + ERROR (priqueue, "resize failed"); + return NULL; + } + self->used -= elements; + } + self->low_water = 0; + } + + return self; +} + + + +PriQueue +priqueue_clib_resize (PriQueue self) +{ + if (self) + { + if (!self->queue) + { + INFO (priqueue, "%p: initial alloc", self); + self->queue = malloc (64*self->element_size); + ERROR_IF (!self->queue, priqueue, "%p: allocation failed", self); + if (!self->queue) + return NULL; + self->high_water = 64; + } + else + { + if (self->used) + { + if (self->used >= self->high_water) + { + unsigned newwater = self->high_water; + while (self->used >= newwater) + newwater *= 2; + + INFO (priqueue, "%p: resize %d -> %d", self, self->high_water, newwater); + + void* newqueue = realloc (self->queue, self->element_size * newwater); + ERROR_IF (!newqueue, priqueue, "%p: allocation failed", self); + if (!newqueue) + return NULL; + self->queue = newqueue; + self->high_water = newwater; + self->low_water = self->high_water/8-8; + TRACE (priqueue, "%p: low_water: %d", self, self->low_water); + } + else + { + INFO (priqueue, "%p: shrink %d -> %d", self, self->high_water, (self->low_water+8)*4); + void* newqueue = realloc (self->queue, self->element_size * (self->low_water+8)*4); + + ERROR_IF (!newqueue, priqueue, "allocation failed"); + if (!newqueue) + return NULL; + self->queue = newqueue; + self->high_water = (self->low_water+8)*4; + self->low_water = self->high_water/8-8; + TRACE (priqueue, "%p: low_water: %d", self, self->low_water); + } + } + else + { + INFO (priqueue, "%p: freeing", self); + free (self->queue); + self->queue = NULL; + } + } + } + return self; +} + + + +static inline void* +pq_index (PriQueue self, unsigned nth) +{ + return (char*)self->queue+self->element_size*nth; +} + + +static inline void +pq_up (PriQueue self, void* tmp) +{ + unsigned i = self->used; + unsigned p = i/2; + + while (p && self->cmpfn (tmp, pq_index(self, p-1)) < 0) + { + self->copyfn (pq_index (self, i-1), pq_index (self, p-1), self->element_size); + i=p; p=i/2; + } + + self->copyfn (pq_index (self, i-1), tmp, self->element_size); +} + + + +PriQueue +priqueue_insert (PriQueue self, void* element) +{ + TRACE (priqueue, "%p: insert %p", self, element); + + if (self && self->used >= self->high_water) + self = self->resizefn (self); + + if (self) + { + ++self->used; + pq_up (self, element); + } + return self; +} + + + + +static inline void +pq_down (PriQueue self, void* tmp) +{ + if (!self->used) + return; + + unsigned i = 1; + + while (i <= self->used/2) + { + unsigned n=i+i; + if (nused && self->cmpfn (pq_index(self, n-1), pq_index(self, n)) >= 0) + ++n; + + if (self->cmpfn (tmp, pq_index(self, n-1)) < 0) + break; + + self->copyfn (pq_index (self, i-1), pq_index (self, n-1), self->element_size); + i = n; + } + self->copyfn (pq_index (self, i-1), tmp, self->element_size); +} + + +PriQueue +priqueue_remove (PriQueue self) +{ + TRACE (priqueue, "%p: remove", self); + + if (self) + { + if (!self->used) + return NULL; + + --self->used; + pq_down (self, pq_index (self, self->used)); + + if (self->used < self->low_water) + self = self->resizefn (self); + } + + return self; +} + + + + + + + + + +#ifdef PRIQUEUE_TEST /* testing */ + +#include + + +void +nobug_priqueue_invariant (PriQueue self, int depth, const struct nobug_context invariant_context, void* extra) +{ + intptr_t n = 1+(intptr_t)extra; + + intptr_t m=n+n; + + if (self && depth && m <= self->used) + { + INVARIANT_ASSERT (self->cmpfn (pq_index(self, n-1), pq_index(self, m-1)) <= 0, "%d %d", (int)n-1, (int)m-2); + nobug_priqueue_invariant (self, depth-1, invariant_context, (void*)m-1); + + if (mused) + { + INVARIANT_ASSERT (self->cmpfn (pq_index(self, n-1), pq_index(self, m)) <= 0, "%d %d", (int)n-1, (int)m-1); + nobug_priqueue_invariant (self, depth-1, invariant_context, (void*)m); + } + } +} + + +static int +cmpintptr (void* a, void* b) +{ + return *(int*)a - *(int*)b; +} + +NOBUG_DEFINE_FLAG (priqueue_test); + +int main() +{ + NOBUG_INIT; + NOBUG_INIT_FLAG (priqueue_test); + + priqueue pq; + + PriQueue r; + + int data; + + r = priqueue_init (&pq, + sizeof (int), + cmpintptr, + NULL, + NULL); + ENSURE (r==&pq); + +#if 1 + data = 10; + r = priqueue_insert (&pq, &data); + ENSURE (r==&pq); + TRACE (priqueue_test, "inserted %d", data); +#endif + +#if 0 + data = 5; + r = priqueue_insert (&pq, &data); + ENSURE (r==&pq); + TRACE (priqueue_test, "inserted %d", data); +#endif + + +#if 0 + data = 15; + r = priqueue_insert (&pq, &data); + ENSURE (r==&pq); + TRACE (priqueue_test, "inserted %d", data); + + data = 20; + r = priqueue_insert (&pq, &data); + ENSURE (r==&pq); + TRACE (priqueue_test, "inserted %d", data); +#endif + + + + +#if 1 + for (int i = 0; i < 1000000; ++i) + { + data = i; + r = priqueue_insert (&pq, &data); + ENSURE (r==&pq); + TRACE (priqueue_test, "inserted %d", data); + } + +#endif + +#if 1 + for (int i = 0; i < 1000000; ++i) + { + data = rand()%1000000; + r = priqueue_insert (&pq, &data); + ENSURE (r==&pq); + TRACE (priqueue_test, "inserted %d", data); + } +#endif + + NOBUG_INVARIANT(priqueue, &pq, 100, NULL); + + +#if 1 + for (int i = 0; pq.used; ++i) + { + TRACE (priqueue_test, "TOP: %d", *(int*)priqueue_peek (&pq)); + r = priqueue_remove (&pq); + ENSURE (r==&pq); + } +#endif + + + r = priqueue_destroy (&pq); + ENSURE (r==&pq); + + return 0; +} + + + + +#endif