Skip to content

Commit b69d4f8

Browse files
committed
Evaluation of two concurrent linked lists: QSBR and lock-based
1 parent cedbbf4 commit b69d4f8

File tree

7 files changed

+831
-0
lines changed

7 files changed

+831
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ rcu_queue/rcu_queue
1818
redirect/redirect
1919
tinync/tinync
2020
tpool/tpool
21+
list-move/bench-lock
22+
list-move/bench-lockfree
2123

2224
# external source files
2325
preempt_sched/list.h

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ purpose of these programs is to be illustrative and educational.
2323
* [Synchronization](https://en.wikipedia.org/wiki/Synchronization_(computer_science))
2424
- [qsbr](qsbr/): An implementation of Quiescent state based reclamation (QSBR).
2525
- [rcu\_list](rcu_list/): A concurrent linked list utilizing the simplified RCU algorithm.
26+
- [list-move](list-move/): Evaluation of two concurrent linked lists: QSBR and lock-based.
2627
- [rcu\_queue](rcu_queue/): An efficient concurrent queue based on QSBR.
2728
* Applications
2829
- [httpd](httpd/): A multi-threaded web server.

list-move/Makefile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
CFLAGS = -Wall -Wextra
2+
LDFLAGS = -lpthread
3+
4+
BINS = bench-lock bench-lockfree
5+
6+
all: $(BINS)
7+
8+
bench-lock: bench.c lock.c
9+
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
10+
11+
bench-lockfree: bench.c lockfree.c
12+
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
13+
14+
clean:
15+
rm -f $(BINS)
16+
17+
check: $(BINS)
18+
./bench-lock
19+
@echo
20+
./bench-lockfree
21+
22+
indent:
23+
clang-format -i *.[ch]

list-move/bench.c

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#include <stdbool.h>
2+
#include <stdint.h>
3+
#include <sys/time.h>
4+
#include <unistd.h>
5+
6+
#include "bench.h"
7+
8+
static void barrier_init(barrier_t *b, int n)
9+
{
10+
pthread_cond_init(&b->complete, NULL);
11+
pthread_mutex_init(&b->mutex, NULL);
12+
b->count = n;
13+
b->crossing = 0;
14+
}
15+
16+
static void barrier_cross(barrier_t *b)
17+
{
18+
pthread_mutex_lock(&b->mutex);
19+
b->crossing++;
20+
if (b->crossing < b->count)
21+
pthread_cond_wait(&b->complete, &b->mutex);
22+
else {
23+
pthread_cond_broadcast(&b->complete);
24+
b->crossing = 0;
25+
}
26+
pthread_mutex_unlock(&b->mutex);
27+
}
28+
29+
#define DEFAULT_DURATION 1000
30+
#define DEFAULT_NTHREADS 64
31+
#define DEFAULT_ISIZE 256
32+
#define DEFAULT_VRANGE 512
33+
34+
static volatile bool should_stop = false;
35+
36+
static void *bench_thread(void *data)
37+
{
38+
pthread_data_t *d = (pthread_data_t *) data;
39+
40+
barrier_cross(d->barrier);
41+
while (!should_stop) {
42+
int from = rand_r(&d->seed) & 0x1;
43+
int key = rand_r(&d->seed) % d->range;
44+
list_move(key, d, from);
45+
d->n_move++;
46+
}
47+
48+
return NULL;
49+
}
50+
51+
int main(void)
52+
{
53+
int duration = DEFAULT_DURATION;
54+
int n_threads = DEFAULT_NTHREADS;
55+
int init_size = DEFAULT_ISIZE;
56+
int value_range = DEFAULT_VRANGE;
57+
58+
printf("List move benchmark\n");
59+
printf("Test time: %d\n", duration);
60+
printf("Thread number: %d\n", n_threads);
61+
printf("Initial size: %d\n", init_size);
62+
printf("Value range: %d\n", value_range);
63+
64+
struct timespec timeout = {.tv_sec = duration / 1000,
65+
.tv_nsec = (duration % 1000) * 1000000};
66+
67+
pthread_t *threads;
68+
if (!(threads = malloc(n_threads * sizeof(pthread_t)))) {
69+
printf("failed to malloc pthread_t\n");
70+
goto out;
71+
}
72+
73+
pthread_data_t **data;
74+
if (!(data = malloc(n_threads * sizeof(pthread_data_t *)))) {
75+
printf("failed to malloc pthread_data_t\n");
76+
goto out;
77+
}
78+
for (int i = 0; i < n_threads; i++) {
79+
if ((data[i] = alloc_pthread_data()) == NULL) {
80+
printf("failed to malloc pthread_data_t %d\n", i);
81+
goto out;
82+
}
83+
}
84+
85+
srand(getpid() ^ (uintptr_t) main);
86+
87+
void *list;
88+
if (!(list = list_global_init(init_size, value_range))) {
89+
printf("failed to do list_global_init\n");
90+
goto out;
91+
}
92+
93+
barrier_t barrier;
94+
pthread_attr_t attr;
95+
barrier_init(&barrier, n_threads + 1);
96+
pthread_attr_init(&attr);
97+
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
98+
for (int i = 0; i < n_threads; i++) {
99+
data[i]->id = i;
100+
data[i]->n_move = 0;
101+
data[i]->range = value_range;
102+
data[i]->seed = rand();
103+
data[i]->barrier = &barrier;
104+
data[i]->list = list;
105+
if (list_thread_init(data[i])) {
106+
printf("Failed to do list_thread_init\n");
107+
goto out;
108+
}
109+
if (pthread_create(&threads[i], &attr, bench_thread,
110+
(void *) (data[i])) != 0) {
111+
printf("Failed to create thread %d\n", i);
112+
goto out;
113+
}
114+
}
115+
pthread_attr_destroy(&attr);
116+
117+
barrier_cross(&barrier);
118+
119+
struct timeval start, end;
120+
gettimeofday(&start, NULL);
121+
nanosleep(&timeout, NULL);
122+
should_stop = true;
123+
gettimeofday(&end, NULL);
124+
125+
for (int i = 0; i < n_threads; i++) {
126+
if (pthread_join(threads[i], NULL) != 0) {
127+
printf("Failed to join child thread %d\n", i);
128+
goto out;
129+
}
130+
}
131+
132+
duration = (end.tv_sec * 1000 + end.tv_usec / 1000) -
133+
(start.tv_sec * 1000 + start.tv_usec / 1000);
134+
unsigned long n_move = 0;
135+
for (int i = 0; i < n_threads; i++)
136+
n_move += (data[i]->n_move);
137+
138+
printf("\tduration: %d ms\n", duration);
139+
printf("\tops/second %lu (%f/s)\n", n_move,
140+
n_move * (1000.0) / duration);
141+
142+
for (int i = 0; i < n_threads; i++)
143+
free_pthread_data(data[i]);
144+
list_global_exit(list);
145+
free(data);
146+
free(threads);
147+
148+
out:
149+
return 0;
150+
}

list-move/bench.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#ifndef BENCH_MOVE_LIST_H
2+
#define BENCH_MOVE_LIST_H
3+
4+
#include <limits.h>
5+
#include <pthread.h>
6+
#include <stdio.h>
7+
#include <stdlib.h>
8+
9+
typedef struct {
10+
pthread_cond_t complete;
11+
pthread_mutex_t mutex;
12+
int count, crossing;
13+
} barrier_t;
14+
15+
#define CACHE_LINE (64)
16+
#define CACHE_ALIGN(size) ((((size - 1) / CACHE_LINE) + 1) * CACHE_LINE)
17+
18+
#define PTHREAD_PADDING (16)
19+
20+
typedef struct {
21+
long pthread_padding[PTHREAD_PADDING];
22+
long id;
23+
unsigned long n_move;
24+
int range;
25+
unsigned int seed;
26+
barrier_t *barrier;
27+
void *list;
28+
void *ds_data; /* data structure specific data */
29+
} pthread_data_t;
30+
31+
pthread_data_t *alloc_pthread_data(void);
32+
33+
void free_pthread_data(pthread_data_t *d);
34+
void *list_global_init(int init_size, int value_range);
35+
int list_thread_init(pthread_data_t *data);
36+
void list_global_exit(void *list);
37+
int list_move(int key, pthread_data_t *data, int from);
38+
39+
#endif

list-move/lock.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#include <assert.h>
2+
3+
#include "bench.h"
4+
5+
typedef struct node {
6+
int val;
7+
struct node *next;
8+
} node_t;
9+
10+
typedef struct spinlock_list {
11+
pthread_spinlock_t spinlock;
12+
node_t *head[2];
13+
} spinlock_list_t;
14+
15+
pthread_data_t *alloc_pthread_data(void)
16+
{
17+
size_t size = sizeof(pthread_data_t);
18+
size = CACHE_ALIGN(size);
19+
20+
pthread_data_t *d = malloc(size);
21+
if (d)
22+
d->ds_data = NULL;
23+
24+
return d;
25+
}
26+
27+
void free_pthread_data(pthread_data_t *d)
28+
{
29+
free(d);
30+
}
31+
32+
void *list_global_init(int size, int value_range)
33+
{
34+
spinlock_list_t *list = malloc(sizeof(spinlock_list_t));
35+
if (!list)
36+
return NULL;
37+
38+
pthread_spin_init(&list->spinlock, PTHREAD_PROCESS_PRIVATE);
39+
list->head[0] = malloc(sizeof(node_t));
40+
list->head[1] = malloc(sizeof(node_t));
41+
if (!list->head[0] || !list->head[1])
42+
return NULL;
43+
node_t *node[2] = {list->head[0], list->head[1]};
44+
node[0]->val = INT_MIN;
45+
node[1]->val = INT_MIN;
46+
47+
for (int i = 0; i < value_range; i += value_range / size) {
48+
node[0]->next = malloc(sizeof(node_t));
49+
node[1]->next = malloc(sizeof(node_t));
50+
if (!node[0]->next || !node[1]->next)
51+
return NULL;
52+
node[0] = node[0]->next;
53+
node[1] = node[1]->next;
54+
node[0]->val = i;
55+
node[1]->val = i + 1;
56+
}
57+
node[0]->next = malloc(sizeof(node_t));
58+
node[1]->next = malloc(sizeof(node_t));
59+
if (!node[0]->next || !node[1]->next)
60+
return NULL;
61+
node[0]->val = node[1]->val = INT_MAX;
62+
63+
return list;
64+
}
65+
66+
int list_thread_init(pthread_data_t *data)
67+
{
68+
return 0;
69+
}
70+
71+
void list_global_exit(void *list)
72+
{
73+
spinlock_list_t *l = (spinlock_list_t *) list;
74+
pthread_spin_destroy(&l->spinlock);
75+
}
76+
77+
int list_move(int key, pthread_data_t *data, int from)
78+
{
79+
spinlock_list_t *list = (spinlock_list_t *) data->list;
80+
node_t *prev_src, *cur, *prev_dst, *next_dst;
81+
int val;
82+
83+
pthread_spin_lock(&list->spinlock);
84+
for (prev_src = list->head[from], cur = prev_src->next; cur;
85+
prev_src = cur, cur = cur->next)
86+
if ((val = cur->val) >= key)
87+
break;
88+
89+
int ret = (val == key);
90+
if (!ret)
91+
goto out;
92+
for (prev_dst = list->head[1 - from], next_dst = prev_dst->next; next_dst;
93+
prev_dst = next_dst, next_dst = next_dst->next)
94+
if ((val = next_dst->val) >= key)
95+
break;
96+
ret = (val != key);
97+
if (!ret)
98+
goto out;
99+
100+
prev_src->next = cur->next;
101+
prev_dst->next = cur;
102+
cur->next = next_dst;
103+
out:
104+
pthread_spin_unlock(&list->spinlock);
105+
106+
return ret;
107+
}

0 commit comments

Comments
 (0)