Implement generic hash table.
authorBen Pfaff <blp@nicira.com>
Tue, 23 Dec 2008 22:59:48 +0000 (14:59 -0800)
committerBen Pfaff <blp@nicira.com>
Tue, 23 Dec 2008 23:06:16 +0000 (15:06 -0800)
lib/automake.mk
lib/hmap.c [new file with mode: 0644]
lib/hmap.h [new file with mode: 0644]
tests/automake.mk
tests/test-hmap.c [new file with mode: 0644]

index 238c3a1515efdc280f9211cb04b0e463e89e3ba6..fd7d89c940decebbdc73fb75206802c68bb103f7 100644 (file)
@@ -25,6 +25,8 @@ lib_libopenflow_a_SOURCES = \
        lib/flow.h \
        lib/hash.c \
        lib/hash.h \
+       lib/hmap.c \
+       lib/hmap.h \
        lib/learning-switch.c \
        lib/learning-switch.h \
        lib/list.c \
diff --git a/lib/hmap.c b/lib/hmap.c
new file mode 100644 (file)
index 0000000..f05bdb7
--- /dev/null
@@ -0,0 +1,153 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include <config.h>
+#include "hmap.h"
+#include <assert.h>
+#include <stdint.h>
+#include "util.h"
+
+/* Initializes 'hmap' as an empty hash table. */
+void
+hmap_init(struct hmap *hmap)
+{
+    hmap->buckets = &hmap->one;
+    hmap->one = NULL;
+    hmap->mask = 0;
+    hmap->n = 0;
+}
+
+/* Frees memory reserved by 'hmap'.  It is the client's responsibility to free
+ * the nodes themselves, if necessary. */
+void
+hmap_destroy(struct hmap *hmap)
+{
+    if (hmap && hmap->buckets != &hmap->one) {
+        free(hmap->buckets);
+    }
+}
+
+/* Exchanges hash maps 'a' and 'b'. */
+void
+hmap_swap(struct hmap *a, struct hmap *b)
+{
+    struct hmap tmp = *a;
+    *a = *b;
+    *b = tmp;
+    if (a->buckets == &b->one) {
+        a->buckets = &a->one;
+    }
+    if (b->buckets == &a->one) {
+        b->buckets = &b->one;
+    }
+}
+
+static void
+resize(struct hmap *hmap, size_t new_mask)
+{
+    struct hmap tmp;
+    size_t i;
+
+    assert(!(new_mask & (new_mask + 1)));
+    assert(new_mask != SIZE_MAX);
+
+    hmap_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 <= hmap->mask; i++) {
+        struct hmap_node *node, *next;
+        for (node = hmap->buckets[i]; node; node = next) {
+            next = node->next;
+            hmap_insert_fast(&tmp, node, node->hash);
+        }
+    }
+    hmap_swap(hmap, &tmp);
+    hmap_destroy(&tmp);
+}
+
+static size_t
+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;
+}
+
+/* Expands 'hmap', if necessary, to optimize the performance of searches. */
+void
+hmap_expand(struct hmap *hmap)
+{
+    size_t new_mask = calc_mask(hmap->n);
+    if (new_mask > hmap->mask) {
+        resize(hmap, new_mask);
+    }
+}
+
+/* Shrinks 'hmap', if necessary, to optimize the performance of iteration. */
+void
+hmap_shrink(struct hmap *hmap)
+{
+    size_t new_mask = calc_mask(hmap->n);
+    if (new_mask < hmap->mask) {
+        resize(hmap, new_mask);
+    }
+}
+
+/* Expands 'hmap', if necessary, to optimize the performance of searches when
+ * it has up to 'n' elements.  (But iteration will be slow in a hash map whose
+ * allocated capacity is much higher than its current number of nodes.)  */
+void
+hmap_reserve(struct hmap *hmap, size_t n)
+{
+    size_t new_mask = calc_mask(n);
+    if (new_mask > hmap->mask) {
+        resize(hmap, new_mask);
+    }
+}
diff --git a/lib/hmap.h b/lib/hmap.h
new file mode 100644 (file)
index 0000000..60695fe
--- /dev/null
@@ -0,0 +1,234 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#ifndef HMAP_H
+#define HMAP_H 1
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+/* A hash map node, to be embedded inside the data structure being mapped. */
+struct hmap_node {
+    size_t hash;                /* Hash value. */
+    struct hmap_node *next;     /* Next in linked list. */
+};
+
+/* Returns the hash value embedded in 'node'. */
+static inline size_t hmap_node_hash(const struct hmap_node *node)
+{
+    return node->hash;
+}
+
+/* A hash map. */
+struct hmap {
+    struct hmap_node **buckets;
+    struct hmap_node *one;
+    size_t mask;
+    size_t n;
+};
+
+/* Initializer for an empty hash map. */
+#define HMAP_INITIALIZER(HMAP) { &(HMAP)->one, NULL, 0, 0 }
+
+/* Initialization. */
+void hmap_init(struct hmap *);
+void hmap_destroy(struct hmap *);
+void hmap_swap(struct hmap *a, struct hmap *b);
+static inline size_t hmap_count(const struct hmap *);
+static inline bool hmap_is_empty(const struct hmap *);
+
+/* Adjusting capacity. */
+void hmap_expand(struct hmap *);
+void hmap_shrink(struct hmap *);
+void hmap_reserve(struct hmap *, size_t capacity);
+
+/* Insertion and deletion. */
+static inline void hmap_insert_fast(struct hmap *,
+                                    struct hmap_node *, size_t hash);
+static inline void hmap_insert(struct hmap *, struct hmap_node *, size_t hash);
+static inline void hmap_remove(struct hmap *, struct hmap_node *);
+
+/* Search. */
+#define HMAP_FOR_EACH_WITH_HASH(NODE, STRUCT, MEMBER, HASH, HMAP)       \
+    for ((NODE) = CONTAINER_OF(hmap_first_with_hash(HMAP, HASH),        \
+                               STRUCT, MEMBER);                         \
+         &(NODE)->MEMBER != NULL;                                       \
+         (NODE) = CONTAINER_OF(hmap_next_with_hash(&(NODE)->MEMBER),    \
+                               STRUCT, MEMBER))
+
+static inline struct hmap_node *hmap_first_with_hash(const struct hmap *,
+                                                     size_t hash);
+static inline struct hmap_node *hmap_next_with_hash(const struct hmap_node *);
+
+/* Iteration.
+ *
+ * The _SAFE version is needed when NODE may be freed.  It is not needed when
+ * NODE may be removed from the hash map but its members remain accessible and
+ * intact. */
+#define HMAP_FOR_EACH(NODE, STRUCT, MEMBER, HMAP)                   \
+    for ((NODE) = CONTAINER_OF(hmap_first(HMAP), STRUCT, MEMBER);   \
+         &(NODE)->MEMBER != NULL;                                   \
+         (NODE) = CONTAINER_OF(hmap_next(HMAP, &(NODE)->MEMBER),    \
+                               STRUCT, MEMBER))
+
+#define HMAP_FOR_EACH_SAFE(NODE, NEXT, STRUCT, MEMBER, HMAP)        \
+    for ((NODE) = CONTAINER_OF(hmap_first(HMAP), STRUCT, MEMBER);   \
+         (&(NODE)->MEMBER != NULL                                   \
+          ? (NEXT) = CONTAINER_OF(hmap_next(HMAP, &(NODE)->MEMBER), \
+                                  STRUCT, MEMBER), 1                \
+          : 0);                                                     \
+         (NODE) = (NEXT))
+
+static inline struct hmap_node *hmap_first(const struct hmap *);
+static inline struct hmap_node *hmap_next(const struct hmap *,
+                                          const struct hmap_node *);
+
+/* Returns the number of nodes currently in 'hmap'. */
+static inline size_t
+hmap_count(const struct hmap *hmap)
+{
+    return hmap->n;
+}
+
+/* Returns true if 'hmap' currently contains no nodes,
+ * false otherwise. */
+static inline bool
+hmap_is_empty(const struct hmap *hmap)
+{
+    return hmap->n == 0;
+}
+
+/* Inserts 'node', with the given 'hash', into 'hmap'.  'hmap' is never
+ * expanded automatically. */
+static inline void
+hmap_insert_fast(struct hmap *hmap, struct hmap_node *node, size_t hash)
+{
+    struct hmap_node **bucket = &hmap->buckets[hash & hmap->mask];
+    node->hash = hash;
+    node->next = *bucket;
+    *bucket = node;
+    hmap->n++;
+}
+
+/* Inserts 'node', with the given 'hash', into 'hmap', and expands 'hmap' if
+ * necessary to optimize search performance. */
+static inline void
+hmap_insert(struct hmap *hmap, struct hmap_node *node, size_t hash)
+{
+    hmap_insert_fast(hmap, node, hash);
+    if (hmap->n / 2 > hmap->mask) {
+        hmap_expand(hmap);
+    }
+}
+
+/* Removes 'node' from 'hmap'.  Does not shrink the hash table; call
+ * hmap_shrink() directly if desired. */
+static inline void
+hmap_remove(struct hmap *hmap, struct hmap_node *node)
+{
+    struct hmap_node **bucket = &hmap->buckets[node->hash & hmap->mask];
+    while (*bucket != node) {
+        bucket = &(*bucket)->next;
+    }
+    *bucket = node->next;
+    hmap->n--;
+}
+
+static inline struct hmap_node *
+hmap_next_with_hash__(const struct hmap_node *node, size_t hash)
+{
+    while (node != NULL && node->hash != hash) {
+        node = node->next;
+    }
+    return (struct hmap_node *) node;
+}
+
+/* Returns the first node in 'hmap' with the given 'hash', or a null pointer if
+ * no nodes have that hash value. */
+static inline struct hmap_node *
+hmap_first_with_hash(const struct hmap *hmap, size_t hash)
+{
+    return hmap_next_with_hash__(hmap->buckets[hash & hmap->mask], hash);
+}
+
+/* Returns the next node in the same hash map as 'node' with the same hash
+ * value, or a null pointer if no more nodes have that hash value.
+ *
+ * If the hash map has been reallocated since 'node' was visited, some nodes
+ * may be skipped; if new nodes with the same hash value have been added, they
+ * will be skipped.  (Removing 'node' from the hash map does not prevent
+ * calling this function, since node->next is preserved, although freeing
+ * 'node' of course does.) */
+static inline struct hmap_node *
+hmap_next_with_hash(const struct hmap_node *node)
+{
+    return hmap_next_with_hash__(node->next, node->hash);
+}
+
+static inline struct hmap_node *
+hmap_next__(const struct hmap *hmap, size_t start)
+{
+    size_t i;
+    for (i = start; i <= hmap->mask; i++) {
+        struct hmap_node *node = hmap->buckets[i];
+        if (node) {
+            return node;
+        }
+    }
+    return NULL;
+}
+
+/* Returns the first node in 'hmap', in arbitrary order, or a null pointer if
+ * 'hmap' is empty. */
+static inline struct hmap_node *
+hmap_first(const struct hmap *hmap)
+{
+    return hmap_next__(hmap, 0);
+}
+
+/* Returns the next node in 'hmap' following 'node', in arbitrary order, or a
+ * null pointer if 'node' is the last node in 'hmap'.
+ *
+ * If the hash map has been reallocated since 'node' was visited, some nodes
+ * may be skipped or visited twice.  (Removing 'node' from the hash map does
+ * not prevent calling this function, since node->next is preserved, although
+ * freeing 'node' of course does.) */
+static inline struct hmap_node *
+hmap_next(const struct hmap *hmap, const struct hmap_node *node)
+{
+    return (node->next
+            ? node->next
+            : hmap_next__(hmap, (node->hash & hmap->mask) + 1));
+}
+
+#endif /* hmap.h */
index 0c7edd16b559386a122a13b7f17e3f0de699a9b9..ecfd58e4493bc629afa6f3641fa5b55a60025051 100644 (file)
@@ -1,3 +1,8 @@
+TESTS += tests/test-hmap
+noinst_PROGRAMS += tests/test-hmap
+tests_test_hmap_SOURCES = tests/test-hmap.c
+tests_test_hmap_LDADD = lib/libopenflow.a
+
 TESTS += tests/test-list
 noinst_PROGRAMS += tests/test-list
 tests_test_list_SOURCES = tests/test-list.c
