]> pd.if.org Git - nbds/blob - struct/skiplist.c
df8e98a75ea6c2bafe77dec7ec6ddae1c50f16c9
[nbds] / struct / skiplist.c
1 /* 
2  * Written by Josh Dybnis and released to the public domain, as explained at
3  * http://creativecommons.org/licenses/publicdomain
4  *
5  * Implementation of the lock-free skiplist data-structure created by Maurice Herlihy, Yossi Lev,
6  * and Nir Shavit. See Herlihy's and Shivit's book "The Art of Multiprocessor Programming".
7  * http://www.amazon.com/Art-Multiprocessor-Programming-Maurice-Herlihy/dp/0123705916/
8  *
9  * See also Kir Fraser's dissertation "Practical Lock Freedom".
10  * www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf
11  *
12  * This code is written for the x86 memory-model. The algorithim depends on certain stores and
13  * loads being ordered. Be careful, this code probably won't work correctly on platforms with
14  * weaker memory models if you don't add memory barriers in the right places.
15  */
16 #include <stdio.h>
17 #include <string.h>
18
19 #include "common.h"
20 #include "runtime.h"
21 #include "struct.h"
22 #include "mem.h"
23 #include "tls.h"
24
25 // Setting MAX_LEVEL to 0 essentially makes this data structure the Harris-Michael lock-free list
26 // in list.c
27 #define MAX_LEVEL 31
28
29 typedef struct node {
30     uint64_t key;
31     uint64_t value;
32     int top_level;
33     struct node *next[];
34 } node_t;
35
36 struct sl {
37     node_t *head;
38 };
39
40 static int random_level (void) {
41     unsigned r = nbd_rand();
42     if (r&1)
43         return 0;
44     int n = __builtin_ctz(r)-1;
45 #if MAX_LEVEL < 31
46     if (n > MAX_LEVEL)
47         return MAX_LEVEL;
48 #endif
49     assert(n <= MAX_LEVEL);
50     return n;
51 }
52
53 node_t *node_alloc (int level, uint64_t key, uint64_t value) {
54     assert(level >= 0 && level <= MAX_LEVEL);
55     size_t sz = sizeof(node_t) + (level + 1) * sizeof(node_t *);
56     node_t *item = (node_t *)nbd_malloc(sz);
57     memset(item, 0, sz);
58     item->key   = key;
59     item->value = value;
60     item->top_level = level;
61     return item;
62 }
63
64 skiplist_t *sl_alloc (void) {
65     skiplist_t *sl = (skiplist_t *)nbd_malloc(sizeof(skiplist_t));
66     sl->head = node_alloc(MAX_LEVEL, 0, 0);
67     memset(sl->head->next, 0, (MAX_LEVEL+1) * sizeof(skiplist_t *));
68     return sl;
69 }
70
71 static node_t *find_preds (node_t *preds[MAX_LEVEL+1], int n, skiplist_t *sl, uint64_t key, int help_remove) {
72     node_t *pred = sl->head;
73     node_t *item = NULL;
74     TRACE("s3", "find_preds: searching for key %p in sl (head is %p)", key, pred);
75
76     int start_level = MAX_LEVEL;
77 #if MAX_LEVEL > 2
78     // Optimization for small lists. No need to traverse empty higher levels.
79     start_level = 2;
80     while (pred->next[start_level+1] != NULL) {
81         start_level += start_level - 1;
82         if (EXPECT_FALSE(start_level >= MAX_LEVEL)) {
83             start_level = MAX_LEVEL;
84             break;
85         }
86     }
87     if (EXPECT_FALSE(start_level < n)) {
88         start_level = n;
89     }
90 #endif
91
92     // Traverse the levels of <sl> from the top level to the bottom
93     for (int level = start_level; level >= 0; --level) {
94         TRACE("s3", "find_preds: level %llu", level, 0);
95         item = pred->next[level];
96         if (EXPECT_FALSE(IS_TAGGED(item))) {
97             TRACE("s3", "find_preds: pred %p is marked for removal (item %p); retry", pred, item);
98             return find_preds(preds, n, sl, key, help_remove); // retry
99         }
100         while (item != NULL) {
101             node_t *next = item->next[level];
102             TRACE("s3", "find_preds: visiting item %p (next %p)", item, next);
103             TRACE("s3", "find_preds: key %p", item->key, 0);
104
105             // Marked items are logically removed, but not fully unlinked yet.
106             while (EXPECT_FALSE(IS_TAGGED(next))) {
107
108                 // Skip over partially removed items.
109                 if (!help_remove) {
110                     item = (node_t *)STRIP_TAG(item->next);
111                     if (EXPECT_FALSE(item == NULL))
112                         break;
113                     next = item->next[level];
114                     continue;
115                 }
116
117                 // Unlink partially removed items.
118                 node_t *other;
119                 if ((other = SYNC_CAS(&pred->next[level], item, STRIP_TAG(next))) == item) {
120                     item = (node_t *)STRIP_TAG(next);
121                     if (EXPECT_FALSE(item == NULL))
122                         break;
123                     next = item->next[level];
124                     TRACE("s3", "find_preds: unlinked item %p from pred %p", item, pred);
125                     TRACE("s3", "find_preds: now item is %p next is %p", item, next);
126
127                     // The thread that completes the unlink should free the memory.
128                     if (level == 0) { nbd_defer_free(other); }
129                 } else {
130                     TRACE("s3", "find_preds: lost race to unlink from pred %p; its link changed to %p", pred, other);
131                     if (IS_TAGGED(other))
132                         return find_preds(preds, n, sl, key, help_remove); // retry
133                     item = other;
134                     if (EXPECT_FALSE(item == NULL))
135                         break;
136                     next = item->next[level];
137                 }
138             }
139
140             if (EXPECT_FALSE(item == NULL))
141                 break;
142
143             // If we reached the key (or passed where it should be), we found a pred. Save it and continue down.
144             if (item->key >= key) {
145                 TRACE("s3", "find_preds: found pred %p item %p", pred, item);
146                 break;
147             }
148
149             pred = item;
150             item = next;
151         }
152         if (preds != NULL) {
153             preds[level] = pred;
154         }
155     }
156     if (n == -1 && item != NULL) {
157         assert(preds != NULL);
158         for (int level = start_level + 1; level <= item->top_level; ++level) {
159             preds[level] = sl->head;
160         }
161     }
162     return item;
163 }
164
165 // Fast find that does not help unlink partially removed nodes and does not return the node's predecessors.
166 uint64_t sl_lookup (skiplist_t *sl, uint64_t key) {
167     TRACE("s3", "sl_lookup: searching for key %p in sl %p", key, sl);
168     node_t *item = find_preds(NULL, 0, sl, key, FALSE);
169
170     // If we found an <item> matching the <key> return its value.
171     return (item && item->key == key) ? item->value : DOES_NOT_EXIST;
172 }
173
174 // Insert the <key> if it doesn't already exist in <sl>
175 uint64_t sl_add (skiplist_t *sl, uint64_t key, uint64_t value) {
176     TRACE("s3", "sl_add: inserting key %p value %p", key, value);
177     node_t *preds[MAX_LEVEL+1];
178     node_t *item = NULL;
179     do {
180         int n = random_level();
181         node_t *next = find_preds(preds, n, sl, key, TRUE);
182
183         // If a node matching <key> already exists in <sl>, return its value.
184         if (next != NULL && next->key == key) {
185             TRACE("s3", "sl_add: there is already an item %p (value %p) with the same key", next, next->value);
186             if (EXPECT_FALSE(item != NULL)) { nbd_free(item); }
187             return next->value;
188         }
189
190         // First insert <item> into the bottom level.
191         if (EXPECT_TRUE(item == NULL)) { item = node_alloc(n, key, value); }
192         TRACE("s3", "sl_add: attempting to insert item between %p and %p", preds[0], next);
193         item->next[0] = next;
194         for (int level = 1; level <= item->top_level; ++level) {
195             node_t *pred = preds[level];
196             item->next[level] = pred->next[level];
197         }
198         node_t *pred = preds[0];
199         node_t *other = SYNC_CAS(&pred->next[0], next, item);
200         if (other == next) {
201             TRACE("s3", "sl_add: successfully inserted item %p at level 0", item, 0);
202             break; // success
203         }
204         TRACE("s3", "sl_add: failed to change pred's link: expected %p found %p", next, other);
205
206     } while (1);
207
208     // Insert <item> into <sl> from the bottom level up.
209     for (int level = 1; level <= item->top_level; ++level) {
210         do {
211             node_t *pred;
212             node_t *next;
213             do {
214                 pred = preds[level];
215                 next = pred->next[level];
216                 if (next == NULL) // item goes at the end of the list
217                     break;
218                 if (!IS_TAGGED(next) && next->key > key) // pred's link changed
219                     break;
220                 find_preds(preds, item->top_level, sl, key, TRUE);
221             } while (1);
222
223             do {
224                 // There in no need to continue linking in the item if another thread removed it.
225                 node_t *old_next = ((volatile node_t *)item)->next[level];
226                 if (IS_TAGGED(old_next))
227                     return DOES_NOT_EXIST; // success
228
229                 // Use a CAS so we to not inadvertantly remove a mark another thread placed on the item.
230                 if (next == old_next || SYNC_CAS(&item->next[level], old_next, next) == old_next)
231                     break;
232             } while (1);
233
234             TRACE("s3", "sl_add: attempting to insert item between %p and %p", pred, next);
235             node_t *other = SYNC_CAS(&pred->next[level], next, item);
236             if (other == next) {
237                 TRACE("s3", "sl_add: successfully inserted item %p at level %llu", item, level);
238                 break; // success
239             }
240             TRACE("s3", "sl_add: failed to change pred's link: expected %p found %p", next, other);
241
242         } while (1);
243     }
244     return value;
245 }
246
247 uint64_t sl_remove (skiplist_t *sl, uint64_t key) {
248     TRACE("s3", "sl_remove: removing item with key %p from sl %p", key, sl);
249     node_t *preds[MAX_LEVEL+1];
250     node_t *item = find_preds(preds, -1, sl, key, TRUE);
251     if (item == NULL || item->key != key) {
252         TRACE("s3", "sl_remove: remove failed, an item with a matching key does not exist in the sl", 0, 0);
253         return DOES_NOT_EXIST;
254     }
255
256     // Mark <item> removed at each level of <sl> from the top down. This must be atomic. If multiple threads
257     // try to remove the same item only one of them should succeed. Marking the bottom level establishes which of 
258     // them succeeds.
259     for (int level = item->top_level; level >= 0; --level) {
260         if (EXPECT_FALSE(IS_TAGGED(item->next[level]))) {
261             TRACE("s3", "sl_remove: %p is already marked for removal by another thread", item, 0);
262             if (level == 0)
263                 return DOES_NOT_EXIST;
264             continue;
265         }
266         node_t *next = SYNC_FETCH_AND_OR(&item->next[level], TAG);
267         if (EXPECT_FALSE(IS_TAGGED(next))) {
268             TRACE("s3", "sl_remove: lost race -- %p is already marked for removal by another thread", item, 0);
269             if (level == 0)
270                 return DOES_NOT_EXIST;
271             continue;
272         }
273     }
274
275     uint64_t value = item->value;
276
277     // Unlink <item> from the top down.
278     int level = item->top_level;
279     while (level >= 0) {
280         node_t *pred = preds[level];
281         node_t *next = item->next[level];
282         TRACE("s3", "sl_remove: link item's pred %p to it's successor %p", pred, STRIP_TAG(next));
283         node_t *other = NULL;
284         if ((other = SYNC_CAS(&pred->next[level], item, STRIP_TAG(next))) != item) {
285             TRACE("s3", "sl_remove: unlink failed; pred's link changed from %p to %p", item, other);
286             // By marking the item earlier, we logically removed it. It is safe to leave the item partially
287             // unlinked. Another thread will finish physically removing it from <sl>.
288             return value;
289         }
290         --level; 
291     }
292
293     // The thread that completes the unlink should free the memory.
294     nbd_defer_free(item); 
295     return value;
296 }
297
298 void sl_print (skiplist_t *sl) {
299     for (int level = MAX_LEVEL; level >= 0; --level) {
300         node_t *item = sl->head;
301         if (item->next[level] == NULL)
302             continue;
303         printf("(%d) ", level);
304         while (item) {
305             node_t *next = item->next[level];
306             printf("%s%p ", IS_TAGGED(next) ? "*" : "", item);
307             item = (node_t *)STRIP_TAG(next);
308         }
309         printf("\n");
310         fflush(stdout);
311     }
312
313     printf("\n");
314     node_t *item = sl->head;
315     while (item) {
316         int is_marked = IS_TAGGED(item->next[0]);
317         printf("%s%p:0x%llx ", is_marked ? "*" : "", item, item->key);
318         if (item != sl->head) {
319             printf("[%d]", item->top_level);
320         } else {
321             printf("[*]");
322         }
323         for (int level = 1; level <= item->top_level; ++level) {
324             node_t *next = (node_t *)STRIP_TAG(item->next[level]);
325             is_marked = IS_TAGGED(item->next[0]);
326             printf(" %p%s", next, is_marked ? "*" : "");
327             if (item == sl->head && item->next[level] == NULL)
328                 break;
329         }
330         printf("\n");
331         fflush(stdout);
332         item = (node_t *)STRIP_TAG(item->next[0]);
333     }
334 }