From: Ben Pfaff Date: Tue, 18 Jun 2013 17:27:34 +0000 (-0700) Subject: hindex: New data structure for hashed multimaps. X-Git-Tag: sliver-openvswitch-1.10.90-3~6^2~100 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;ds=sidebyside;h=822b7f52108e1936f68fe2f726d0796df1b19903;p=sliver-openvswitch.git hindex: New data structure for hashed multimaps. Signed-off-by: Ben Pfaff --- diff --git a/lib/automake.mk b/lib/automake.mk index 8b4cd9279..eec71dd6d 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010, 2011, 2012 Nicira, Inc. +# Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicira, Inc. # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright @@ -57,6 +57,8 @@ lib_libopenvswitch_a_SOURCES = \ lib/flow.h \ lib/hash.c \ lib/hash.h \ + lib/hindex.c \ + lib/hindex.h \ lib/hmap.c \ lib/hmap.h \ lib/hmapx.c \ diff --git a/lib/hindex.c b/lib/hindex.c new file mode 100644 index 000000000..dc0f1b73a --- /dev/null +++ b/lib/hindex.c @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2013 Nicira, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "hindex.h" +#include "coverage.h" + +static bool hindex_node_is_body(const struct hindex_node *); +static bool hindex_node_is_head(const struct hindex_node *); +static void hindex_resize(struct hindex *, size_t new_mask); +static size_t hindex_calc_mask(size_t capacity); + +COVERAGE_DEFINE(hindex_pathological); +COVERAGE_DEFINE(hindex_expand); +COVERAGE_DEFINE(hindex_shrink); +COVERAGE_DEFINE(hindex_reserve); + +/* Initializes 'hindex' as an empty hash index. */ +void +hindex_init(struct hindex *hindex) +{ + hindex->buckets = &hindex->one; + hindex->one = NULL; + hindex->mask = 0; + hindex->n_unique = 0; +} + +/* Frees memory reserved by 'hindex'. It is the client's responsibility to + * free the nodes themselves, if necessary. */ +void +hindex_destroy(struct hindex *hindex) +{ + if (hindex && hindex->buckets != &hindex->one) { + free(hindex->buckets); + } +} + +/* Removes all node from 'hindex', leaving it ready to accept more nodes. Does + * not free memory allocated for 'hindex'. + * + * This function is appropriate when 'hindex' will soon have about as many + * elements as it before. If 'hindex' will likely have fewer elements than + * before, use hindex_destroy() followed by hindex_clear() to save memory and + * iteration time. */ +void +hindex_clear(struct hindex *hindex) +{ + if (hindex->n_unique > 0) { + hindex->n_unique = 0; + memset(hindex->buckets, 0, + (hindex->mask + 1) * sizeof *hindex->buckets); + } +} + +/* Exchanges hash indexes 'a' and 'b'. */ +void +hindex_swap(struct hindex *a, struct hindex *b) +{ + struct hindex tmp = *a; + *a = *b; + *b = tmp; + hindex_moved(a); + hindex_moved(b); +} + +/* Adjusts 'hindex' to compensate for having moved position in memory (e.g. due + * to realloc()). */ +void +hindex_moved(struct hindex *hindex) +{ + if (!hindex->mask) { + hindex->buckets = &hindex->one; + } +} + +/* Expands 'hindex', if necessary, to optimize the performance of searches. */ +void +hindex_expand(struct hindex *hindex) +{ + size_t new_mask = hindex_calc_mask(hindex->n_unique); + if (new_mask > hindex->mask) { + COVERAGE_INC(hindex_expand); + hindex_resize(hindex, new_mask); + } +} + +/* Shrinks 'hindex', if necessary, to optimize the performance of iteration. */ +void +hindex_shrink(struct hindex *hindex) +{ + size_t new_mask = hindex_calc_mask(hindex->n_unique); + if (new_mask < hindex->mask) { + COVERAGE_INC(hindex_shrink); + hindex_resize(hindex, new_mask); + } +} + +/* Expands 'hindex', if necessary, to optimize the performance of searches when + * it has up to 'n' unique hashes. (But iteration will be slow in a hash index + * whose allocated capacity is much higher than its current number of + * nodes.) */ +void +hindex_reserve(struct hindex *hindex, size_t n) +{ + size_t new_mask = hindex_calc_mask(n); + if (new_mask > hindex->mask) { + COVERAGE_INC(hindex_reserve); + hindex_resize(hindex, new_mask); + } +} + +/* Inserts 'node', with the given 'hash', into 'hindex'. Never automatically + * expands 'hindex' (use hindex_insert() instead if you want that). */ +void +hindex_insert_fast(struct hindex *hindex, + struct hindex_node *node, size_t hash) +{ + struct hindex_node *head = hindex_node_with_hash(hindex, hash); + if (head) { + /* 'head' is an existing head with hash == 'hash'. + * Insert 'node' as a body node just below 'head'. */ + node->s = head->s; + node->d = head; + if (node->s) { + node->s->d = node; + } + head->s = node; + } else { + /* No existing node has hash 'hash'. Insert 'node' as a new head in + * its bucket. */ + struct hindex_node **bucket = &hindex->buckets[hash & hindex->mask]; + node->s = NULL; + node->d = *bucket; + *bucket = node; + hindex->n_unique++; + } + node->hash = hash; +} + +/* Inserts 'node', with the given 'hash', into 'hindex', and expands 'hindex' + * if necessary to optimize search performance. */ +void +hindex_insert(struct hindex *hindex, struct hindex_node *node, size_t hash) +{ + hindex_insert_fast(hindex, node, hash); + if (hindex->n_unique / 2 > hindex->mask) { + hindex_expand(hindex); + } +} + +/* Removes 'node' from 'hindex'. Does not shrink the hash index; call + * hindex_shrink() directly if desired. */ +void +hindex_remove(struct hindex *hindex, struct hindex_node *node) +{ + if (!hindex_node_is_head(node)) { + node->d->s = node->s; + if (node->s) { + node->s->d = node->d; + } + } else { + struct hindex_node **head; + + for (head = &hindex->buckets[node->hash & hindex->mask]; + (*head)->hash != node->hash; + head = &(*head)->d) + { + continue; + } + + if (node->s) { + *head = node->s; + node->s->d = node->d; + } else { + *head = node->d; + hindex->n_unique--; + } + } +} + +/* Helper functions. */ + +/* Returns true if 'node', which must be inserted into an hindex, is a "body" + * node, that is, it is not reachable from a bucket by following zero or more + * 'd' pointers. Returns false otherwise. */ +static bool +hindex_node_is_body(const struct hindex_node *node) +{ + return node->d && node->d->hash == node->hash; +} + +/* Returns true if 'node', which must be inserted into an hindex, is a "head" + * node, that is, if it is reachable from a bucket by following zero or more + * 'd' pointers. Returns false if 'node' is a body node (and therefore one + * must follow at least one 's' pointer to reach it). */ +static bool +hindex_node_is_head(const struct hindex_node *node) +{ + return !hindex_node_is_body(node); +} + +/* Reallocates 'hindex''s array of buckets to use bitwise mask 'new_mask'. */ +static void +hindex_resize(struct hindex *hindex, size_t new_mask) +{ + struct hindex tmp; + size_t i; + + ovs_assert(is_pow2(new_mask + 1)); + ovs_assert(new_mask != SIZE_MAX); + + hindex_init(&tmp); + if (new_mask) { + tmp.buckets = xmalloc(sizeof *tmp.buckets * (new_mask + 1)); + tmp.mask = new_mask; + for (i = 0; i <= tmp.mask; i++) { + tmp.buckets[i] = NULL; + } + } + for (i = 0; i <= hindex->mask; i++) { + struct hindex_node *node, *next; + int count; + + count = 0; + for (node = hindex->buckets[i]; node; node = next) { + struct hindex_node **head = &tmp.buckets[node->hash & tmp.mask]; + + next = node->d; + node->d = *head; + *head = node; + count++; + } + if (count > 5) { + COVERAGE_INC(hindex_pathological); + } + } + tmp.n_unique = hindex->n_unique; + hindex_swap(hindex, &tmp); + hindex_destroy(&tmp); +} + +/* Returns the bitwise mask to use in struct hindex to support 'capacity' + * hindex_nodes with unique hashes. */ +static size_t +hindex_calc_mask(size_t capacity) +{ + size_t mask = capacity / 2; + mask |= mask >> 1; + mask |= mask >> 2; + mask |= mask >> 4; + mask |= mask >> 8; + mask |= mask >> 16; +#if SIZE_MAX > UINT32_MAX + mask |= mask >> 32; +#endif + + /* If we need to dynamically allocate buckets we might as well allocate at + * least 4 of them. */ + mask |= (mask & 1) << 1; + + return mask; +} + +/* Returns the head node in 'hindex' with the given 'hash', or a null pointer + * if no nodes have that hash value. */ +struct hindex_node * +hindex_node_with_hash(const struct hindex *hindex, size_t hash) +{ + struct hindex_node *node = hindex->buckets[hash & hindex->mask]; + + while (node && node->hash != hash) { + node = node->d; + } + return node; +} + +static struct hindex_node * +hindex_next__(const struct hindex *hindex, size_t start) +{ + size_t i; + for (i = start; i <= hindex->mask; i++) { + struct hindex_node *node = hindex->buckets[i]; + if (node) { + return node; + } + } + return NULL; +} + +/* Returns the first node in 'hindex', in arbitrary order, or a null pointer if + * 'hindex' is empty. */ +struct hindex_node * +hindex_first(const struct hindex *hindex) +{ + return hindex_next__(hindex, 0); +} + +/* Returns the next node in 'hindex' following 'node', in arbitrary order, or a + * null pointer if 'node' is the last node in 'hindex'. + * + * If the hash index has been reallocated since 'node' was visited, some nodes + * may be skipped or visited twice. (Removing 'node' from the hash index does + * not prevent calling this function, since node->next is preserved, although + * freeing 'node' of course does.) */ +struct hindex_node * +hindex_next(const struct hindex *hindex, const struct hindex_node *node) +{ + return (node->s ? node->s + : node->d && node->d->hash != node->hash ? node->d + : hindex_next__(hindex, (node->hash & hindex->mask) + 1)); +} diff --git a/lib/hindex.h b/lib/hindex.h new file mode 100644 index 000000000..10bf0244d --- /dev/null +++ b/lib/hindex.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2013 Nicira, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HINDEX_H +#define HINDEX_H 1 + +/* Hashed multimap. + * + * hindex is a hash table data structure that gracefully handles duplicates. + * With a high-quality hash function, insertion, deletion, and search are O(1) + * expected time, regardless of the number of duplicates for a given key. */ + +#include +#include +#include "util.h" + +/* A hash index node, to embed inside the data structure being indexed. + * + * Nodes are linked together like this (the boxes are labeled with hash + * values): + * + * +--------+ d +--------+ d +--------+ d + * bucket---> | 6 |---->| 20 |---->| 15 |---->null + * +-|------+ +-|------+ +-|------+ + * | ^ | | ^ + * s| |d |s s| |d + * V | V V | + * +------|-+ null +------|-+ + * | 6 | | 15 | + * +-|------+ +-|------+ + * | ^ | + * s| |d s| + * V | V + * +------|-+ null + * | 6 | + * +-|------+ + * | + * s| + * V + * null + * + * The basic usage is: + * + * - To visit the unique hash values in the hindex, follow the 'd' + * ("different") pointers starting from each bucket. The nodes visited + * this way are called "head" nodes, because they are at the head of the + * vertical chains. + * + * - To visit the nodes with hash value H, follow the 'd' pointers in the + * appropriate bucket until you find one with hash H, then follow the 's' + * ("same") pointers until you hit a null pointer. The non-head nodes + * visited this way are called "body" nodes. + * + * - The 'd' pointers in body nodes point back to the previous body node + * or, for the first body node, to the head node. (This makes it + * possible to remove a body node without traversing all the way downward + * from the head). + */ +struct hindex_node { + /* Hash value. */ + size_t hash; + + /* In a head node, the next head node (with a hash different from this + * node), or NULL if this is the last node in this bucket. + * + * In a body node, the previous head or body node (with the same hash as + * this node). Never null. */ + struct hindex_node *d; + + /* In a head or a body node, the next body node with the same hash as this + * node. NULL if this is the last node with this hash. */ + struct hindex_node *s; +}; + +/* A hash index. */ +struct hindex { + struct hindex_node **buckets; /* Must point to 'one' iff 'mask' == 0. */ + struct hindex_node *one; + size_t mask; /* 0 or more lowest-order bits set, others cleared. */ + size_t n_unique; /* Number of unique hashes (the number of head nodes). */ +}; + +/* Initializer for an empty hash index. */ +#define HINDEX_INITIALIZER(HINDEX) \ + { (struct hindex_node **const) &(HINDEX)->one, NULL, 0, 0 } + +/* Initialization. */ +void hindex_init(struct hindex *); +void hindex_destroy(struct hindex *); +void hindex_clear(struct hindex *); +void hindex_swap(struct hindex *a, struct hindex *b); +void hindex_moved(struct hindex *hindex); +static inline bool hindex_is_empty(const struct hindex *); + +/* Adjusting capacity. */ +void hindex_expand(struct hindex *); +void hindex_shrink(struct hindex *); +void hindex_reserve(struct hindex *, size_t capacity); + +/* Insertion and deletion. */ +void hindex_insert_fast(struct hindex *, struct hindex_node *, size_t hash); +void hindex_insert(struct hindex *, struct hindex_node *, size_t hash); +void hindex_remove(struct hindex *, struct hindex_node *); + +/* Search. + * + * HINDEX_FOR_EACH_WITH_HASH iterates NODE over all of the nodes in HINDEX that + * have hash value equal to HASH. MEMBER must be the name of the 'struct + * hindex_node' member within NODE. + * + * The loop should not change NODE to point to a different node or insert or + * delete nodes in HINDEX (unless it "break"s out of the loop to terminate + * iteration). + * + * Evaluates HASH only once. + */ +#define HINDEX_FOR_EACH_WITH_HASH(NODE, MEMBER, HASH, HINDEX) \ + for (ASSIGN_CONTAINER(NODE, hindex_node_with_hash(HINDEX, HASH), MEMBER); \ + &(NODE)->MEMBER != NULL; \ + ASSIGN_CONTAINER(NODE, (NODE)->MEMBER.s, MEMBER)) + +struct hindex_node *hindex_node_with_hash(const struct hindex *, size_t hash); + +/* Iteration. */ + +/* Iterates through every node in HINDEX. */ +#define HINDEX_FOR_EACH(NODE, MEMBER, HINDEX) \ + for (ASSIGN_CONTAINER(NODE, hindex_first(HINDEX), MEMBER); \ + &(NODE)->MEMBER != NULL; \ + ASSIGN_CONTAINER(NODE, hindex_next(HINDEX, &(NODE)->MEMBER), MEMBER)) + +/* Safe when NODE may be freed (not needed when NODE may be removed from the + * hash index but its members remain accessible and intact). */ +#define HINDEX_FOR_EACH_SAFE(NODE, NEXT, MEMBER, HINDEX) \ + for (ASSIGN_CONTAINER(NODE, hindex_first(HINDEX), MEMBER); \ + (&(NODE)->MEMBER != NULL \ + ? ASSIGN_CONTAINER(NEXT, hindex_next(HINDEX, &(NODE)->MEMBER), MEMBER) \ + : 0); \ + (NODE) = (NEXT)) + +struct hindex_node *hindex_first(const struct hindex *); +struct hindex_node *hindex_next(const struct hindex *, + const struct hindex_node *); + +/* Returns true if 'hindex' currently contains no nodes, false otherwise. */ +static inline bool +hindex_is_empty(const struct hindex *hindex) +{ + return hindex->n_unique == 0; +} + +#endif /* hindex.h */ diff --git a/tests/automake.mk b/tests/automake.mk index 15df6231f..1af41ccb7 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -105,6 +105,7 @@ valgrind_wrappers = \ tests/valgrind/test-flows \ tests/valgrind/test-hash \ tests/valgrind/test-heap \ + tests/valgrind/test-hindex \ tests/valgrind/test-hmap \ tests/valgrind/test-json \ tests/valgrind/test-jsonrpc \ @@ -203,6 +204,10 @@ noinst_PROGRAMS += tests/test-heap tests_test_heap_SOURCES = tests/test-heap.c tests_test_heap_LDADD = lib/libopenvswitch.a $(SSL_LIBS) +noinst_PROGRAMS += tests/test-hindex +tests_test_hindex_SOURCES = tests/test-hindex.c +tests_test_hindex_LDADD = lib/libopenvswitch.a $(SSL_LIBS) + noinst_PROGRAMS += tests/test-hmap tests_test_hmap_SOURCES = tests/test-hmap.c tests_test_hmap_LDADD = lib/libopenvswitch.a $(SSL_LIBS) diff --git a/tests/library.at b/tests/library.at index 650fef3f4..532af3bd5 100644 --- a/tests/library.at +++ b/tests/library.at @@ -20,6 +20,11 @@ AT_CHECK([test-hmap], [0], [......... ]) AT_CLEANUP +AT_SETUP([test hash index]) +AT_CHECK([test-hindex], [0], [.................. +]) +AT_CLEANUP + AT_SETUP([test linked lists]) AT_CHECK([test-list], [0], [.. ]) diff --git a/tests/test-hindex.c b/tests/test-hindex.c new file mode 100644 index 000000000..b5fe9f014 --- /dev/null +++ b/tests/test-hindex.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2013 Nicira, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* A non-exhaustive test for some of the functions and macros declared in + * hindex.h. */ + +#include +#include "hindex.h" +#include +#include "hash.h" +#include "util.h" + +#undef NDEBUG +#include + +/* Sample hindex element. */ +struct element { + int value; + struct hindex_node node; +}; + +typedef size_t hash_func(int value); + +static int +compare_ints(const void *a_, const void *b_) +{ + const int *a = a_; + const int *b = b_; + return *a < *b ? -1 : *a > *b; +} + +/* Verifies that 'hindex' contains exactly the 'n' values in 'values'. */ +static void +check_hindex(struct hindex *hindex, const int values[], size_t n, + hash_func *hash) +{ + int *sort_values, *hindex_values; + struct element *e; + size_t i; + + /* Check that all the values are there in iteration. */ + sort_values = xmalloc(sizeof *sort_values * n); + hindex_values = xmalloc(sizeof *sort_values * n); + + i = 0; + HINDEX_FOR_EACH (e, node, hindex) { + assert(i < n); + hindex_values[i++] = e->value; + } + assert(i == n); + + memcpy(sort_values, values, sizeof *sort_values * n); + qsort(sort_values, n, sizeof *sort_values, compare_ints); + qsort(hindex_values, n, sizeof *hindex_values, compare_ints); + + for (i = 0; i < n; i++) { + assert(sort_values[i] == hindex_values[i]); + } + + free(hindex_values); + free(sort_values); + + /* Check that all the values are there in lookup. */ + for (i = 0; i < n; i++) { + size_t count = 0; + + HINDEX_FOR_EACH_WITH_HASH (e, node, hash(values[i]), hindex) { + count += e->value == values[i]; + } + assert(count == 1); + } + + /* Check counters. */ + assert(hindex_is_empty(hindex) == !n); + assert(hindex->n_unique <= n); +} + +/* Puts the 'n' values in 'values' into 'elements', and then puts those + * elements into 'hindex'. */ +static void +make_hindex(struct hindex *hindex, struct element elements[], + int values[], size_t n, hash_func *hash) +{ + size_t i; + + hindex_init(hindex); + for (i = 0; i < n; i++) { + elements[i].value = i; + hindex_insert(hindex, &elements[i].node, hash(elements[i].value)); + values[i] = i; + } +} + +static void +shuffle(int *p, size_t n) +{ + for (; n > 1; n--, p++) { + int *q = &p[rand() % n]; + int tmp = *p; + *p = *q; + *q = tmp; + } +} + +/* Prints the 'n' values in 'values', plus 'name' as a title. */ +static void OVS_UNUSED +print_ints(const char *name, const int *values, size_t n) +{ + size_t i; + + printf("%s:", name); + for (i = 0; i < n; i++) { + printf(" %d", values[i]); + } + printf("\n"); +} + +/* Prints the values in 'hindex', plus 'name' as a title. */ +static void OVS_UNUSED +print_hindex(const char *name, struct hindex *hindex) +{ + struct element *e; + + printf("%s:", name); + HINDEX_FOR_EACH (e, node, hindex) { + printf(" %d(%zu)", e->value, e->node.hash & hindex->mask); + } + printf("\n"); +} + +static size_t +unique_hash(int value) +{ + return value; +} + +static size_t +good_hash(int value) +{ + return hash_int(value, 0x1234abcd); +} + +static size_t +constant_hash(int value OVS_UNUSED) +{ + return 123; +} + +static size_t +mod4_hash(int value) +{ + return value % 4; +} + +static size_t +mod3_hash(int value) +{ + return value % 3; +} + +static size_t +mod2_hash(int value) +{ + return value % 2; +} + +/* Tests basic hindex insertion and deletion. */ +static void +test_hindex_insert_delete(hash_func *hash) +{ + enum { N_ELEMS = 100 }; + + struct element elements[N_ELEMS]; + int values[N_ELEMS]; + struct hindex hindex; + size_t i; + + hindex_init(&hindex); + for (i = 0; i < N_ELEMS; i++) { + elements[i].value = i; + hindex_insert(&hindex, &elements[i].node, hash(i)); + values[i] = i; + check_hindex(&hindex, values, i + 1, hash); + } + shuffle(values, N_ELEMS); + for (i = 0; i < N_ELEMS; i++) { + hindex_remove(&hindex, &elements[values[i]].node); + check_hindex(&hindex, values + (i + 1), N_ELEMS - (i + 1), hash); + } + hindex_destroy(&hindex); +} + +/* Tests basic hindex_reserve() and hindex_shrink(). */ +static void +test_hindex_reserve_shrink(hash_func *hash) +{ + enum { N_ELEMS = 32 }; + + size_t i; + + for (i = 0; i < N_ELEMS; i++) { + struct element elements[N_ELEMS]; + int values[N_ELEMS]; + struct hindex hindex; + size_t j; + + hindex_init(&hindex); + hindex_reserve(&hindex, i); + for (j = 0; j < N_ELEMS; j++) { + elements[j].value = j; + hindex_insert(&hindex, &elements[j].node, hash(j)); + values[j] = j; + check_hindex(&hindex, values, j + 1, hash); + } + shuffle(values, N_ELEMS); + for (j = 0; j < N_ELEMS; j++) { + hindex_remove(&hindex, &elements[values[j]].node); + hindex_shrink(&hindex); + check_hindex(&hindex, values + (j + 1), N_ELEMS - (j + 1), hash); + } + hindex_destroy(&hindex); + } +} + +/* Tests that HINDEX_FOR_EACH_SAFE properly allows for deletion of the current + * element of a hindex. */ +static void +test_hindex_for_each_safe(hash_func *hash) +{ + enum { MAX_ELEMS = 10 }; + size_t n; + unsigned long int pattern; + + for (n = 0; n <= MAX_ELEMS; n++) { + for (pattern = 0; pattern < 1ul << n; pattern++) { + struct element elements[MAX_ELEMS]; + int values[MAX_ELEMS]; + struct hindex hindex; + struct element *e, *next; + size_t n_remaining; + int i; + + make_hindex(&hindex, elements, values, n, hash); + + i = 0; + n_remaining = n; + HINDEX_FOR_EACH_SAFE (e, next, node, &hindex) { + assert(i < n); + if (pattern & (1ul << e->value)) { + size_t j; + hindex_remove(&hindex, &e->node); + for (j = 0; ; j++) { + assert(j < n_remaining); + if (values[j] == e->value) { + values[j] = values[--n_remaining]; + break; + } + } + } + check_hindex(&hindex, values, n_remaining, hash); + i++; + } + assert(i == n); + + for (i = 0; i < n; i++) { + if (pattern & (1ul << i)) { + n_remaining++; + } + } + assert(n == n_remaining); + + hindex_destroy(&hindex); + } + } +} + +static void +run_test(void (*function)(hash_func *)) +{ + hash_func *hash_funcs[] = { + unique_hash, + good_hash, + constant_hash, + mod4_hash, + mod3_hash, + mod2_hash, + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(hash_funcs); i++) { + function(hash_funcs[i]); + printf("."); + fflush(stdout); + } +} + +int +main(void) +{ + run_test(test_hindex_insert_delete); + run_test(test_hindex_for_each_safe); + run_test(test_hindex_reserve_shrink); + printf("\n"); + return 0; +} +