diff --git a/examples/slm.c b/examples/slm.c index 6441be1..b53f82f 100644 --- a/examples/slm.c +++ b/examples/slm.c @@ -232,8 +232,7 @@ main() #ifdef SNAPSHOTS /* Test creating a snapshot of an empty Skiplist */ - size_t snp[TEST_ARRAY_SIZE * 2 + 10]; - snp[0] = api_skip_snapshot_slex(list); + api_skip_snapshot_slex(list); #endif /* Insert 7 key/value pairs into the list. */ @@ -254,7 +253,7 @@ main() //rc = api_skip_put_slex(list, array[i], numeral); INTEGRITY_CHK; #ifdef SNAPSHOTS - snp[i + 1] = api_skip_snapshot_slex(list); + api_skip_snapshot_slex(list); INTEGRITY_CHK; #endif #ifdef DOT @@ -267,9 +266,6 @@ main() api_skip_set_slex(list, array[i], to_upper(v)); INTEGRITY_CHK; } -#ifdef SNAPSHOTS - int r = i; -#endif numeral = int_to_roman_numeral(-1); api_skip_dup_slex(list, -1, numeral); INTEGRITY_CHK; @@ -287,7 +283,7 @@ main() INTEGRITY_CHK; #endif #ifdef SNAPSHOTS - snp[++i] = api_skip_snapshot_slex(list); + api_skip_snapshot_slex(list); INTEGRITY_CHK; #endif @@ -308,7 +304,7 @@ main() api_skip_del_slex(list, key); INTEGRITY_CHK; #ifdef SNAPSHOTS - snp[++i] = api_skip_snapshot_slex(list); + api_skip_snapshot_slex(list); INTEGRITY_CHK; #endif diff --git a/include/sl.h b/include/sl.h index 646b46a..3c4315e 100644 --- a/include/sl.h +++ b/include/sl.h @@ -165,7 +165,7 @@ #define SKIPLIST_ENTRY(type) \ struct __skiplist_##decl_entry { \ struct type *sle_prev, **sle_next; \ - size_t sle_height; \ + size_t sle_height, sle_gen; \ } /* @@ -268,7 +268,11 @@ void *slh_aux; \ decl##_node_t *slh_head; \ decl##_node_t *slh_tail; \ - decl##_node_t *slh_snap; \ + struct { \ + decl##_node_t *head; \ + decl##_node_t *tail; \ + size_t gen; \ + } slh_snap; \ } decl##_t; \ \ /* Skip List comparison function type */ \ @@ -324,6 +328,26 @@ return level; \ } \ \ + /** \ + * -- __skip_gen_ \ + * \ + * Returns the current generation for snapshot purposes. \ + */ \ + static inline size_t __skip_gen_##decl(decl##_t *slist) \ + { \ + return slist->slh_snap.gen; \ + } \ + \ + /** \ + * -- __skip_incr_gen_ \ + * \ + * Returns the current generation for snapshot purposes. \ + */ \ + static inline size_t __skip_incr_gen_##decl(decl##_t *slist) \ + { \ + return ++slist->slh_snap.gen; \ + } \ + \ /** \ * -- skip_alloc_node_ \ * \ @@ -585,7 +609,7 @@ if (path == NULL) \ return ENOMEM; \ } \ - memset(path, 0, sizeof(decl##_node_t *) * slist->slh_max + 1); \ + memset(path, 0, sizeof(decl##_node_t *) * slist->slh_max + 1); \ \ /* Find a `path` to `new` in the list and a match (`path[0]`) if it exists. */ \ len = __skip_locate_##decl(slist, new, path); \ @@ -642,6 +666,8 @@ slist->slh_head->field.sle_height = new_height; \ slist->slh_tail->field.sle_height = new_height; \ } \ + /* Record this node's generation for snapshots. */ \ + new->field.sle_gen = __skip_gen_##decl(slist); \ /* Increase our list length (aka. size, count, etc.) by one. */ \ slist->slh_length++; \ \ @@ -892,7 +918,7 @@ if (path == NULL) \ return ENOMEM; \ } \ - memset(path, 0, sizeof(sizeof(decl##_node_t *) * slist->slh_max + 1)); \ + memset(path, 0, sizeof(decl##_node_t *) * slist->slh_max + 1); \ \ __skip_locate_##decl(slist, new, path); \ node = path[0]; \ @@ -903,6 +929,7 @@ if (node) { \ decl##_node_t *src = node, *dest = new; \ update_node_blk; \ + node->field.sle_gen = __skip_gen_##decl(slist); \ return rc; \ } \ return -1; \ @@ -930,7 +957,7 @@ if (path == NULL) \ return ENOMEM; \ } \ - memset(path, 0, sizeof(sizeof(decl##_node_t *) * slist->slh_max + 1)); \ + memset(path, 0, sizeof(decl##_node_t *) * slist->slh_max + 1); \ \ /* Attempt to locate the node in the list. */ \ len = __skip_locate_##decl(slist, n, path); \ @@ -993,14 +1020,104 @@ return; \ } \ \ + /** \ + * -- skip_snap_head_ \ + * \ + * Returns the node containing the first (smallest) element in the \ + * snapshot which can be used to traverse the list. \ + */ \ + decl##_node_t *prefix##skip_snap_head_##decl(decl##_t *slist) \ + { \ + return slist->slh_snap.head->field.sle_next[0] == slist->slh_snap.tail ? NULL : slist->slh_snap.head->field.sle_next[0]; \ + } \ + \ + /** \ + * -- skip_snap_tail_ \ + * \ + * Returns the node containing the last (largest) element in the \ + * snapshot which can be used to traverse the list. \ + */ \ + decl##_node_t *prefix##skip_snap_tail_##decl(decl##_t *slist) \ + { \ + return slist->slh_snap.tail->field.sle_prev == slist->slh_snap.head->field.sle_next[0] ? NULL : slist->slh_snap.tail->field.sle_prev; \ + } \ + \ /** \ * -- skip_release_snapshot_ \ * \ - * Removes ... TODO \ + * ALGORITHM: \ + * A snapshot starts with a copy of the head and the tail of the list. \ + * Those can be free'd here as our last step. We record a generation, which \ + * is a monatomically growing unsigned int, at the time of the snapshot, \ + * nodes inserted or mutated have there generation updated. To find the \ + * nodes in the list that are only referenced by the snapshot we can perform \ + * a "mergesort" like traversal of both the snapshot and the live list. For \ + * each node, if the keys match and the nodes are not equal (same address in \ + * memory) and we've reached a node greater than the snap node then we can \ + * free the node in the snap. Duplicates complicate this a bit. \ */ \ void prefix##skip_release_snapshot_##decl(decl##_t *slist) \ { \ - ((void)slist); \ + int cmp; \ + size_t nth, cnt = 0; \ + decl##_node_t *sn, *ln, *node, *next; \ + \ + if (slist == NULL) \ + return; \ + \ + sn = prefix##skip_snap_head_##decl(slist); \ + SKIPLIST_FOREACH_H2T(decl, prefix, slist, ln, nth) \ + { \ + goto top_of_loop; \ + sn_forward_only:; \ + sn = sn->field.sle_next[0]; \ + if (sn == slist->slh_snap.tail) \ + break; \ + top_of_loop:; \ + /* When a node is referenced by both, keep going. */ \ + if (ln == sn) \ + goto both_sn_and_ln_forward; \ + \ + cmp = slist->slh_cmp(slist, sn, ln, slist->slh_aux); \ + /* When the `sn` > the `ln`, move forward `ln` only. */ \ + if (cmp > 0) \ + goto ln_forward_only; \ + /* When the `sn` < the `ln`, move both forward. */ \ + if (cmp < 0) { \ + sn = sn->field.sle_next[0]; \ + if (sn == slist->slh_snap.tail) \ + break; \ + goto sn_forward_only; \ + } \ + /* When nodes match but have different addresses... */ \ + if (cmp == 0) { \ + /* ... we could have: \ + a) a duplicate, or \ + b) a copied a node, but not it's value, because it was \ + adjacent to some mutated node (it was in the `path[]` \ + during an insert, update, or delete), or \ + c) ...? \ + */ \ + cnt++; \ + } \ + /* ln forward only */ \ + goto ln_forward_only; \ + \ + ln_forward_only:; \ + continue; \ + both_sn_and_ln_forward:; \ + sn = sn->field.sle_next[0]; \ + if (sn == slist->slh_snap.tail) \ + break; \ + } \ + /* If `sn` isn't at tail then there are a few more nodes to release. */ \ + while (sn != slist->slh_snap.tail) { \ + node = sn; \ + free_node_blk; \ + next = sn->field.sle_next[0]; \ + free(sn); \ + sn = next; \ + } \ } \ \ /** \ @@ -1008,35 +1125,29 @@ * \ * Preserve given node in the slh_pres list. \ */ \ - static int __skip_preserve_node_##decl(decl##_t *slist, decl##_node_t *node, decl##_node_t **preserved) \ + static int __skip_preserve_node_##decl(decl##_t *slist, const decl##_node_t *src, decl##_node_t **preserved) \ { \ int rc = 0; \ - size_t amt, i; \ - char *d; \ - const char *s; \ - decl##_node_t *src, *dest; \ + decl##_node_t *dest; \ \ - if (slist == NULL || node == NULL) \ + if (slist == NULL || src == NULL) \ return 0; \ \ /* (a) alloc */ \ rc = prefix##skip_alloc_node_##decl(slist, &dest); \ if (rc) \ return rc; \ - amt = sizeof(decl##_node_t); \ \ /* (b) shallow copy */ \ - s = (const char *)node; \ - d = (char *)dest; \ - for (i = 0; i < amt; i++) \ - d[i] = s[i]; \ + memcpy(dest, src, sizeof(decl##_node_t)); \ \ - /* (d) deep copy */ \ - src = node; \ - archive_node_blk; \ - if (rc) { \ - prefix##skip_free_node_##decl(dest); \ - return rc; \ + if (!(src == slist->slh_head || src == slist->slh_tail)) { \ + /* (d) deep copy */ \ + archive_node_blk; \ + if (rc) { \ + prefix##skip_free_node_##decl(dest); \ + return rc; \ + } \ } \ \ *preserved = dest; \ @@ -1065,15 +1176,36 @@ size_t prefix##skip_snapshot_##decl(decl##_t *slist) \ { \ int rc; \ + decl##_node_t *head, *tail; \ \ if (slist == NULL) \ return 0; \ - if (slist->slh_snap != NULL) \ - prefix##skip_release_snapshot_##decl(slist); \ - rc = __skip_preserve_node_##decl(slist, slist->slh_head, &slist->slh_snap); \ + /* (a) preserve the head node */ \ + rc = __skip_preserve_node_##decl(slist, slist->slh_head, &head); \ if (rc > 0) \ return rc; \ - slist->slh_gen++; \ + /* (b) preserve the tail node */ \ + rc = __skip_preserve_node_##decl(slist, slist->slh_tail, &tail); \ + if (rc > 0) { \ + prefix##skip_free_node_##decl(head); \ + return rc; \ + } \ + /* (c) update any references in the new head's next[] array to point to \ + the new tail; update the prev field in the new tail to the new head \ + for similar reasons. */ \ + __SKIP_ENTRIES_B2T(field, head) \ + { \ + if (head->field.sle_next[lvl] == slist->slh_tail) \ + head->field.sle_next[lvl] = tail; \ + } \ + if (tail->field.sle_prev == slist->slh_head) \ + tail->field.sle_prev = head; \ + /* (d) release previous snapshot, assign the new one */ \ + if (slist->slh_snap.head != NULL) \ + prefix##skip_release_snapshot_##decl(slist); \ + slist->slh_snap.head = head; \ + slist->slh_snap.tail = tail; \ + slist->slh_snap.gen = __skip_incr_gen_##decl(slist); \ return slist->slh_gen; \ } @@ -1092,6 +1224,7 @@ */ \ static int __skip_integrity_check_##decl(decl##_t *slist, int flags) \ { \ + size_t n = 0; \ unsigned long nth, n_err = 0; \ decl##_node_t *node, *prev, *next; \ struct __skiplist_##decl_entry *this; \ @@ -1152,7 +1285,6 @@ return n_err; \ } \ \ - size_t n = 0; \ node = slist->slh_head; \ __SKIP_ENTRIES_B2T(field, node) \ { \ @@ -1220,7 +1352,6 @@ return n_err; \ } \ \ - size_t n = 0; \ __SKIP_ENTRIES_B2T(field, node) \ { \ if (this->sle_next[lvl] == NULL) { \ @@ -1488,7 +1619,7 @@ if (next) \ fprintf(os, "%p } |", (void *)next); \ else \ - fprintf(os, "tail } |"); \ + fprintf(os, "0x0 } |"); \ fflush(os); \ } \ if (fn) { \ @@ -1509,7 +1640,7 @@ fprintf(os, ":f%lu -> ", lvl); \ __skip_dot_write_node_##decl(os, nsg, next); \ fprintf(os, ":w%lu [];\n", lvl); \ - fflush(os); \ + fflush(os); \ } \ } \ \ @@ -1597,7 +1728,7 @@ if (next) \ fprintf(os, "%p }", (void *)next); \ else \ - fprintf(os, "tail }"); \ + fprintf(os, "0x0 }"); \ __SKIP_IS_LAST_ENTRY_T2B() continue; \ fprintf(os, " | "); \ } \ @@ -1645,7 +1776,7 @@ size_t th = slist->slh_head->field.sle_height; \ for (size_t lvl = th; lvl != (size_t)-1; lvl--) { \ next = (node->field.sle_next[lvl] == slist->slh_tail) ? NULL : node->field.sle_next[lvl]; \ - fprintf(os, " TAIL", lvl); \ + fprintf(os, " 0x0", lvl); \ __SKIP_IS_LAST_ENTRY_T2B() continue; \ fprintf(os, " | "); \ } \