Skip to content
This repository was archived by the owner on Aug 19, 2021. It is now read-only.

Commit 7937a76

Browse files
committed
Moved to constant-time event cancellation
The previous cancel relied on a naive linear search to find events to cancel. This provided a simple and robust solution that avoided issues with in-flight events. Achieving constant-time cancellation required two modifications: - Use doubly-linked list for queue - Encode event addresses in the event id Encoding the event addresses turned out to be a bit tricky. The equeue library supports cancelling events after they have been dispatched. This used an incrementing counter to obtain pseudo- unique ids. To fit everything in a single int, the event address is stored as an offset from the equeue's memory region. A seperate counter is used for each allocated chunk, taking advantage of the non-coalescing nature of the underlying allocator. Together these are ored to obtain a unique id that can be decoded to obtain the underlying event. Notable performance impact (make prof): equeue_cancel_many_prof: 132 cycles (+75%) equeue_alloc_many_size_prof: 56000 bytes (-12%) equeue_alloc_fragmented_size_prof: 56000 bytes (-12%)
1 parent f602cb5 commit 7937a76

File tree

3 files changed

+71
-58
lines changed

3 files changed

+71
-58
lines changed

equeue.c

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,24 @@ int equeue_create(equeue_t *q, size_t size) {
1717
}
1818

1919
int err = equeue_create_inplace(q, size, buffer);
20-
q->buffer = buffer;
20+
q->allocated = buffer;
2121
return err;
2222
}
2323

