From 3a28a0fb4d5594784ce3d0c3bb0cc837eee6b4f5 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Mon, 8 Apr 2024 11:18:36 -0400 Subject: [PATCH] Fixes --- .gitignore | 2 ++ Makefile | 10 +++--- examples/ex1.c | 49 +++++++++++++++++++++++----- include/sl.h | 87 +++++++++++++++----------------------------------- 4 files changed, 73 insertions(+), 75 deletions(-) diff --git a/.gitignore b/.gitignore index b062548..7a98879 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,9 @@ libskiplist.a libskiplist.so **/*.o tests/test +examples/mls.c examples/mls +examples/mls.o /mxe .cache hints.txt diff --git a/Makefile b/Makefile index 621f1a9..5d8912e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -OBJS = skiplist.o -STATIC_LIB = libskiplist.a -SHARED_LIB = libskiplist.so +OBJS = +STATIC_LIB = +SHARED_LIB = # https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html #CFLAGS = -Wall -Wextra -Wpedantic -Of -std=c99 -Iinclude/ -fPIC @@ -69,11 +69,11 @@ examples/mls: examples/mls.o $(STATIC_LIB) #dot: # ./examples/mls -# dot -Tpdf /tmp/slm.dot -o /tmp/slm.pdf >/dev/null 2>&1 +# dot -Tpdf /tmp/ex1.dot -o /tmp/ex1.pdf >/dev/null 2>&1 #re-write CPP line information comments, but keep them # $(CC) $(CFLAGS) -C -E examples/ex1.c | sed -e '1,7d' -e 's/^#\( [0-9]* ".*$$\)/\/\* \1 \*\//' | clang-format > examples/mls.c # workflow: -# clear; rm examples/mls.c; make examples/mls && env ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./examples/mls #&& dot -Tpdf /tmp/slm.dot -o /tmp/slm.pdf +# clear; rm examples/mls.c; make examples/mls && env ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./examples/mls #&& dot -Tpdf /tmp/ex1.dot -o /tmp/ex1.pdf # cp include/sl.h /tmp/foo; clang-format -i include/sl.h diff --git a/examples/ex1.c b/examples/ex1.c index b0f4bfa..d611bc5 100644 --- a/examples/ex1.c +++ b/examples/ex1.c @@ -18,11 +18,13 @@ // Local demo application OPTIONS: // --------------------------------------------------------------------------- -#define TEST_ARRAY_SIZE 1000 +#define TEST_ARRAY_SIZE 10 #define VALIDATE //define SNAPSHOTS //define TODO_RESTORE_SNAPSHOTS -//define DOT +#define STABLE_SEED +#define DOT + #ifdef DOT size_t gen = 0; FILE *of = 0; @@ -38,8 +40,8 @@ FILE *of = 0; /* * SKIPLIST EXAMPLE: * - * This example creates an "ex" (example in Italian) Skiplist where keys - * are integers, values are strings allocated on the heap. + * This example creates an "ex" Skiplist where keys are integers, values are + * strings containing the roman numeral for the key allocated on the heap. */ /* @@ -179,12 +181,38 @@ SKIPLIST_DECL_DOT(ex, api_, entries) void sprintf_ex_node(ex_node_t *node, char *buf) { - // sprintf(buf, "%d:%s (hits: %lu)", node->key, node->value, node->entries.sle_levels[0].hits); sprintf(buf, "%d:%s", node->key, node->value); } // Function for this demo application. // --------------------------------------------------------------------------- +int __xorshift32_state = 0; + +// Xorshift algorithm for PRNG +uint32_t +xorshift32() +{ + uint32_t x = __xorshift32_state; + if (x == 0) + x = 123456789; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + __xorshift32_state = x; + return x; +} + +void +xorshift32_seed() +{ + // Seed the PRNG +#ifdef STABLE_SEED + __xorshift32_state = 8675309; +#else + __xorshift32_state = (unsigned int)time(NULL) ^ getpid(); +#endif +} + static char * to_lower(char *str) { @@ -241,7 +269,7 @@ shuffle(int *array, size_t n) if (n > 1) { size_t i; for (i = n - 1; i > 0; i--) { - size_t j = (unsigned int)(rand() % (i + 1)); /* NOLINT(*-msc50-cpp) */ + size_t j = (unsigned int)(xorshift32() % (i + 1)); /* NOLINT(*-msc50-cpp) */ int t = array[j]; array[j] = array[i]; array[i] = t; @@ -267,10 +295,12 @@ main() snap_info_t snaps[TEST_ARRAY_SIZE * 2 + 1]; #endif + xorshift32_seed(); + #ifdef DOT - of = fopen("/tmp/slm.dot", "w"); + of = fopen("/tmp/ex1.dot", "w"); if (!of) { - perror("Failed to open file /tmp/slm.dot"); + perror("Failed to open file /tmp/ex1.dot"); return 1; } #endif @@ -280,6 +310,9 @@ main() if (list == NULL) return ENOMEM; + /* We set the max height here to 12, it's negative so that + the PRNG is seeded with this value as a testing trick for + predictable random sequences. */ rc = api_skip_init_ex(list, -12); if (rc) return rc; diff --git a/include/sl.h b/include/sl.h index aa25f46..6e24806 100644 --- a/include/sl.h +++ b/include/sl.h @@ -61,10 +61,10 @@ * * A skiplist is a sorted list with O(log(n)) on average for most operations. * It is a probabilistic datastructure, meaning that it does not guarantee - * O(log(n)) it approximates it over time. This implementation includes - * improves the probability by integrating the splay list algorithm for - * rebalancing trading off a bit of computational overhead and code complexity - * for a nearly always optimal, or "perfect" skiplist. + * O(log(n)) it approximates it over time. This implementation improves the + * probability by integrating the splay list algorithm for rebalancing trading + * off a bit of computational overhead and code complexity for a nearly always + * optimal, or "perfect" skiplist. * * Conceptually, a skiplist is arranged as follows: * @@ -217,7 +217,6 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li struct decl##_node *sle_prev; \ struct __skiplist_##decl##_level { \ struct decl##_node *next; \ - size_t hits; \ } *sle_levels; \ } @@ -235,16 +234,10 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li #define __SKIP_IS_LAST_ENTRY_T2B() if (lvl == 0) #define __SKIP_ALL_ENTRIES_B2T(field, elm) for (size_t lvl = 0; lvl < slist->slh_max_height; lvl++) -#define __SKIP_ENTRIES_B2T(field, elm) for (size_t lvl = 0; lvl < elm->field.sle_height; lvl++) -#define __SKIP_ENTRIES_B2T_FROM(field, elm, off) for (size_t lvl = off; lvl < elm->field.sle_height; lvl++) +#define __SKIP_ENTRIES_B2T(field, elm) for (size_t lvl = 0; lvl <= elm->field.sle_height; lvl++) +#define __SKIP_ENTRIES_B2T_FROM(field, elm, off) for (size_t lvl = off; lvl <= elm->field.sle_height; lvl++) #define __SKIP_IS_LAST_ENTRY_B2T() if (lvl + 1 == elm->field.sle_height) -/* Iterate over the subtree to the left (v, or 'lt') and right (u) or "CHu" and "CHv". */ -#define __SKIP_SUBTREE_CHv(decl, field, list, path, nth) \ - for (decl##_node_t *elm = path[nth].node; elm->field.sle_levels[path[nth].in].next == path[nth].node; elm = elm->field.sle_prev) -#define __SKIP_SUBTREE_CHu(decl, field, list, path, nth) \ - for (decl##_node_t *elm = path[nth].node; elm != path[nth].node->field.sle_levels[0].next; elm = elm->field.sle_levels[0].next) - /* * Skiplist declarations and access methods. */ @@ -263,6 +256,7 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li struct decl { \ size_t slh_length, slh_max_height; \ void *slh_aux; \ + int slh_prng_state; \ decl##_node_t *slh_head; \ decl##_node_t *slh_tail; \ struct { \ @@ -285,10 +279,21 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li \ typedef struct __skiplist_path_##decl { \ decl##_node_t *node; /* node traversed in the act of location */ \ - size_t in; /* level at which the node was intersected */ \ - size_t pu; /* sum of hits from intersection to level[1] */ \ } __skiplist_path_##decl##_t; \ \ + /* Xorshift algorithm for PRNG */ \ + static uint32_t __##decl##_xorshift32(int *state) \ + { \ + uint32_t x = *state; \ + if (x == 0) \ + x = 123456789; \ + x ^= x << 13; \ + x ^= x >> 17; \ + x ^= x << 5; \ + *state = x; \ + return x; \ + } \ + \ /** \ * -- __skip_compare_entries_fn_ \ * \ @@ -379,12 +384,12 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li * Skiplist. For example, when `max = 6` this function returns 0 with \ * probability 0.5, 1 with 0.25, 2 with 0.125, etc. until 6 with 0.5^7. \ */ \ - static int __skip_toss_##decl(size_t max) \ + static int __skip_toss_##decl(decl##_t *slist, size_t max) \ { \ size_t level = 0; \ double probability = 0.5; \ \ - double random_value = (double)rand() / RAND_MAX; /* NOLINT(*-msc50-cpp) */ \ + double random_value = (double)__##decl##_xorshift32(&slist->slh_prng_state) / RAND_MAX; \ while (random_value < probability && level < max) { \ level++; \ probability *= 0.5; \ @@ -456,9 +461,9 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li * seed the PRNG in a predictable way and have reproducible random numbers. \ */ \ if (max < 0) \ - srand(-max); \ + slist->slh_prng_state = -max; \ else \ - srand(((unsigned int)time(NULL) ^ getpid())); \ + slist->slh_prng_state = ((unsigned int)time(NULL) ^ getpid()); \ fail:; \ return rc; \ } \ @@ -599,35 +604,6 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li return nodes; \ } \ \ - /** \ - * -- __skip_adjust_hit_counts_ TODO: see gburd/splay-list \ - * \ - * On delete we check the hit counts across all nodes and next[] pointers \ - * and find the smallest counter then subtract that + 1 from all hit \ - * counters. \ - * \ - */ \ - static void __skip_adjust_hit_counts_##decl(decl##_t *slist) \ - { \ - ((void)slist); \ - } \ - \ - /** \ - * -- __skip_rebalance_ \ - * \ - * Restore balance to our list by adjusting heights and forward pointers \ - * according to the algorithm put forth in "The Splay-List: A \ - * Distribution-Adaptive Concurrent Skip-List". \ - * \ - */ \ - static void __skip_rebalance_##decl(decl##_t *slist, size_t len, __skiplist_path_##decl##_t path[]) \ - { \ - ((void)slist); \ - ((void)len); \ - ((void)path); \ - return; /* TODO: WIP, see branch gburd/splay-list */ \ - } \ - \ /** \ * -- __skip_locate_ \ * \ @@ -648,22 +624,16 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li /* Find the node that matches `node` or NULL. */ \ i = slist->slh_head->field.sle_height; \ do { \ - path[i + 1].pu = 0; \ while (elm != slist->slh_tail && elm->field.sle_levels[i].next && \ __skip_compare_nodes_##decl(slist, elm->field.sle_levels[i].next, n, slist->slh_aux) < 0) { \ elm = elm->field.sle_levels[i].next; \ - path[i + 1].in = i; \ - path[i + 1].pu += elm->field.sle_levels[path[i + 1].in].hits; \ } \ path[i + 1].node = elm; \ - path[i + 1].node->field.sle_levels[path[i + 1].in].hits++; \ len++; \ } while (i--); \ elm = elm->field.sle_levels[0].next; \ if (__skip_compare_nodes_##decl(slist, elm, n, slist->slh_aux) == 0) { \ path[0].node = elm; \ - path[0].node->field.sle_levels[0].hits++; \ - __skip_rebalance_##decl(slist, len, path); \ } \ return len; \ } \ @@ -694,12 +664,8 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li } \ /* First element in path should be NULL, reset should start pointing at tail. */ \ path[0].node = NULL; \ - path[0].in = 0; \ - path[0].pu = 0; \ for (i = 1; i < slist->slh_max_height + 1; i++) { \ path[i].node = slist->slh_tail; \ - path[i].in = 0; \ - path[i].pu = 0; \ } \ \ /* Find a `path` to `new` in the list and a match (`path[0]`) if it exists. */ \ @@ -712,7 +678,7 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li } \ /* Coin toss to determine level of this new node [0, max) */ \ cur_height = slist->slh_head->field.sle_height; \ - new_height = __skip_toss_##decl(slist->slh_max_height - 1); \ + new_height = __skip_toss_##decl(slist, slist->slh_max_height - 1); \ new->field.sle_height = new_height; \ /* Trim the path to at most the new height for the new node. */ \ for (i = cur_height + 1; i <= new_height; i++) { \ @@ -759,8 +725,6 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li /* Increase the the list's era/age and record it. */ \ new->field.sle_era = slist->slh_snap.cur_era++; \ } \ - /* Set hits for rebalencing to 1 when new born. */ \ - new->field.sle_levels[new_height].hits = 1; \ /* Increase our list length (aka. size, count, etc.) by one. */ \ slist->slh_length++; \ \ @@ -1124,7 +1088,6 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li slist->slh_tail->field.sle_height = i; \ \ slist->slh_length--; \ - __skip_adjust_hit_counts_##decl(slist); \ } \ return 0; \ } \