This commit is contained in:
Gregory Burd 2024-04-08 11:18:36 -04:00
parent 28db528869
commit 3a28a0fb4d
4 changed files with 73 additions and 75 deletions

2
.gitignore vendored
View file

@ -2,7 +2,9 @@ libskiplist.a
libskiplist.so libskiplist.so
**/*.o **/*.o
tests/test tests/test
examples/mls.c
examples/mls examples/mls
examples/mls.o
/mxe /mxe
.cache .cache
hints.txt hints.txt

View file

@ -1,7 +1,7 @@
OBJS = skiplist.o OBJS =
STATIC_LIB = libskiplist.a STATIC_LIB =
SHARED_LIB = libskiplist.so SHARED_LIB =
# https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html # https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
#CFLAGS = -Wall -Wextra -Wpedantic -Of -std=c99 -Iinclude/ -fPIC #CFLAGS = -Wall -Wextra -Wpedantic -Of -std=c99 -Iinclude/ -fPIC
@ -69,11 +69,11 @@ examples/mls: examples/mls.o $(STATIC_LIB)
#dot: #dot:
# ./examples/mls # ./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 #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 # $(CC) $(CFLAGS) -C -E examples/ex1.c | sed -e '1,7d' -e 's/^#\( [0-9]* ".*$$\)/\/\* \1 \*\//' | clang-format > examples/mls.c
# workflow: # 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 # cp include/sl.h /tmp/foo; clang-format -i include/sl.h

View file