2424
int equeue_create_inplace(equeue_t *q, size_t size, void *buffer) {
25+
q->buffer = buffer;
26+
q->allocated = 0;
27+
28+
q->npw2 = 0;
29+
for (unsigned s = size; s; s >>= 1) {
30+
q->npw2++;
31+
}
32+
33+
q->chunks = 0;
2534
q->slab.size = size;
2635
q->slab.data = buffer;
27-
q->chunks = 0;
28-
q->buffer = 0;
2936

3037
q->queue = 0;
31-
q->next_id = 42;
3238
q->break_ = (struct equeue_event){
3339
.id = 0,
3440
.period = -1,
@@ -45,7 +51,7 @@ int equeue_create_inplace(equeue_t *q, size_t size, void *buffer) {
4551
return err;
4652
}
4753

48-
err = equeue_mutex_create(&q->freelock);
54+
err = equeue_mutex_create(&q->memlock);
4955
if (err < 0) {
5056
return err;
5157
}
@@ -60,21 +66,18 @@ void equeue_destroy(equeue_t *q) {
6066
equeue_dealloc(q, e+1);
6167
}
6268

63-
equeue_mutex_destroy(&q->freelock);
69+
equeue_mutex_destroy(&q->memlock);
6470
equeue_mutex_destroy(&q->queuelock);
6571
equeue_sema_destroy(&q->eventsema);
66-
free(q->buffer);
72+
free(q->allocated);
6773
}
6874

69-
// equeue allocation functions
70-
static void *equeue_mem_alloc(equeue_t *q, size_t size) {
71-
size = size + sizeof(unsigned);
72-
size = (size + sizeof(unsigned)-1) & ~(sizeof(unsigned)-1);
73-
if (size < sizeof(struct equeue_chunk)) {
74-
size = sizeof(struct equeue_chunk);
75-
}
75+
// equeue chunk allocation functions
76+
static struct equeue_event *equeue_mem_alloc(equeue_t *q, size_t size) {
77+
size += sizeof(struct equeue_event);
78+
size = (size + sizeof(void*)-1) & ~(sizeof(void*)-1);
7679

77-
equeue_mutex_lock(&q->freelock);
80+
equeue_mutex_lock(&q->memlock);
7881

7982
for (struct equeue_chunk **p = &q->chunks; *p; p = &(*p)->nchunk) {
8083
if ((*p)->size >= size) {
@@ -85,8 +88,14 @@ static void *equeue_mem_alloc(equeue_t *q, size_t size) {
8588
} else {
8689
*p = c->nchunk;
8790
}
88-
equeue_mutex_unlock(&q->freelock);
89-
return (unsigned *)c + 1;
91+
92+
c->id += 1;
93+
if (c->id >> (8*sizeof(int)-1 - q->npw2)) {
94+
c->id = 1;
95+
}
96+
97+
equeue_mutex_unlock(&q->memlock);
98+
return (struct equeue_event *)c;
9099
}
91100
}
92101

@@ -95,18 +104,20 @@ static void *equeue_mem_alloc(equeue_t *q, size_t size) {
95104
q->slab.data += size;
96105
q->slab.size -= size;
97106
c->size = size;
98-
equeue_mutex_unlock(&q->freelock);
99-
return (unsigned *)c + 1;
107+
c->id = 1;
108+
109+
equeue_mutex_unlock(&q->memlock);
110+
return (struct equeue_event *)c;
100111
}
101112

102-
equeue_mutex_unlock(&q->freelock);
113+
equeue_mutex_unlock(&q->memlock);
103114
return 0;
104115
}
105116

106-
static void equeue_mem_dealloc(equeue_t *q, void *e) {
107-
struct equeue_chunk *c = (struct equeue_chunk *)((unsigned *)e - 1);
117+
static void equeue_mem_dealloc(equeue_t *q, struct equeue_event *e) {
118+
struct equeue_chunk *c = (struct equeue_chunk *)e;
108119

109-
equeue_mutex_lock(&q->freelock);
120+
equeue_mutex_lock(&q->memlock);
110121

111122
struct equeue_chunk **p = &q->chunks;
112123
while (*p && (*p)->size < c->size) {
@@ -121,27 +132,17 @@ static void equeue_mem_dealloc(equeue_t *q, void *e) {
121132
c->nchunk = *p;
122133
}
123134
*p = c;
124-
125-
equeue_mutex_unlock(&q->freelock);
126-
}
127135

128-
// event allocation functions
129-
static inline int equeue_next_id(equeue_t *q) {
130-
int id = q->next_id++;
131-
if (q->next_id < 0) {
132-
q->next_id = 42;
133-
}
134-
return id;
136+
equeue_mutex_unlock(&q->memlock);
135137
}
136138

139+
// equeue allocation functions
137140
void *equeue_alloc(equeue_t *q, size_t size) {
138-
struct equeue_event *e = equeue_mem_alloc(q,
139-
sizeof(struct equeue_event) + size);
141+
struct equeue_event *e = equeue_mem_alloc(q, size);
140142
if (!e) {
141143
return 0;
142144
}
143145

144-
e->id = equeue_next_id(q);
145146
e->target = 0;
146147
e->period = -1;
147148
e->dtor = 0;
@@ -172,24 +173,23 @@ static void equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned ms) {
172173
p = &(*p)->next;
173174
}
174175

176+
e->ref = p;
175177
e->next = *p;
178+
if (*p) {
179+
(*p)->ref = &e->next;
180+
}
176181
*p = e;
177182
}
178183

179-
static struct equeue_event *equeue_dequeue(equeue_t *q, int id) {
180-
for (struct equeue_event **p = &q->queue; *p; p = &(*p)->next) {
181-
if ((*p)->id == id) {
182-
struct equeue_event *e = *p;
183-
*p = (*p)->next;
184-
return e;
185-
}
184+
static void equeue_dequeue(equeue_t *q, struct equeue_event *e) {
185+
if (e->next) {
186+
e->next->ref = e->ref;
186187
}
187-
188-
return 0;
188+
*e->ref = e->next;
189189
}
190190

191191
static int equeue_post_in(equeue_t *q, struct equeue_event *e, int ms) {
192-
int id = e->id;
192+
int id = (e->id << q->npw2) | ((unsigned char *)e - q->buffer);
193193
if (ms < 0) {
194194
equeue_dealloc(q, e+1);
195195
return id;
@@ -211,13 +211,19 @@ int equeue_post(equeue_t *q, void (*cb)(void*), void *p) {
211211
}
212212

213213
void equeue_cancel(equeue_t *q, int id) {
214+
struct equeue_event *e = (struct equeue_event *)
215+
&q->buffer[id & ((1 << q->npw2)-1)];
216+
214217
equeue_mutex_lock(&q->queuelock);
215-
struct equeue_event *e = equeue_dequeue(q, id);
218+
if (e->id != id >> q->npw2) {
219+
equeue_mutex_unlock(&q->queuelock);
220+
return;
221+
}
222+
223+
equeue_dequeue(q, e);
216224
equeue_mutex_unlock(&q->queuelock);
217225

218-
if (e) {
219-
equeue_dealloc(q, e+1);
220-
}
226+
equeue_dealloc(q, e+1);
221227
}
222228

223229
void equeue_break(equeue_t *q) {
@@ -248,6 +254,7 @@ void equeue_dispatch(equeue_t *q, int ms) {
248254
}
249255

250256
struct equeue_event *e = q->queue;
257+
e->id += 1;
251258
q->queue = e->next;
252259

253260
if (e->period >= 0) {

equeue.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ extern "C" {
2121

2222
// Definition of the minimum size of an event
2323
// This size fits the events created in the event_call set of functions.
24-
#define EQUEUE_EVENT_SIZE (sizeof(struct equeue_event) + 3*sizeof(void*))
24+
#define EQUEUE_EVENT_SIZE (sizeof(struct equeue_event) + 2*sizeof(void*))
2525

2626
// Event/queue structures
2727
struct equeue_event {
28-
struct equeue_event *next;
28+
unsigned size;
2929
int id;
30+
struct equeue_event *next;
31+
struct equeue_event **ref;
32+
3033
unsigned target;
3134
int period;
3235
void (*dtor)(void *);
@@ -37,11 +40,14 @@ struct equeue_event {
3740

3841
typedef struct equeue {
3942
struct equeue_event *queue;
40-
int next_id;
4143

42-
void *buffer;
44+
unsigned char *buffer;
45+
unsigned npw2;
46+
void *allocated;
47+
4348
struct equeue_chunk {
4449
unsigned size;
50+
int id;
4551
struct equeue_chunk *next;
4652
struct equeue_chunk *nchunk;
4753
} *chunks;
@@ -54,7 +60,7 @@ typedef struct equeue {
5460

5561
equeue_sema_t eventsema;
5662
equeue_mutex_t queuelock;
57-
equeue_mutex_t freelock;
63+
equeue_mutex_t memlock;
5864
} equeue_t;
5965

6066

tests/prof.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ void equeue_post_many_prof(int count) {
185185
struct equeue q;
186186
equeue_create(&q, count*EQUEUE_EVENT_SIZE);
187187

188-
for (int i = 0; i < count; i++) {
188+
for (int i = 0; i < count-1; i++) {
189189
equeue_call(&q, no_func, 0);
190190
}
191191

@@ -224,7 +224,7 @@ void equeue_post_future_many_prof(int count) {
224224
struct equeue q;
225225
equeue_create(&q, count*EQUEUE_EVENT_SIZE);
226226

227-
for (int i = 0; i < count; i++) {
227+
for (int i = 0; i < count-1; i++) {
228228
equeue_call(&q, no_func, 0);
229229
}
230230

@@ -293,7 +293,7 @@ void equeue_cancel_many_prof(int count) {
293293
struct equeue q;
294294
equeue_create(&q, count*EQUEUE_EVENT_SIZE);
295295

296-
for (int i = 0; i < count; i++) {
296+
for (int i = 0; i < count-1; i++) {
297297
equeue_call(&q, no_func, 0);
298298
}
299299

0 commit comments

Comments
 (0)