]> pd.if.org Git - nbds/blob - struct/list.c
optimize 8 byte integer keys for list and skiplist
[nbds] / struct / list.c
1 /* 
2  * Written by Josh Dybnis and released to the public domain, as explained at
3  * http://creativecommons.org/licenses/publicdomain
4  *
5  * Harris-Michael lock-free list-based set
6  * http://www.research.ibm.com/people/m/michael/spaa-2002.pdf
7  */
8 #include <stdio.h>
9 #include <string.h>
10
11 #include "common.h"
12 #include "struct.h"
13 #include "nstring.h"
14 #include "mem.h"
15
16 typedef struct node {
17     nstring_t *key;
18     uint64_t value;
19     struct node *next;
20 } node_t;
21
22 struct ll {
23     node_t *head;
24 };
25
26 static node_t *node_alloc (const void *key_data, uint32_t key_len, uint64_t value) {
27     node_t *item = (node_t *)nbd_malloc(sizeof(node_t));
28     memset(item, 0, sizeof(node_t));
29     // If <key_len> is -1 it indicates <key_data> is an integer and not a pointer
30     item->key = (key_len == (unsigned)-1) 
31               ? (void *)TAG_VALUE(key_data) 
32               : ns_alloc(key_data, key_len); 
33     item->value = value;
34     return item;
35 }
36
37 list_t *ll_alloc (void) {
38     list_t *ll = (list_t *)nbd_malloc(sizeof(list_t));
39     ll->head = node_alloc(" ", 0, 0);
40     ll->head->next = NULL;
41     return ll;
42 }
43
44 static node_t *find_pred (node_t **pred_ptr, list_t *ll, const void *key_data, uint32_t key_len, int help_remove) {
45     node_t *pred = ll->head;
46     node_t *item = pred->next;
47     TRACE("l3", "find_pred: searching for key %p in ll (head is %p)", key_data, pred);
48
49     while (item != NULL) {
50         node_t *next = item->next;
51         TRACE("l3", "find_pred: visiting item %p (next %p)", item, next);
52         TRACE("l3", "find_pred: key %p", STRIP_TAG(item->key), item->value);
53
54         // A tag means an item is logically removed but not physically unlinked yet.
55         while (EXPECT_FALSE(IS_TAGGED(next))) {
56
57             // Skip over logically removed items.
58             if (!help_remove) {
59                 item = (node_t *)STRIP_TAG(item->next);
60                 if (EXPECT_FALSE(item == NULL))
61                     break;
62                 next = item->next;
63                 continue;
64             }
65
66             // Unlink logically removed items.
67             node_t *other;
68             if ((other = SYNC_CAS(&pred->next, item, STRIP_TAG(next))) == item) {
69                 item = (node_t *)STRIP_TAG(next);
70                 if (EXPECT_FALSE(item == NULL))
71                     break;
72                 next = item->next;
73                 TRACE("l3", "find_pred: unlinked item %p from pred %p", item, pred);
74                 TRACE("l3", "find_pred: now item is %p next is %p", item, next);
75
76                 // The thread that completes the unlink should free the memory.
77                 nbd_defer_free(other);
78             } else {
79                 TRACE("l3", "find_pred: lost race to unlink from pred %p; its link changed to %p", pred, other);
80                 if (IS_TAGGED(other))
81                     return find_pred(pred_ptr, ll, key_data, key_len, help_remove); // retry
82                 item = other;
83                 if (EXPECT_FALSE(item == NULL))
84                     break;
85                 next = item->next;
86             }
87         }
88
89         if (EXPECT_FALSE(item == NULL))
90             break;
91
92         // If we reached the key (or passed where it should be), we found the right predesssor
93         int x = (IS_TAGGED(item->key))
94               ? (STRIP_TAG(item->key) - (uint64_t)key_data)
95               : ns_cmp_raw(item->key, key_data, key_len);
96         if (x >= 0) {
97             TRACE("l3", "find_pred: found pred %p item %p", pred, item);
98             if (pred_ptr != NULL) {
99                 *pred_ptr = pred;
100             }
101             return x == 0 ? item : NULL;
102         }
103
104         pred = item;
105         item = next;
106
107     }
108
109     // <key> is not in <ll>.
110     if (pred_ptr != NULL) {
111         *pred_ptr = pred;
112     }
113     return NULL;
114 }
115
116 // Fast find. Do not help unlink partially removed nodes and do not return the found item's predecessor.
117 uint64_t ll_lookup (list_t *ll, const void *key_data, uint32_t key_len) {
118     TRACE("l3", "ll_lookup: searching for key %p in ll %p", key_data, ll);
119     node_t *item = find_pred(NULL, ll, key_data, key_len, FALSE);
120
121     // If we found an <item> matching the key return its value.
122     return item != NULL ? item->value : DOES_NOT_EXIST;
123 }
124
125 // Insert a new item if a matching key doesn't already exist in <ll>
126 uint64_t ll_add (list_t *ll, const void *key_data, uint32_t key_len, uint64_t value) {
127     TRACE("l3", "ll_add: inserting key %p value %p", key_data, value);
128     node_t *pred;
129     node_t *item = NULL;
130     do {
131         node_t *next = find_pred(&pred, ll, key_data, key_len, TRUE);
132
133         // If a node matching the key already exists in <ll> return its value.
134         if (next != NULL) {
135             TRACE("l3", "ll_add: there is already an item %p (value %p) with the same key", next, next->value);
136             if (EXPECT_FALSE(item != NULL)) { nbd_free(item); }
137             return next->value;
138         }
139
140         next = pred->next;
141         TRACE("l3", "ll_add: attempting to insert item between %p and %p", pred, next);
142         if (EXPECT_TRUE(item == NULL)) { item = node_alloc(key_data, key_len, value); }
143         item->next = next;
144         node_t *other = SYNC_CAS(&pred->next, next, item);
145         if (other == next) {
146             TRACE("l3", "ll_add: successfully inserted item %p", item, 0);
147             return DOES_NOT_EXIST; // success
148         }
149         TRACE("l3", "ll_add: failed to change pred's link: expected %p found %p", next, other);
150
151     } while (1);
152 }
153
154 uint64_t ll_remove (list_t *ll, const void *key_data, uint32_t key_len) {
155     TRACE("l3", "ll_remove: removing item with key %p from ll %p", key_data, ll);
156     node_t *pred;
157     node_t *item = find_pred(&pred, ll, key_data, key_len, TRUE);
158     if (item == NULL) {
159         TRACE("l3", "ll_remove: remove failed, an item with a matching key does not exist in the ll", 0, 0);
160         return DOES_NOT_EXIST;
161     }
162
163     // Mark <item> removed. This must be atomic. If multiple threads try to remove the same item
164     // only one of them should succeed.
165     if (EXPECT_FALSE(IS_TAGGED(item->next))) {
166         TRACE("l3", "ll_remove: %p is already marked for removal by another thread", item, 0);
167         return DOES_NOT_EXIST;
168     }
169     node_t *next = SYNC_FETCH_AND_OR(&item->next, TAG);
170     if (EXPECT_FALSE(IS_TAGGED(next))) {
171         TRACE("l3", "ll_remove: lost race -- %p is already marked for removal by another thread", item, 0);
172         return DOES_NOT_EXIST;
173     }
174
175     uint64_t value = item->value;
176
177     // Unlink <item> from <ll>.
178     TRACE("l3", "ll_remove: link item's pred %p to it's successor %p", pred, next);
179     node_t *other;
180     if ((other = SYNC_CAS(&pred->next, item, next)) != item) {
181         TRACE("l3", "ll_remove: unlink failed; pred's link changed from %p to %p", item, other);
182         // By marking the item earlier, we logically removed it. It is safe to leave the item.
183         // Another thread will finish physically removing it from the ll.
184         return value;
185     } 
186
187     // The thread that completes the unlink should free the memory.
188     nbd_defer_free(item); 
189     return value;
190 }
191
192 void ll_print (list_t *ll) {
193     node_t *item;
194     item = ll->head->next;
195     while (item) {
196         if (IS_TAGGED(item->key)) {
197             printf("0x%llx ", STRIP_TAG(item->key));
198         } else {
199             printf("%s ", (char *)ns_data(item->key));
200         }
201         fflush(stdout);
202         item = item->next;
203     }
204     printf("\n");
205 }