@ -18,11 +18,13 @@
// Local demo application OPTIONS: // Local demo application OPTIONS:
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
#define TEST_ARRAY_SIZE 1000 #define TEST_ARRAY_SIZE 10
#define VALIDATE #define VALIDATE
//define SNAPSHOTS //define SNAPSHOTS
//define TODO_RESTORE_SNAPSHOTS //define TODO_RESTORE_SNAPSHOTS
//define DOT #define STABLE_SEED
#define DOT
#ifdef DOT #ifdef DOT
size_t gen = 0; size_t gen = 0;
FILE *of = 0; FILE *of = 0;
@ -38,8 +40,8 @@ FILE *of = 0;
/* /*
* SKIPLIST EXAMPLE: * SKIPLIST EXAMPLE:
* *
* This example creates an "ex" (example in Italian) Skiplist where keys * This example creates an "ex" Skiplist where keys are integers, values are
* are integers, values are strings allocated on the heap. * strings containing the roman numeral for the key allocated on the heap.
*/ */
/* /*
@ -179,12 +181,38 @@ SKIPLIST_DECL_DOT(ex, api_, entries)
void void
sprintf_ex_node(ex_node_t *node, char *buf) 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); sprintf(buf, "%d:%s", node->key, node->value);
} }
// Function for this demo application. // 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 * static char *
to_lower(char *str) to_lower(char *str)
{ {
@ -241,7 +269,7 @@ shuffle(int *array, size_t n)
if (n > 1) { if (n > 1) {
size_t i; size_t i;
for (i = n - 1; i > 0; 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]; int t = array[j];
array[j] = array[i]; array[j] = array[i];
array[i] = t; array[i] = t;
@ -267,10 +295,12 @@ main()
snap_info_t snaps[TEST_ARRAY_SIZE * 2 + 1]; snap_info_t snaps[TEST_ARRAY_SIZE * 2 + 1];
#endif #endif
xorshift32_seed();
#ifdef DOT #ifdef DOT
of = fopen("/tmp/slm.dot", "w"); of = fopen("/tmp/ex1.dot", "w");
if (!of) { if (!of) {
perror("Failed to open file /tmp/slm.dot"); perror("Failed to open file /tmp/ex1.dot");
return 1; return 1;
} }
#endif #endif
@ -280,6 +310,9 @@ main()
if (list == NULL) if (list == NULL)
return ENOMEM; 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); rc = api_skip_init_ex(list, -12);
if (rc) if (rc)
return rc; return rc;

View file

@ -61,10 +61,10 @@
* *
* A skiplist is a sorted list with O(log(n)) on average for most operations. * 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 * It is a probabilistic datastructure, meaning that it does not guarantee
* O(log(n)) it approximates it over time. This implementation includes * O(log(n)) it approximates it over time. This implementation improves the
* improves the probability by integrating the splay list algorithm for * probability by integrating the splay list algorithm for rebalancing trading
* rebalancing trading off a bit of computational overhead and code complexity * off a bit of computational overhead and code complexity for a nearly always
* for a nearly always optimal, or "perfect" skiplist. * optimal, or "perfect" skiplist.
* *
* Conceptually, a skiplist is arranged as follows: * 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 decl##_node *sle_prev; \
struct __skiplist_##decl##_level { \ struct __skiplist_##decl##_level { \
struct decl##_node *next; \ struct decl##_node *next; \
size_t hits; \
} *sle_levels; \ } *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_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_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(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_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) #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. * Skiplist declarations and access methods.
*/ */
@ -263,6 +256,7 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li
struct decl { \ struct decl { \
size_t slh_length, slh_max_height; \ size_t slh_length, slh_max_height; \
void *slh_aux; \ void *slh_aux; \
int slh_prng_state; \
decl##_node_t *slh_head; \ decl##_node_t *slh_head; \
decl##_node_t *slh_tail; \ decl##_node_t *slh_tail; \
struct { \ struct { \
@ -285,10 +279,21 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li
\ \
typedef struct __skiplist_path_##decl { \ typedef struct __skiplist_path_##decl { \
decl##_node_t *node; /* node traversed in the act of location */ \ 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; \ } __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_ \ * -- __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 \ * 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. \ * 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; \ size_t level = 0; \
double probability = 0.5; \ 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) { \ while (random_value < probability && level < max) { \
level++; \ level++; \
probability *= 0.5; \ 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. \ * seed the PRNG in a predictable way and have reproducible random numbers. \
*/ \ */ \
if (max < 0) \ if (max < 0) \
srand(-max); \ slist->slh_prng_state = -max; \
else \ else \
srand(((unsigned int)time(NULL) ^ getpid())); \ slist->slh_prng_state = ((unsigned int)time(NULL) ^ getpid()); \
fail:; \ fail:; \
return rc; \ return rc; \
} \ } \
@ -599,35 +604,6 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li
return nodes; \ 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_ \ * -- __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. */ \ /* Find the node that matches `node` or NULL. */ \
i = slist->slh_head->field.sle_height; \ i = slist->slh_head->field.sle_height; \
do { \ do { \
path[i + 1].pu = 0; \
while (elm != slist->slh_tail && elm->field.sle_levels[i].next && \ 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) { \ __skip_compare_nodes_##decl(slist, elm->field.sle_levels[i].next, n, slist->slh_aux) < 0) { \
elm = elm->field.sle_levels[i].next; \ 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 = elm; \
path[i + 1].node->field.sle_levels[path[i + 1].in].hits++; \
len++; \ len++; \
} while (i--); \ } while (i--); \
elm = elm->field.sle_levels[0].next; \ elm = elm->field.sle_levels[0].next; \
if (__skip_compare_nodes_##decl(slist, elm, n, slist->slh_aux) == 0) { \ if (__skip_compare_nodes_##decl(slist, elm, n, slist->slh_aux) == 0) { \
path[0].node = elm; \ path[0].node = elm; \
path[0].node->field.sle_levels[0].hits++; \
__skip_rebalance_##decl(slist, len, path); \
} \ } \
return len; \ 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. */ \ /* First element in path should be NULL, reset should start pointing at tail. */ \
path[0].node = NULL; \ path[0].node = NULL; \
path[0].in = 0; \
path[0].pu = 0; \
for (i = 1; i < slist->slh_max_height + 1; i++) { \ for (i = 1; i < slist->slh_max_height + 1; i++) { \
path[i].node = slist->slh_tail; \ 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. */ \ /* 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) */ \ /* Coin toss to determine level of this new node [0, max) */ \
cur_height = slist->slh_head->field.sle_height; \ 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; \ new->field.sle_height = new_height; \
/* Trim the path to at most the new height for the new node. */ \ /* Trim the path to at most the new height for the new node. */ \
for (i = cur_height + 1; i <= new_height; i++) { \ 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. */ \ /* Increase the the list's era/age and record it. */ \
new->field.sle_era = slist->slh_snap.cur_era++; \ 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. */ \ /* Increase our list length (aka. size, count, etc.) by one. */ \
slist->slh_length++; \ 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_tail->field.sle_height = i; \
\ \
slist->slh_length--; \ slist->slh_length--; \
__skip_adjust_hit_counts_##decl(slist); \
} \ } \
return 0; \ return 0; \
} \ } \