diff --git a/tests/test-hmap.c b/tests/test-hmap.c
new file mode 100644 (file)
index 0000000..1ef6fea
--- /dev/null
@@ -0,0 +1,282 @@
+/* A non-exhaustive test for some of the functions and macros declared in
+ * hmap.h. */
+
+#include <config.h>
+#include "hmap.h"
+#include <string.h>
+#include "hash.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+/* Sample hmap element. */
+struct element {
+    int value;
+    struct hmap_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 'hmap' contains exactly the 'n' values in 'values'. */
+static void
+check_hmap(struct hmap *hmap, const int values[], size_t n,
+           hash_func *hash)
+{
+    int *sort_values, *hmap_values;
+    struct element *e;
+    size_t i;
+
+    /* Check that all the values are there in iteration. */
+    sort_values = xmalloc(sizeof *sort_values * n);
+    hmap_values = xmalloc(sizeof *sort_values * n);
+
+    i = 0;
+    HMAP_FOR_EACH (e, struct element, node, hmap) {
+        assert(i < n);
+        hmap_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(hmap_values, n, sizeof *hmap_values, compare_ints);
+
+    for (i = 0; i < n; i++) {
+        assert(sort_values[i] == hmap_values[i]);
+    }
+
+    free(hmap_values);
+    free(sort_values);
+
+    /* Check that all the values are there in lookup. */
+    for (i = 0; i < n; i++) {
+        size_t count = 0;
+
+        HMAP_FOR_EACH_WITH_HASH (e, struct element, node,
+                                 hash(values[i]), hmap) {
+            count += e->value == values[i];
+        }
+        assert(count == 1);
+    }
+
+    /* Check counters. */
+    assert(hmap_is_empty(hmap) == !n);
+    assert(hmap_count(hmap) == n);
+}
+
+/* Puts the 'n' values in 'values' into 'elements', and then puts those
+ * elements into 'hmap'. */
+static void
+make_hmap(struct hmap *hmap, struct element elements[],
+          int values[], size_t n, hash_func *hash)
+{
+    size_t i;
+
+    hmap_init(hmap);
+    for (i = 0; i < n; i++) {
+        elements[i].value = i;
+        hmap_insert(hmap, &elements[i].node, hash(elements[i].value));
+        values[i] = i;
+    }
+}
+
+void
+shuffle(int *p, size_t n)
+{
+    for (; n > 1; n--, p++) {
+        int *q = &p[rand() % n];
+        int tmp = *p;
+        *p = *q;
+        *q = tmp;
+    }
+}
+
+#if 0
+/* Prints the values in 'hmap', plus 'name' as a title. */
+static void
+print_hmap(const char *name, struct hmap *hmap)
+{
+    struct element *e;
+
+    printf("%s:", name);
+    HMAP_FOR_EACH (e, struct element, node, hmap) {
+        printf(" %d(%zu)", e->value, e->node.hash & hmap->mask);
+    }
+    printf("\n");
+}
+
+/* Prints the 'n' values in 'values', plus 'name' as a title. */
+static void
+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");
+}
+#endif
+
+static size_t
+identity_hash(int value)
+{
+    return value;
+}
+
+static size_t
+good_hash(int value)
+{
+    const uint32_t x = value;
+    return hash_lookup3(&x, 1, 0x1234abcd);
+}
+
+static size_t
+constant_hash(int value)
+{
+    return 123;
+}
+
+/* Tests basic hmap insertion and deletion. */
+static void
+test_hmap_insert_delete(hash_func *hash)
+{
+    enum { N_ELEMS = 100 };
+
+    struct element elements[N_ELEMS];
+    int values[N_ELEMS];
+    struct hmap hmap;
+    size_t i;
+
+    hmap_init(&hmap);
+    for (i = 0; i < N_ELEMS; i++) {
+        elements[i].value = i;
+        hmap_insert(&hmap, &elements[i].node, hash(i));
+        values[i] = i;
+        check_hmap(&hmap, values, i + 1, hash);
+    }
+    shuffle(values, N_ELEMS);
+    for (i = 0; i < N_ELEMS; i++) {
+        hmap_remove(&hmap, &elements[values[i]].node);
+        check_hmap(&hmap, values + (i + 1), N_ELEMS - (i + 1), hash);
+    }
+    hmap_destroy(&hmap);
+}
+
+/* Tests basic hmap_reserve() and hmap_shrink(). */
+static void
+test_hmap_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 hmap hmap;
+        size_t j;
+
+        hmap_init(&hmap);
+        hmap_reserve(&hmap, i);
+        for (j = 0; j < N_ELEMS; j++) {
+            elements[j].value = j;
+            hmap_insert(&hmap, &elements[j].node, hash(j));
+            values[j] = j;
+            check_hmap(&hmap, values, j + 1, hash);
+        }
+        shuffle(values, N_ELEMS);
+        for (j = 0; j < N_ELEMS; j++) {
+            hmap_remove(&hmap, &elements[values[j]].node);
+            hmap_shrink(&hmap);
+            check_hmap(&hmap, values + (j + 1), N_ELEMS - (j + 1), hash);
+        }
+        hmap_destroy(&hmap);
+    }
+}
+
+/* Tests that HMAP_FOR_EACH_SAFE properly allows for deletion of the current
+ * element of a hmap.  */
+static void
+test_hmap_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 hmap hmap;
+            struct element *e, *next;
+            size_t n_remaining;
+            int i;
+
+            make_hmap(&hmap, elements, values, n, hash);
+
+            i = 0;
+            n_remaining = n;
+            HMAP_FOR_EACH_SAFE (e, next, struct element, node, &hmap) {
+                assert(i < n);
+                if (pattern & (1ul << e->value)) {
+                    size_t j;
+                    hmap_remove(&hmap, &e->node);
+                    for (j = 0; ; j++) {
+                        assert(j < n_remaining);
+                        if (values[j] == e->value) {
+                            values[j] = values[--n_remaining];
+                            break;
+                        }
+                    }
+                }
+                check_hmap(&hmap, values, n_remaining, hash);
+                i++;
+            }
+            assert(i == n);
+
+            for (i = 0; i < n; i++) {
+                if (pattern & (1ul << i)) {
+                    n_remaining++;
+                }
+            }
+            assert(n == n_remaining);
+
+            hmap_destroy(&hmap);
+        }
+    }
+}
+
+static void
+run_test(void (*function)(hash_func *))
+{
+    hash_func *hash_funcs[] = { identity_hash, good_hash, constant_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_hmap_insert_delete);
+    run_test(test_hmap_for_each_safe);
+    run_test(test_hmap_reserve_shrink);
+    printf("\n");
+    return 0;
+}
+