├── CMakeLists.txt ├── README.md ├── generate_plots.R ├── linkedlist.c ├── linkedlist.h ├── linkedlistseq.c ├── listbench.c ├── run_benchmarks.sh └── run_steady_benchmark.sh /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(linkedlist) 2 | cmake_minimum_required(VERSION 3.7) 3 | 4 | set(SHARED_SOURCE_FILES 5 | linkedlist.h 6 | listbench.c 7 | ) 8 | 9 | set(SOURCE_FILES 10 | linkedlist.c 11 | ${SHARED_SOURCE_FILES} 12 | ) 13 | 14 | set(CMAKE_C_FLAGS "-fopenmp") 15 | 16 | link_libraries(atomic) 17 | 18 | add_executable(ldraconic ${SOURCE_FILES}) 19 | target_compile_definitions(ldraconic PUBLIC TEXTBOOK) 20 | 21 | add_executable(lsingly ${SOURCE_FILES}) 22 | target_compile_definitions(lsingly PUBLIC "") 23 | 24 | add_executable(ldoubly ${SOURCE_FILES}) 25 | target_compile_definitions(ldoubly PUBLIC DOUBLY) 26 | 27 | add_executable(ldoubly_cursor ${SOURCE_FILES}) 28 | target_compile_definitions(ldoubly_cursor PUBLIC DOUBLY CURSOR) 29 | 30 | add_executable(lsingly_cursor ${SOURCE_FILES}) 31 | target_compile_definitions(lsingly_cursor PUBLIC CURSOR) 32 | 33 | add_executable(lsingly_cursor_fetch ${SOURCE_FILES}) 34 | target_compile_definitions(lsingly_cursor_fetch PUBLIC CURSOR FETCH) 35 | 36 | #add_executable(lprivate ${SOURCE_FILES}) 37 | #target_compile_definitions(lprivate PUBLIC PRIVATE) 38 | 39 | #add_executable(lsequential linkedlistseq.c ${SHARED_SOURCE_FILES}) 40 | #target_compile_definitions(lsequential PUBLIC PRIVATE) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A more Pragmatic Implementation of the Lock-free, Ordered, Linked List 2 | ==== 3 | 4 | The lock-free ordered, linked list is an important, standard example 5 | of a concurrent data structure. An obvious, practical drawback of 6 | textbook implementations is that failed compare-and-swap (`CAS`) 7 | operations lead to retraversal of the entire list (retries), which 8 | is particularly harmful for a linear-time data structure. We 9 | alleviate this drawback by first observing that failed `CAS` 10 | operations under some conditions do not require a full retry, and 11 | second by maintaining approximate backwards pointers that are used 12 | to find a closer starting position in the list for operation 13 | retry. Experiments with both a worst-case deterministic benchmark, 14 | and a standard, randomized, mixed-operation throughput benchmark on 15 | three shared-memory systems (Intel Xeon, AMD EPYC, SPARC-T5) show 16 | practical improvements ranging from significant to dramatic several 17 | orders of magnitude. 18 | 19 | Publications 20 | ---- 21 | * [A more Pragmatic Implementation of the Lock-free, Ordered, Linked List](https://arxiv.org/abs/2010.15755) 22 | J. Träff, M. Pöter / Report for CoRR - Computing Research Repository; Report No. arXiv:2010.15755, 2020 23 | 24 | Building and running 25 | ---- 26 | ``` 27 | mkdir build && cd build 28 | cmake -DCMAKE_BUILD_TYPE=Release .. 29 | make -j 4 30 | ``` 31 | 32 | The build process generates six different executables: 33 | * `ldraconic` - this implements the list as proposed by Harris (also referred to as "textbook implementation" in the paper). 34 | * `ldoubly` - this implements the list with approximate backward pointers and retry from head of list. 35 | * `ldoubly_cursor` - as `ldoubly` with per thread retry from the last recorded position (cursor) in the list. 36 | * `lsingly` - this implements the list with the mild improvements described in the paper. 37 | * `lsingly_cursor` - as `lsingly` with per thread retry from the last recorded position (cursor) in the list. 38 | * `lsingly_cursor_fetch` - as `lsingly_cursor` but uses `fetch_or` to set the delete mark on the next pointer. 39 | 40 | Each of the executables takes a number of arguments: 41 | * `-p ` - the number of threads; optional, defaults to `omp_get_max_threads()` 42 | * `-B [D|S]` - the benchmark to run - D = deterministic; S = steady (randomized). If omitted, both are run, starting with deterministic. 43 | * `-L` - output is formatted as a LaTeX table 44 | 45 | Additional arguments for deterministic benchmark: 46 | * `-n ` - the number of elements; optional, defaults to 10000 47 | * `-r ` - factor to calculate effective key in insert operations; optional, defaults to number of threads 48 | * `-o ` - offset to calculate effective key in insert operations; optional, defaults to 0 49 | * `-R ` - factor to calculate effective key in remove operations; optional, defaults to number of threads 50 | * `-O ` - offset to calculate effective key in remove operations; optional, defaults to 0 51 | 52 | Additional arguments for randomized (steady) benchmark: 53 | * `-S ` - randomization seed 54 | * `-f ` - the number of items for prefill; optional, defaults to 10000 55 | * `-U ` - the key range; optional, defaults to 10*prefill 56 | * `-A ` - probability of insert operation in percent (0-100); optional, defaults to 10 57 | * `-R ` - probability of remove operation in percent (0-100); optional, defaults to 10 58 | * `-c ` - number of operations; optional, defaults to 10000 59 | * `-C` - output is formatted as CSV (only applies if LaTeX output (`-L`) is not set) 60 | 61 | The `run_benchmark.sh` script runs each executable in three different configurations: 62 | 1. Deterministic benchmark with `k(i)=i`, p=[threads], n=100000 63 | 2. Deterministic benchmark with `k(i)=t+ip`, p=[threads], n=10000 64 | 3. Random operation mix benchmark, p=[threads], c=1000000, f=1000, U=10000$; operation Mix 10% add, 10% remove, 80% lookup. 65 | 66 | The number of threads can be configured by setting the `THREAD` environment variable, e.g., `THREADS=16 run_benchmark.sh`. 67 | 68 | These settings correspond to the ones used to produce tables 1-9 in the paper. By setting the environment variable 69 | `OUTPUT_FORMAT` to `-L` the output is formatted as a LaTeX table. However, the generated output only contains the table 70 | lines and has to be put into a tabular evironment: 71 | ``` 72 | \begin{tabular}{rrrrrrrrr} 73 | % Paste in results 74 | \end{tabular} 75 | ``` 76 | 77 | The `run_steady_benchmark.sh` scripts executes the randomized (steady) benchmark for each executable with varying numbers 78 | of threads and the following parameters: c=50000, f=16384, U=32768; operation mix 25% add, 25% remove, 50% lookup. 79 | Each configuration is run five times, and all the results are collected in the result file `steady_results.csv`. 80 | These settings correspond to the ones used to produce the scalability charts in the paper. Please note, that you have 81 | to edit the shell script directly in order to change the varying thread numbers based on your system. 82 | 83 | The resulting csv has to be massaged a bit in order to be passed to `generate_plots.R`: 84 | ``` 85 | awk '(/;/ && !/Time/) || NR==2' results.csv 86 | Rscript generate_plots.R 87 | ``` 88 | Note: The following R packages are required in order to run the script: `ggplot2`, `plyr`, `purrr` -------------------------------------------------------------------------------- /generate_plots.R: -------------------------------------------------------------------------------- 1 | library(ggplot2) 2 | library(plyr) 3 | library(purrr) 4 | 5 | calc_data <- function(data, variables, col = "unit") { 6 | cdata <- ddply(data, .variables = variables, 7 | .fun = function(d) { 8 | N = length (d[[col]]) 9 | sd = sd(d[[col]]) 10 | c(N, 11 | mean = mean(d[[col]]), 12 | sd, 13 | se = sd / sqrt(N) 14 | ) 15 | } 16 | ) 17 | cdata 18 | } 19 | 20 | color_palette <- function() 21 | { 22 | c("singly" = "#D55E00", 23 | "doubly" = "#009E43", 24 | "singly_cursor" = "#0072B2", 25 | "doubly_cursor" = "#E69F00", 26 | "draconic" = "#56B4E9") 27 | } 28 | 29 | bar_plot <- function(plot, title, x, y, palette = color_palette(), text_size=10) 30 | { 31 | plot + 32 | geom_bar(position = position_dodge(width=0.9), width=0.8, stat="identity") + 33 | geom_errorbar(aes(ymin=mean-se, ymax=mean+se), position=position_dodge(width=0.9), width=0.8) + 34 | scale_fill_manual(values = palette) + 35 | labs(x=x, y=y) + 36 | theme(legend.position = "bottom", 37 | legend.title = element_blank(), 38 | text = element_text(size=text_size), 39 | legend.text = element_text(size=text_size), 40 | plot.title = element_text(size=text_size + 0.5), 41 | axis.text.x = element_text(size=text_size), 42 | axis.title.x = element_text(size=text_size), 43 | axis.text.y = element_text(size=text_size), 44 | axis.title.y = element_text(size=text_size)) + 45 | guides(fill=guide_legend(nrow=1, byrow=TRUE)) 46 | } 47 | 48 | read_file <- function(file) 49 | { 50 | read.csv(file=file, head=TRUE, sep=";") 51 | } 52 | 53 | benchmarks <- function() { c("draconic", "singly", "doubly", "singly_cursor", "doubly_cursor") } 54 | 55 | plot_threads <- function(file, title) 56 | { 57 | data <- read_file(file) 58 | data <- within(data, benchmark <- factor(benchmark, 59 | levels=benchmarks())) 60 | data$unit <- data[["Throughput..Kops.s."]] 61 | 62 | cdata <- calc_data(data, c("threads", "benchmark")) 63 | cdata$threads <- as.ordered(cdata$threads) 64 | plot <- ggplot(data=cdata, aes(threads, mean, fill=benchmark)) 65 | bar_plot(plot, title=title, x="threads", y="Kops/sec") 66 | } 67 | 68 | calc_speedup <- function(file, base = "draconic") { 69 | data <- read_file(file) 70 | data$unit <- data[["Throughput..Kops.s."]] 71 | cdata <- calc_data(data, c("threads", "benchmark")) 72 | base <- cdata[cdata$benchmark == base,] 73 | map(benchmarks(), .f = function(b) { 74 | c(b, mean(cdata[cdata$benchmark == b,]$mean / base$mean)) 75 | }) 76 | } 77 | 78 | plot <- plot_threads("results.csv", "Steady") 79 | ggsave("threads.pdf", plot, width=300, height=95, units="mm", device=cairo_pdf) 80 | -------------------------------------------------------------------------------- /linkedlist.c: -------------------------------------------------------------------------------- 1 | /* (C) Jesper Larsson Traff, May 2020, October 2020 */ 2 | /* Improved lock-free linked list implementations */ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include // gcc -latomic 9 | 10 | #include 11 | 12 | #include "linkedlist.h" 13 | 14 | //#define DOUBLY 15 | //#define CURSOR / only with DOUBLY 16 | //#define TEXTBOOK 17 | //#define FETCH 18 | 19 | // Memory model 20 | //#define SC 21 | 22 | #define UNMARK_MASK ~1 23 | #define MARK_BIT 0x0000000000001 24 | 25 | #define getpointer(_markedpointer) ((node_t*)(((long)_markedpointer) & UNMARK_MASK)) 26 | #define ismarked(_markedpointer) ((((long)_markedpointer) & MARK_BIT) != 0x0) 27 | #define setmark(_markedpointer) ((node_t*)(((long)_markedpointer) | MARK_BIT)) 28 | 29 | #ifdef SC 30 | #define CAS(_a,_e,_d) atomic_compare_exchange_weak(_a,_e,_d) 31 | #define LOAD(_a) atomic_load(_a) 32 | #define STORE(_a,_e) atomic_store(_a,_e) 33 | #define FAO(_a,_e) atomic_fetch_or(_a,_e) 34 | #else 35 | #define CAS(_a,_e,_d) atomic_compare_exchange_weak_explicit(_a,_e,_d,memory_order_acq_rel,memory_order_acquire) 36 | #define LOAD(_a) atomic_load_explicit(_a,memory_order_acquire) 37 | #define STORE(_a,_e) atomic_store_explicit(_a,_e,memory_order_release) 38 | #define FAO(_a,_e) atomic_fetch_or_explicit(_a,_e,memory_order_acq_rel) 39 | #endif 40 | 41 | void init(node_t *head, node_t *tail, list_t *list) 42 | { 43 | list->head = head; 44 | list->tail = tail; 45 | 46 | // the sentinels 47 | list->head->key = LONG_MIN; 48 | list->head->next = tail; 49 | list->head->prev = NULL; 50 | 51 | list->tail->key = LONG_MAX; 52 | list->tail->next = NULL; 53 | list->tail->prev = head; 54 | 55 | list->pred = head; 56 | list->curr = NULL; 57 | 58 | list->free = NULL; 59 | 60 | #ifdef COUNTERS 61 | list->adds = 0; 62 | list->rems = 0; 63 | list->cons = 0; 64 | list->trav = 0; 65 | list->fail = 0; 66 | list->rtry = 0; 67 | #endif 68 | } 69 | 70 | void clean(list_t *list) 71 | { 72 | node_t *next, *node; 73 | 74 | next = list->free; 75 | while (next != NULL) { 76 | node = next; 77 | next = next->free; 78 | free(node); 79 | } 80 | list->free = NULL; 81 | } 82 | 83 | void pos(long key, list_t *list) 84 | { 85 | node_t *pred, *succ, *curr, *next; 86 | 87 | #ifdef DOUBLY 88 | #ifdef CURSOR 89 | pred = list->pred; 90 | #else 91 | pred = list->pred; 92 | if (key <= pred->key) 93 | pred = list->head; 94 | #endif 95 | 96 | retry: 97 | while (ismarked(LOAD(&pred->next)) || key <= pred->key) { 98 | INC(list->trav); 99 | pred = LOAD(&pred->prev); 100 | } 101 | curr = getpointer(LOAD(&pred->next)); 102 | INC(list->trav); 103 | #else // DOUBLY 104 | retry: 105 | #ifdef TEXTBOOK 106 | pred = list->head; 107 | #else 108 | pred = list->pred; 109 | if (ismarked(LOAD(&pred->next)) || key <= pred->key) 110 | pred = list->head; 111 | #endif 112 | 113 | curr = getpointer(LOAD(&pred->next)); 114 | INC(list->trav); 115 | #endif // DOUBLY 116 | assert(pred->key < key); 117 | 118 | do { 119 | succ = LOAD(&curr->next); 120 | while (ismarked(succ)) { 121 | succ = getpointer(succ); 122 | if (!CAS(&pred->next, &curr, succ)) { 123 | INC(list->fail); 124 | #ifdef TEXTBOOK 125 | INC(list->rtry); 126 | goto retry; 127 | #else 128 | next = LOAD(&pred->next); 129 | if (ismarked(next)) { 130 | INC(list->rtry); 131 | goto retry; 132 | } 133 | succ = next; 134 | #endif 135 | } 136 | #ifdef DOUBLY 137 | else 138 | STORE(&succ->prev, pred); 139 | #endif 140 | 141 | curr = getpointer(succ); 142 | succ = LOAD(&succ->next); 143 | INC(list->trav); 144 | } 145 | #ifdef DOUBLY 146 | if (LOAD(&curr->prev) != pred) 147 | STORE(&curr->prev, pred); 148 | #endif 149 | 150 | if (key <= curr->key) { 151 | assert(pred->key < curr->key); 152 | list->pred = pred; 153 | list->curr = curr; 154 | return; 155 | } 156 | pred = curr; 157 | curr = getpointer(LOAD(&curr->next)); 158 | INC(list->trav); 159 | } while (1); 160 | } 161 | 162 | int add(long key, list_t *list) 163 | { 164 | node_t *pred, *curr, *node; 165 | 166 | node = (node_t*)malloc(sizeof(node_t)); 167 | assert(node != NULL); 168 | node->key = key; 169 | 170 | #ifndef CURSOR 171 | list->pred = list->head; 172 | #endif 173 | do { 174 | pos(key, list); 175 | pred = list->pred; 176 | curr = list->curr; 177 | if (curr->key == key) { 178 | free(node); 179 | return 0; // already there 180 | } 181 | 182 | node->next = curr; 183 | #ifdef DOUBLY 184 | node->prev = pred; 185 | #endif 186 | 187 | if (CAS(&pred->next, &curr, node)) { 188 | INC(list->adds); 189 | #ifdef DOUBLY 190 | STORE(&curr->prev, node); 191 | #endif 192 | 193 | return 1; 194 | } 195 | INC(list->fail); 196 | } while (1); 197 | } 198 | 199 | int rem(long key, list_t *list) 200 | { 201 | node_t *pred, *succ, *node; 202 | node_t *markedsucc; 203 | 204 | do { 205 | pos(key, list); 206 | pred = list->pred; 207 | node = list->curr; 208 | if (node->key != key) 209 | return 0; // not there 210 | 211 | #ifdef TEXTBOOK 212 | succ = getpointer(LOAD(&node->next)); // unmarked 213 | markedsucc = setmark(succ); 214 | 215 | if (!CAS(&node->next, &succ, markedsucc)) { 216 | INC(list->fail); 217 | continue; 218 | } 219 | #else 220 | #ifdef FETCH 221 | // x86 supports atomic-or, but not atomic-fetch-or, i.e., we cannot get the old value. 222 | // If we do not use the return value of the fetch-or, the compiler is smart enough to 223 | // use an atomic-or. If we use the return value, the atomic-fetch-or is emulated via 224 | // a CAS loop. 225 | // In this case it would be sufficient to use a pure atomic-or, although it has the 226 | // drawback that we cannot tell WHO has set the bit, so we would also have to adapt 227 | // the management of the free-list. 228 | 229 | succ = (node_t*)FAO((_Atomic uint64_t*)&node->next, MARK_BIT); 230 | if (ismarked(succ)) return 0; 231 | #else 232 | succ = LOAD(&node->next); 233 | do { 234 | if (ismarked(succ)) 235 | return 0; 236 | markedsucc = setmark(succ); 237 | if (CAS(&node->next, &succ, markedsucc)) 238 | break; 239 | INC(list->fail); 240 | } while (1); 241 | #endif 242 | #endif 243 | 244 | if (!CAS(&pred->next, &node, succ)) 245 | node = list->curr; // beware! 246 | #ifdef DOUBLY 247 | STORE(&succ->prev, pred); 248 | #endif 249 | 250 | node->free = list->free; 251 | list->free = node; 252 | INC(list->rems); 253 | 254 | return 1; 255 | } while (1); 256 | } 257 | 258 | int con(long key, list_t *list) 259 | { 260 | node_t *curr; 261 | 262 | #ifdef DOUBLY 263 | #ifdef CURSOR 264 | curr = list->pred; 265 | #else 266 | curr = list->head; 267 | #endif 268 | INC(list->cons); 269 | while (key < curr->key) { 270 | curr = LOAD(&curr->prev); 271 | INC(list->cons); 272 | } 273 | #else // DOUBLY 274 | #ifdef CURSOR 275 | curr = list->pred; 276 | if (key < curr->key) 277 | curr = list->head; 278 | #else 279 | curr = list->head; 280 | #endif 281 | #endif // DOUBLY 282 | assert(curr->key <= key); 283 | 284 | while (key > curr->key) { 285 | curr = getpointer(LOAD(&curr->next)); 286 | INC(list->cons); 287 | } 288 | 289 | #ifdef CURSOR 290 | list->pred = curr; 291 | #endif 292 | 293 | return (curr->key == key && !ismarked(LOAD(&curr->next))); 294 | } 295 | -------------------------------------------------------------------------------- /linkedlist.h: -------------------------------------------------------------------------------- 1 | /* (C) Jesper Larsson Traff, May 2020 */ 2 | /* Improved lock-free linked list implementations */ 3 | 4 | #define COUNTERS 5 | 6 | #ifdef COUNTERS 7 | #define INC(_c) ((_c)++) 8 | #else 9 | #define INC(_c) 10 | #endif 11 | 12 | typedef struct _node { 13 | _Atomic(struct _node *) next; 14 | _Atomic(struct _node *) prev; 15 | struct _node *free; // for freelist 16 | char padding[40]; // fill the cacheline 17 | long key; 18 | } node_t; 19 | 20 | typedef struct _list { 21 | node_t *head, *tail; // sentinels, possibly shared 22 | 23 | node_t *curr; // private cursor (last operation) 24 | node_t *pred; // predecessor of cursor 25 | 26 | node_t *free; // private free list 27 | 28 | #ifdef COUNTERS 29 | unsigned long long adds, rems, cons, trav, fail, rtry; 30 | #endif 31 | } list_t; 32 | 33 | void init(node_t *head, node_t *tail, list_t* list); 34 | void clean(list_t *list); 35 | 36 | int add(long key, list_t *list); 37 | int rem(long key, list_t *list); 38 | int con(long key, list_t *list); 39 | -------------------------------------------------------------------------------- /linkedlistseq.c: -------------------------------------------------------------------------------- 1 | /* (C) Jesper Larsson Traff, May 2020 */ 2 | /* Improved lock-free linked list implementations */ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "linkedlist.h" 11 | 12 | //#define DOUBLY 13 | //#define CURSOR / only with DOUBLY 14 | 15 | #ifdef COUNTERS 16 | #define INC(_c) ((_c)++) 17 | #else 18 | #define INC(_c) 19 | #endif 20 | 21 | void init(node_t *head, node_t *tail, list_t *list) 22 | { 23 | list->head = head; 24 | list->tail = tail; 25 | 26 | // the sentinels 27 | list->head->key = LONG_MIN; 28 | list->head->next = tail; 29 | list->head->prev = NULL; 30 | 31 | list->tail->key = LONG_MAX; 32 | list->tail->next = NULL; 33 | list->tail->prev = head; 34 | 35 | list->pred = head; 36 | list->curr = NULL; 37 | 38 | list->free = NULL; 39 | 40 | #ifdef COUNTERS 41 | list->adds = 0; 42 | list->rems = 0; 43 | list->cons = 0; 44 | list->trav = 0; 45 | list->fail = 0; 46 | list->rtry = 0; 47 | #endif 48 | } 49 | 50 | void clean(list_t *list) 51 | { 52 | node_t *next, *node; 53 | 54 | next = list->free; 55 | while (next!=NULL) { 56 | node = next; next = next->free; 57 | free(node); 58 | } 59 | list->free = NULL; 60 | } 61 | 62 | void pos(long key, list_t *list) 63 | { 64 | node_t *pred, *curr; 65 | 66 | #ifdef DOUBLY 67 | #ifdef CURSOR 68 | curr = list->pred; 69 | #else 70 | curr = list->head; 71 | #endif 72 | if (curr->key>=key) { 73 | pred = curr->prev; 74 | while (keykey) { 75 | curr = pred; pred = pred->prev; 76 | INC(list->trav); 77 | } 78 | } else 79 | #else 80 | curr = list->head; 81 | #endif 82 | { 83 | do { 84 | pred = curr; curr = curr->next; 85 | INC(list->trav); 86 | } while (key>curr->key); 87 | } 88 | 89 | list->pred = pred; 90 | list->curr = curr; 91 | assert(pred->keykey); 92 | } 93 | 94 | int add(long key, list_t *list) 95 | { 96 | node_t *pred, *curr, *node; 97 | 98 | pos(key,list); 99 | pred = list->pred; curr = list->curr; 100 | if (curr->key==key) return 0; // already there 101 | 102 | INC(list->adds); 103 | 104 | node = (node_t*)malloc(sizeof(node_t)); 105 | assert(node!=NULL); 106 | 107 | node->key = key; 108 | node->next = curr; 109 | pred->next = node; 110 | 111 | #ifdef DOUBLY 112 | node->prev = pred; 113 | curr->prev = node; 114 | #endif 115 | 116 | return 1; 117 | } 118 | 119 | int rem(long key, list_t *list) 120 | { 121 | node_t *pred, *node; 122 | 123 | pos(key,list); 124 | pred = list->pred; 125 | node = list->curr; 126 | if (node->key!=key) return 0; // not there 127 | 128 | INC(list->rems); 129 | 130 | pred->next = node->next; 131 | #ifdef DOUBLY 132 | node->next->prev = pred; 133 | #endif 134 | 135 | node->free = list->free; 136 | list->free = node; 137 | 138 | return 1; 139 | } 140 | 141 | int con(long key, list_t *list) 142 | { 143 | node_t *curr; 144 | 145 | #ifdef DOUBLY 146 | #ifdef CURSOR 147 | curr = list->pred; 148 | #else 149 | curr = list->head; 150 | #endif 151 | INC(list->cons); 152 | while (keykey) { 153 | curr = curr->prev; 154 | INC(list->cons); 155 | } 156 | #else 157 | curr = list->head; 158 | #endif 159 | while (key>curr->key) { 160 | curr = curr->next; 161 | INC(list->cons); 162 | } 163 | 164 | #ifdef DOUBLY 165 | #ifdef CURSOR 166 | list->pred = curr; 167 | #endif 168 | #endif 169 | 170 | return (curr->key==key); 171 | } 172 | -------------------------------------------------------------------------------- /listbench.c: -------------------------------------------------------------------------------- 1 | /* (C) Jesper Larsson Traff, May 2020 */ 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "linkedlist.h" 11 | 12 | #define N 10000 13 | 14 | //#define PRIVATE 15 | 16 | #define MICRO 1000000.0 17 | #define MILLI 1000.0 18 | #define KOPS 1000 19 | 20 | //#define TEST(_A) assert(_A) // just _A when shared with overlap 21 | #define TEST(_A) if (!(_A)) printf("Line %d: t %d key %ld\n",__LINE__,t,key) 22 | //#define TEST(_A) assert( _A) 23 | 24 | // stress linearity benchmark 25 | void benchmark1(int n, int p, int ar, int ao, int rr, int ro, int verbose, 26 | int latex) 27 | { 28 | double time; 29 | int disjoint; 30 | 31 | disjoint = (ar==rr&&ao==ro)&&((ar==p)||(ar==1&&ao==n)); 32 | 33 | time = 0.0; 34 | 35 | // performance counters 36 | #ifdef COUNTERS 37 | unsigned long long tops, adds, rems, cons, trav, fail, rtry; 38 | 39 | tops = 0; 40 | 41 | adds = 0; 42 | rems = 0; 43 | cons = 0; 44 | trav = 0; 45 | fail = 0; 46 | rtry = 0; 47 | #endif 48 | 49 | #ifndef PRIVATE 50 | node_t head, tail; // shared list 51 | #endif 52 | 53 | #ifndef PRIVATE 54 | #pragma omp parallel shared(head) shared(tail) reduction(max:time) reduction(+:tops,adds,rems,cons,trav,fail) 55 | #else 56 | #pragma omp parallel reduction(max:time) reduction(+:tops,adds,rems,cons,trav,fail) 57 | #endif 58 | { 59 | double start, stop; 60 | #ifdef PRIVATE 61 | node_t head, tail; 62 | #endif 63 | list_t list; 64 | 65 | #ifdef COUNTERS 66 | int ops = 0; 67 | #endif 68 | int t = omp_get_thread_num(); 69 | long key; 70 | 71 | init(&head,&tail,&list); 72 | 73 | #pragma omp barrier 74 | start = omp_get_wtime(); 75 | 76 | int i; 77 | int ok; 78 | for (i=0; i=0; i--) { 92 | key = i; 93 | key = key*ar+t*ao+t%ar; 94 | ok = con(key,&list); INC(ops); 95 | TEST(!disjoint||ok); 96 | ok = rem(key,&list); INC(ops); 97 | TEST(!disjoint||ok); 98 | ok = !con(key,&list); INC(ops); 99 | TEST(!disjoint||ok); 100 | ok = !rem(key,&list); INC(ops); 101 | TEST(!disjoint||ok); 102 | } 103 | 104 | for (i=0; inext; 279 | while (node!=&tail) { 280 | node_t *curr = node; 281 | node = node->next; 282 | free(curr); 283 | } 284 | } 285 | 286 | clean(&list); 287 | } 288 | 289 | #if defined(TEXTBOOK) 290 | char* benchmark = "draconic"; 291 | #elif defined(CURSOR) 292 | #if defined(DOUBLY) 293 | char* benchmark = "doubly_cursor"; 294 | #else 295 | char* benchmark = "singly_cursor"; 296 | #endif 297 | #elif defined(DOUBLY) 298 | char* benchmark = "doubly"; 299 | #else 300 | char* benchmark = "singly"; 301 | #endif 302 | 303 | printf("STEADY Threads: %d\n",p); 304 | if (latex) { 305 | printf("Time (ms) & Total ops & Throughput (Kops/s) & adds & rems & cons& trav & fail & rtry \\\\\n"); 306 | printf("%.2f & %llu & %.2f & %llu & %llu & %llu & %llu & %llu & %llu \\\\\n", 307 | time*MILLI,tops,((double)tops/time)/KOPS, 308 | adds,rems,cons,trav,fail,rtry); 309 | } else if (csv) { 310 | printf("Time (ms);Total ops;Throughput (Kops/s);adds;rems;cons;trav;fail;rtry;threads;benchmark\n"); 311 | printf("%.2f;%llu;%.2f;%llu;%llu;%llu;%llu;%llu;%llu;%d;%s\n", 312 | time*MILLI, tops, ((double)tops/time)/KOPS, adds, rems, cons, trav, fail, rtry, p, benchmark); 313 | } else { 314 | printf("Time (ms) %.2f Total ops %llu Throughput (Kops/s) %.2f\n", 315 | time*MILLI,tops,((double)tops/time)/KOPS); 316 | printf("adds %llu rems %llu cons %llu trav %llu fail %llu rtry %llu\n", 317 | adds,rems,cons,trav,fail,rtry); 318 | } 319 | } 320 | 321 | int main(int argc, char *argv[]) 322 | { 323 | int i; 324 | 325 | int p; // number of threads 326 | 327 | int n, f, c; 328 | int ar, ao, rr, ro; // add and remove factors and offsets 329 | int pa, pr; // percentage (integer) of adds and removes 330 | int verbose, latex, csv; 331 | 332 | int U; 333 | unsigned seed; 334 | char benchmark = '_'; // _ = both; D = deterministic; S = steady 335 | 336 | n = N; 337 | f = N; 338 | c = N; 339 | 340 | p = -1; 341 | 342 | ar = -1; 343 | ao = -1; 344 | rr = -1; 345 | ro = -1; 346 | 347 | U = -1; 348 | pa = 10; pr = 10; // 10% add, 10% rem 349 | 350 | verbose = 0; 351 | latex = 0; 352 | csv=0; 353 | 354 | for (i=1; i> steady_results.csv 7 | done 8 | done 9 | done 10 | --------------------------------------------------------------------------------