Commit 5a0de7cb authored by Pekka Pessi's avatar Pekka Pessi
Browse files

sofia-sip/heap.h: optimized the implementation

Reduced average number of comparisons and assignments when removing an
entry. Improved tests.

darcs-hash:20070703112744-65a35-64852a7e7fbf38b4a11e8af70d481721259e8ff2.gz
parent bf480410
/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
* Copyright (C) 2007 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
......@@ -28,35 +28,47 @@
/**@file sofia-sip/heap.h
*
* Heap implementation.
* Heap implemented with dynamic array.
*
* Note: this version can handle structures as entries, and it can be used
* without <su_alloc.h>.
*
* This file contain a hash table template for C. The hash tables are
* resizeable, and they usually contain pointers to entries. The
* declaration for template datatypes is instantiated with macro
* HEAP_DECLARE(). The prototypes for hashing functions are instantiated
* with macro HEAP_PROTOS(). The implementation is instantiated with
* macro HEAP_BODIES().
*
* The hash table template is most efficient when the hash value is
* precalculated and stored in each entry. The hash "function" given to the
* HEAP_BODIES() would then be something like macro
* @code
* #define HEAP_ENTRY_HASH(e) ((e).e_hash_value)
* @endcode
*
* When a entry with new identical hash key is added to the table, it can be
* either @e inserted (before any other entry with same key value) or
* @e appended.
*
* Example code can be found from <htable_test.c>.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>.
* This file contain template macros implementing heap in C. The @a heap
* keeps its element in a known order and it can be used to implement, for
* example, a prioritye queue or an ordered queue.
*
* The ordering within the heap is defined as follows:
* - for each element with index @a [i] in the heap there are two descendant
* elements with indices @a [2*i+1] and @a [2*i+2],
* - the heap guarantees that the descendant elements are never smaller than
* their parent element.
* There is no element smaller than element at index [0] in the
* rest of the heap.
*
* Adding and removing elements to the heap is an @a O(logN)
* operation.
*
* @date Created: Tue Sep 25 17:42:40 2001 ppessi
* The heap array is resizeable, and it usually contain pointers to the
* actual entries. The template macros define two functions used to add and
* remove entries to the heap. The @a add() function takes the element to be
* added as its argument, the @a remove() function the index of the element
* to be removed. The template defines also a predicate used to check if the
* heap is full, and a function used to resize the heap.
*
* The heap user must define three primitives:
* - less than comparison
* - array setter
* - heap array allocator
*
* Please note that in order to remove an entry in the heap, the application
* must know its index in the heap array.
*
* The heap struct is declared with macro HEAP_DECLARE(). The prototypes for
* heap functions are instantiated with macro HEAP_PROTOS(). The
* implementation is instantiated with macro HEAP_BODIES().
*
* Example code can be found from <su/torture_heap.c> and
* <sresolv/sres_cache.c>.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>.
* @NEW_1_12_7.
*/
/** Minimum size of heap */
......@@ -64,55 +76,85 @@
/** Declare heap structure type.
*
* The macro HEAP_DECLARE() expands to a declaration for heap
* structure. The its typedef will be <em>prefix</em><code>_t</code>, the
* field names start with @a pr. The entry type is @a entrytype.
* The macro HEAP_DECLARE() expands to the declaration of the heap
* structure. The field names start with @a pr. The type of heap array
* element is @a entrytype.
*
* @param sname name of struct
* @param pr heap type prefix
* @param entrytype entry type
*
* @showinitializer
*/
#define HEAP_DECLARE(sname, pr, entrytype) \
struct sname { \
unsigned pr##size; \
unsigned pr##used; \
entrytype *pr##heap; /**< Heap table itself */ \
size_t pr##size; /**< Number of elements in pr##heap */ \
size_t pr##used; /**< Number of elements used from pr##heap */ \
entrytype *pr##heap; /**< Array of entries in the heap */ \
}
/** Prototypes for heap.
*
* The macro HEAP_PROTOS() expands to the prototypes of heap
* functions. The function and type names start with @a prefix, the field
* names start with @a pr. The entry type is @a entrytype.
* The macro HEAP_PROTOS() expands to the prototypes of heap functions:
* - prefix ## resize(argument, heap, size)
* - prefix ## is_full(heap)
* - prefix ## add(heap, entry)
* - prefix ## remove(heap, index)
*
* @param scope scope of functions
* @param type heap type or typedef
* @param prefix function prefix
* @param entrytype entry type
*
* The declared functions will have scope @a scope (for example, @c static
* or @c static inline). The declared function names will have prefix @a
* prefix. The heap structure has type @a type. The heap element type is @a
* entrytype.
*
* @showinitializer
*/
#define HEAP_PROTOS(scope, type, prefix, entrytype) \
scope int prefix##resize(void *a, type pr[1], size_t); \
scope int prefix##is_full(type const *); \
scope int prefix##add(type *pr, entrytype e); \
scope int prefix##remove(type *, size_t index)
scope int prefix##resize(void *argument, type heap[1], size_t size); \
scope int prefix##is_full(type const *heap); \
scope int prefix##add(type *heap, entrytype entry); \
scope int prefix##remove(type *heap, size_t index)
/** Hash table implementation.
/**Heap implementation.
*
* The macro HEAP_BODIES() expands the heap functions. The function
* and type names start with @a prefix, the field names start with @a pr.
* The entry type is @a entrytype. The function (or macro) name returning
* hash value of each entry is given as @a hfun.
* The macro HEAP_BODIES() expands to the bodies of heap functions:
* - prefix ## resize(argument, heap, size)
* - prefix ## is_full(heap)
* - prefix ## add(heap, entry)
* - prefix ## remove(heap, index)
*
* @param scope scope of functions
* @param type hash table type
* @param prefix function prefix for heap
* @param pr field prefix for heap structure
* @param entrytype type of element
* @param cmp function or macro comparing two entries
* @param less function or macro comparing two entries
* @param set function or macro assigning entry to array
* @param halloc function allocating or freeing memory
*
* Functions have scope @a scope, e.g., @c static @c inline.
* The heap structure has type @a type.
* The function names start with @a prefix, the field names start
* with @a pr. The entry type is @a entrytype.
* The function (or macro) @a less compares two entries in heap. It gets two
* arguments and it returns true if its left argument is less than its right
* argument.
* The function (or macro) @a set stores an entry in heap array. It gets
* three arguments, first is heap array, second index to the array and third
* the element to store at the given index.
*
* The function (or macro) @a halloc re-allocates the heap array. It
* receives three arguments, first is the first @a argument given to @a
* resize(), second the pointer to existing heap and third is the number of
* bytes in the heap.
*/
#define HEAP_BODIES(scope, type, prefix, pr, entrytype, cmp, set, alloc) \
#define HEAP_BODIES(scope, type, prefix, pr, entrytype, less, set, alloc) \
/** Resize heap. */ \
scope int prefix##resize(void *realloc_arg, \
type pr[1], \
......@@ -120,103 +162,101 @@ scope int prefix##resize(void *realloc_arg, \
{ \
entrytype *heap; \
size_t bytes; \
\
\
(void)realloc_arg; \
\
\
if (new_size == 0) \
new_size = 2 * pr->pr##size + 1; \
if (new_size < HEAP_MIN_SIZE) \
new_size = HEAP_MIN_SIZE; \
\
\
bytes = new_size * (sizeof heap[0]); \
\
\
heap = alloc(realloc_arg, pr->pr##heap, bytes); \
if (!heap) \
return -1; \
\
\
pr->pr##size = new_size; \
if (pr->pr##used > new_size) \
pr->pr##used = new_size; \
pr->pr##heap = heap; \
\
\
return 0; \
} \
\
\
/** Check if heap is full */ \
scope \
int prefix##is_full(type const *pr) \
{ \
return pr->pr##heap == NULL || pr->pr##used >= pr->pr##size; \
} \
\
/** Sort heap from element at index upwards */ \
scope \
void prefix##sort(type *pr, size_t index) \
{ \
size_t top, left, right; \
entrytype *heap = pr->pr##heap; \
size_t used = pr->pr##used; \
\
top = index; \
\
for (;;) { \
entrytype swap; \
\
left = 2 * top; \
right = left + 1; \
\
if (left < used && cmp(heap[top], heap[left]) > 0) \
top = left; \
if (right < used && cmp(heap[top], heap[right]) > 0) \
top = right; \
\
if (top == index) \
break; \
\
swap = heap[index]; \
set(heap, index, heap[top]); \
set(heap, top, swap); \
index = top; \
} \
} \
\
\
/** Add an element to the heap */ \
scope \
int prefix##add(type *pr, entrytype e) \
{ \
size_t i, parent; \
entrytype *heap = pr->pr##heap; \
\
\
if (pr->pr##used >= pr->pr##size) \
return -1; \
\
\
for (i = pr->pr##used++; i > 0; i = parent) { \
parent = i / 2; \
if (cmp(e, heap[parent]) >= 0) \
parent = (i - 1) / 2; \
if (!less(e, heap[parent])) \
break; \
set(heap, i, heap[parent]); \
} \
\
\
set(heap, i, e); \
\
\
return 0; \
} \
\
\
/** Remove element from heap */ \
scope \
int prefix##remove(type *pr, size_t index) \
{ \
entrytype *heap = pr->pr##heap; \
\
if (index >= pr->pr##used) \
entrytype e; \
size_t top, left, right; \
size_t used = pr->pr##used; \
\
if (index >= used) \
return -1; \
\
set(heap, index, heap[--pr->pr##used]); \
\
prefix##sort(pr, index); \
\
\
pr->pr##used = --used; \
top = index; \
\
for (;;) { \
left = 2 * top + 1; \
right = 2 * top + 2; \
\
if (right >= used) \
break; \
if (less(heap[right], heap[left])) \
top = right; \
else \
top = left; \
set(heap, index, heap[top]); \
index = top; \
} \
\
if (index == used) \
return 0; \
\
e = heap[used]; \
for (; index > 0; index = top) { \
top = (index - 1) / 2; \
if (!less(e, heap[top])) \
break; \
set(heap, index, heap[top]); \
} \
\
set(heap, index, e); \
\
return 0; \
} \
extern int const prefix##dummy
extern int const prefix##dummy_heap
#endif /** !defined(HEAP_H) */
#endif /** !defined(SOFIA_SIP_HEAP_H) */
......@@ -22,14 +22,9 @@ HEAP_DECLARE(Heap, pr_, entrytype);
HEAP_PROTOS(static inline, Heap, heapXX_, entrytype);
static inline
int cmp_entry(entrytype a, entrytype b)
int less_than(entrytype a, entrytype b)
{
if (a.key < b.key)
return -1;
else if (a.key > b.key)
return 1;
else
return 0;
return a.key < b.key;
}
static inline
......@@ -42,7 +37,7 @@ void set_entry(entrytype *heap, size_t index, entrytype entry)
#define alloc(a, o, size) realloc((o), (size))
HEAP_BODIES(static inline, Heap, heapXX_, pr_, entrytype,
cmp_entry, set_entry, alloc);
less_than, set_entry, alloc);
/* ====================================================================== */
......@@ -60,31 +55,40 @@ int test_speed()
Heap heap[1];
unsigned i, previous, n, N;
unsigned char *tests;
N = 300000;
memset(heap, 0, sizeof heap);
TEST(heapXX_resize(NULL, heap, 0), 0);
TEST_1(tests = calloc(sizeof (unsigned char), N + 1));
N = 300000;
TEST(heapXX_resize(NULL, heap, 0), 0);
/* Add N entries in reverse order */
for (i = N; i > 0; i--) {
entrytype e = { i / 10, i };
if (heapXX_is_full(heap))
TEST(heapXX_resize(NULL, heap, 0), 0);
TEST(heapXX_is_full(heap), 0);
TEST(heapXX_add(heap, e), 0);
tests[i] |= 1;
}
TEST(heap->pr_used, N);
for (i = 0; i < N; i++) {
TEST(heap->pr_heap[i].index, i);
TEST(tests[heap->pr_heap[i].value] & 2, 0);
tests[heap->pr_heap[i].value] |= 2;
}
for (i = 0; i < N; i++) {
heapXX_sort(heap, i);
size_t left = 2 * i + 1, right = left + 1;
if (left < heap->pr_used)
TEST_1(heap->pr_heap[i].key <= heap->pr_heap[left].key);
if (right < heap->pr_used)
TEST_1(heap->pr_heap[i].key <= heap->pr_heap[right].key);
}
for (i = 0; i < N; i++) {
......@@ -96,11 +100,57 @@ int test_speed()
for (n = 0; heap->pr_used > 0; n++) {
TEST_1(previous <= heap->pr_heap[0].key);
TEST(tests[heap->pr_heap[0].value] & 4, 0);
tests[heap->pr_heap[0].value] |= 4;
previous = heap->pr_heap[0].key;
heapXX_remove(heap, 0);
TEST(heapXX_remove(heap, 0), 0);
}
TEST(n, N);
/* Add N entries in reverse order */
for (i = N; i > 0; i--) {
entrytype e = { i / 10, i };
if (heapXX_is_full(heap))
TEST(heapXX_resize(NULL, heap, 0), 0);
TEST(heapXX_is_full(heap), 0);
TEST(heapXX_add(heap, e), 0);
}
TEST(heap->pr_used, N);
/* Remove 1000 entries from random places */
previous = 0;
for (i = 0; i < 1000 && heap->pr_used > 0; i++) {
n = i * 397651 % heap->pr_used;
TEST(tests[heap->pr_heap[n].value] & 8, 0);
tests[heap->pr_heap[n].value] |= 8;
TEST(heapXX_remove(heap, n), 0);
}
for (i = 0; i < N; i++) {
size_t left = 2 * i + 1, right = left + 1;
if (left < heap->pr_used)
TEST_1(heap->pr_heap[i].key <= heap->pr_heap[left].key);
if (right < heap->pr_used)
TEST_1(heap->pr_heap[i].key <= heap->pr_heap[right].key);
}
/* Remove rest */
for (n = 0, previous = 0; heap->pr_used > 0; n++) {
TEST(tests[heap->pr_heap[0].value] & 8, 0);
tests[heap->pr_heap[0].value] |= 8;
TEST_1(previous <= heap->pr_heap[0].key);
previous = heap->pr_heap[0].key;
heapXX_remove(heap, 0);
}
for (i = 1; i <= N; i++) {
TEST(tests[i], 8 | 4 | 2 | 1);
}
TEST(heapXX_resize(NULL, heap, 31), 0);
END();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment