2 * Written by Josh Dybnis and released to the public domain, as explained at
3 * http://creativecommons.org/licenses/publicdomain
5 * Harris-Michael lock-free list-based set
6 * http://www.research.ibm.com/people/m/michael/spaa-2002.pdf
16 #define NUM_ITERATIONS 10000000
18 #define PLACE_MARK(x) (((size_t)(x))|1)
19 #define CLEAR_MARK(x) (((size_t)(x))&~(size_t)1)
20 #define IS_MARKED(x) ((size_t)(x))&1
32 static void list_node_init (node_t *item, int key) {
33 memset(item, 0, sizeof(node_t));
37 node_t *list_node_alloc (int key) {
38 node_t *item = (node_t *)nbd_malloc(sizeof(node_t));
39 list_node_init(item, key);
43 list_t *list_alloc (void) {
44 list_t *list = (list_t *)nbd_malloc(sizeof(list_t));
45 list_node_init(list->head, INT_MIN);
46 list_node_init(&list->last, INT_MAX);
47 list->head->next = &list->last;
51 static void find_pred_and_item (node_t **pred_ptr, node_t **item_ptr, list_t *list, int key) {
52 node_t *pred = list->head;
53 node_t *item = list->head->next; // head is never removed
54 TRACE("l3", "find_pred_and_item: searching for key %llu in list (head is %p)", key, pred);
60 node_t *other, *next = item->next;
61 TRACE("l3", "find_pred_and_item: visiting item %p (next is %p)", item, next);
62 while (EXPECT_FALSE(IS_MARKED(next))) {
64 // assist in unlinking partially removed items
65 if ((other = SYNC_CAS(&pred->next, item, CLEAR_MARK(next))) != item)
67 TRACE("l3", "find_pred_and_item: failed to unlink item from pred %p, pred's next pointer was changed to %p", pred, other);
68 return find_pred_and_item(pred_ptr, item_ptr, list, key); // retry
72 item = (node_t *)CLEAR_MARK(next);
74 TRACE("l3", "find_pred_and_item: unlinked item, %p is the new item (next is %p)", item, next);
77 if (item->key >= key) {
80 TRACE("l3", "find_pred_and_item: key found, returning pred %p and item %p", pred, item);
91 int list_insert (list_t *list, node_t *item) {
92 TRACE("l3", "list_insert: inserting %p (with key %llu)", item, item->key);
93 node_t *pred, *next, *other = (node_t *)-1;
95 if (other != (node_t *)-1) {
96 TRACE("l3", "list_insert: failed to swap item into list; pred's next was changed to %p", other, 0);
98 find_pred_and_item(&pred, &next, list, item->key);
100 // fail if item already exists in list
101 if (next->key == item->key)
103 TRACE("l3", "list_insert: insert failed item with key already exists %p", next, 0);
108 TRACE("l3", "list_insert: attempting to insert item between %p and %p", pred, next);
110 } while ((other = __sync_val_compare_and_swap(&pred->next, next, item)) != next);
112 TRACE("l3", "list_insert: insert was successful", 0, 0);
118 node_t *list_remove (list_t *list, int key) {
119 node_t *pred, *item, *next;
121 TRACE("l3", "list_remove: removing item with key %llu", key, 0);
122 find_pred_and_item(&pred, &item, list, key);
123 if (item->key != key)
125 TRACE("l3", "list_remove: remove failed, key does not exist in list", 0, 0);
129 // Mark <item> removed, must be atomic. If multiple threads try to remove the
130 // same item only one of them should succeed
132 node_t *other = (node_t *)-1;
133 if (IS_MARKED(next) || (other = __sync_val_compare_and_swap(&item->next, next, PLACE_MARK(next))) != next) {
134 if (other == (node_t *)-1) {
135 TRACE("l3", "list_remove: retry; %p is already marked for removal (it's next pointer is %p)", item, next);
137 TRACE("l3", "list_remove: retry; failed to mark %p for removal; it's next pointer was %p, but changed to %p", next, other);
139 return list_remove(list, key); // retry
142 // Remove <item> from list
143 TRACE("l3", "list_remove: link item's pred %p to it's successor %p", pred, next);
144 if ((other = __sync_val_compare_and_swap(&pred->next, item, next)) != item) {
145 TRACE("l3", "list_remove: link failed; pred's link changed from %p to %p", item, other);
147 // make sure item gets unlinked before returning it
149 find_pred_and_item(&d1, &d2, list, key);
151 TRACE("l3", "list_remove: link succeeded; pred's link changed from %p to %p", item, next);
157 void list_print (list_t *list) {
161 printf("%d ", item->key);
168 #ifdef MAKE_list_test
173 static volatile int wait_;
174 static long num_threads_;
175 static list_t *list_;
177 void *worker (void *arg) {
178 int id = (int)(size_t)arg;
180 unsigned int rand_seed = id+1;//rdtsc_l();
182 // Wait for all the worker threads to be ready.
183 __sync_fetch_and_add(&wait_, -1);
185 __asm__ __volatile__("lfence");
188 for (i = 0; i < NUM_ITERATIONS/num_threads_; ++i) {
189 int n = rand_r(&rand_seed);
190 int key = (n & 0xF) + 1;
192 node_t *item = list_node_alloc(key);
193 int success = list_insert(list_, item);
198 node_t *item = list_remove(list_, key);
200 nbd_defer_free(item);
210 int main (int argc, char **argv) {
212 //lwt_set_trace_level("m0l0");
214 char* program_name = argv[0];
215 pthread_t thread[MAX_NUM_THREADS];
218 fprintf(stderr, "Usage: %s num_threads\n", program_name);
226 num_threads_ = strtol(argv[1], NULL, 10);
228 fprintf(stderr, "%s: Invalid argument for number of threads\n", program_name);
231 if (num_threads_ <= 0) {
232 fprintf(stderr, "%s: Number of threads must be at least 1\n", program_name);
235 if (num_threads_ > MAX_NUM_THREADS) {
236 fprintf(stderr, "%s: Number of threads cannot be more than %d\n", program_name, MAX_NUM_THREADS);
241 list_ = list_alloc();
243 struct timeval tv1, tv2;
244 gettimeofday(&tv1, NULL);
246 __asm__ __volatile__("sfence");
247 wait_ = num_threads_;
250 for (i = 0; i < num_threads_; ++i) {
251 int rc = nbd_thread_create(thread + i, i, worker, (void*)(size_t)i);
252 if (rc != 0) { perror("pthread_create"); return rc; }
255 for (i = 0; i < num_threads_; ++i) {
256 pthread_join(thread[i], NULL);
259 gettimeofday(&tv2, NULL);
260 int ms = (int)(1000000*(tv2.tv_sec - tv1.tv_sec) + tv2.tv_usec - tv1.tv_usec) / 1000;
261 printf("Th:%ld Time:%dms\n", num_threads_, ms);