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

Commit c4d4839

Browse files
committed
Moved to segmented dispatch
Before, the dispatch function iterated over the events, calling the callbacks as it removed the events from the queue. Now the dispatch function dequeues all pending events before dispatching callbacks. This removes the need for a break event, allows better cache coherency, and results in a better organization of the dispatch function. The only downsides are a longer startup in dispatch and increased jitter, although the latter could be fixed by adding fabricated unlock points during the linked-list traversal. Notable performance impact (make prof): equeue_dispatch_prof: 466 cycles (+25%) equeue_alloc_many_prof: 79 cycles (-12%) equeue_post_many_prof: 7681 cycles (+33%) equeue_post_future_many_prof: 7612 cycles (+35%) equeue_dispatch_many_prof: 8353 cycles (+65%) It's interesting to note there is a decrease in performance for the alloc_many test, this may just be because moving the equeue members around caused the slab info to cross a cache boundary. The alloc_many tests is already very fast (~3* the time for mutex lock).
1 parent 7937a76 commit c4d4839

File tree

2 files changed

+92
-47
lines changed

2 files changed

+92
-47
lines changed

equeue.c

Lines changed: 91 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,7 @@ int equeue_create_inplace(equeue_t *q, size_t size, void *buffer) {
3535
q->slab.data = buffer;
3636

3737
q->queue = 0;
38-
q->break_ = (struct equeue_event){
39-
.id = 0,
40-
.period = -1,
41-
};
38+
q->breaks = 0;
4239

4340
int err;
4441
err = equeue_sema_create(&q->eventsema);
@@ -172,7 +169,7 @@ static void equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned ms) {
172169
while (*p && equeue_tickdiff((*p)->target, e->target) <= 0) {
173170
p = &(*p)->next;
174171
}
175-
172+
176173
e->ref = p;
177174
e->next = *p;
178175
if (*p) {
@@ -181,102 +178,151 @@ static void equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned ms) {
181178
*p = e;
182179
}
183180

184-
static void equeue_dequeue(equeue_t *q, struct equeue_event *e) {
181+
static void equeue_unqueue(equeue_t *q, struct equeue_event *e) {
185182
if (e->next) {
186183
e->next->ref = e->ref;
187184
}
188185
*e->ref = e->next;
189186
}
190187

191-
static int equeue_post_in(equeue_t *q, struct equeue_event *e, int ms) {
188+
static struct equeue_event *equeue_dequeue(
189+
equeue_t *q, unsigned target, int *deadline) {
190+
struct equeue_event *head = q->queue;
191+
if (!head || equeue_tickdiff(head->target, target) > 0) {
192+
return 0;
193+
}
194+
195+
struct equeue_event **p = &q->queue;
196+
while (*p) {
197+
int diff = equeue_tickdiff((*p)->target, target);
198+
if (diff > 0) {
199+
*deadline = diff;
200+
break;
201+
}
202+
203+
p = &(*p)->next;
204+
}
205+
206+
if (*p) {
207+
(*p)->ref = &q->queue;
208+
}
209+
q->queue = *p;
210+
*p = 0;
211+
212+
return head;
213+
}
214+
215+
int equeue_post(equeue_t *q, void (*cb)(void*), void *p) {
216+
struct equeue_event *e = (struct equeue_event*)p - 1;
192217
int id = (e->id << q->npw2) | ((unsigned char *)e - q->buffer);
193-
if (ms < 0) {
218+
e->cb = cb;
219+
220+
if (e->target < 0) {
194221
equeue_dealloc(q, e+1);
195222
return id;
196223
}
197224

198225
equeue_mutex_lock(&q->queuelock);
199-
equeue_enqueue(q, e, ms);
226+
equeue_enqueue(q, e, e->target);
200227
equeue_mutex_unlock(&q->queuelock);
201228

202229
equeue_sema_release(&q->eventsema);
203230
return id;
204231
}
205232

206-
int equeue_post(equeue_t *q, void (*cb)(void*), void *p) {
207-
struct equeue_event *e = (struct equeue_event*)p - 1;
208-
e->cb = cb;
209-
int id = equeue_post_in(q, e, e->target);
210-
return id;
211-
}
212-
213233
void equeue_cancel(equeue_t *q, int id) {
214234
struct equeue_event *e = (struct equeue_event *)
215235
&q->buffer[id & ((1 << q->npw2)-1)];
216236

217237
equeue_mutex_lock(&q->queuelock);
238+
if (e->id == -id >> q->npw2) {
239+
e->cb = 0;
240+
e->period = -1;
241+
}
242+
218243
if (e->id != id >> q->npw2) {
219244
equeue_mutex_unlock(&q->queuelock);
220245
return;
221246
}
222247

223-
equeue_dequeue(q, e);
248+
equeue_unqueue(q, e);
224249
equeue_mutex_unlock(&q->queuelock);
225250

226251
equeue_dealloc(q, e+1);
227252
}
228253

229254
void equeue_break(equeue_t *q) {
230-
equeue_post_in(q, &q->break_, 0);
255+
equeue_mutex_lock(&q->queuelock);
256+
q->breaks++;
257+
equeue_mutex_unlock(&q->queuelock);
258+
equeue_sema_release(&q->eventsema);
231259
}
232260

233261
void equeue_dispatch(equeue_t *q, int ms) {
234-
if (ms >= 0) {
235-
equeue_post_in(q, &q->break_, ms);
236-
}
262+
unsigned timeout = equeue_tick() + ms;
237263

238264
while (1) {
265+
// collect all the available events and next deadline
266+
struct equeue_event *es = 0;
239267
int deadline = -1;
240-
241-
while (q->queue) {
242-
deadline = -1;
243-
268+
if (q->queue) {
244269
equeue_mutex_lock(&q->queuelock);
245-
if (!q->queue) {
246-
equeue_mutex_unlock(&q->queuelock);
247-
break;
248-
}
270+
es = equeue_dequeue(q, equeue_tick(), &deadline);
249271

250-
deadline = equeue_tickdiff(q->queue->target, equeue_tick());
251-
if (deadline > 0) {
252-
equeue_mutex_unlock(&q->queuelock);
253-
break;
272+
// mark events as in-flight
273+
for (struct equeue_event *e = es; e; e = e->next) {
274+
e->id = -e->id;
254275
}
276+
equeue_mutex_unlock(&q->queuelock);
277+
}
255278

256-
struct equeue_event *e = q->queue;
257-
e->id += 1;
258-
q->queue = e->next;
279+
// dispatch events
280+
while (es) {
281+
struct equeue_event *e = es;
282+
es = e->next;
259283

284+
// actually dispatch the callbacks
285+
void (*cb)(void *) = e->cb;
286+
if (cb) {
287+
cb(e + 1);
288+
}
289+
290+
// undirty the id and either dealloc or reenqueue periodic events
291+
e->id = -e->id;
260292
if (e->period >= 0) {
261-
// requeue periodic tasks to avoid race conditions
262-
// in equeue_cancel
293+
equeue_mutex_lock(&q->queuelock);
263294
equeue_enqueue(q, e, e->period);
295+
equeue_mutex_unlock(&q->queuelock);
296+
} else {
297+
equeue_dealloc(q, e+1);
264298
}
265-
equeue_mutex_unlock(&q->queuelock);
299+
}
266300

267-
if (e == &q->break_) {
301+
// check if we should stop dispatching soon
302+
if (ms >= 0) {
303+
int diff = equeue_tickdiff(timeout, equeue_tick());
304+
if (diff <= 0) {
268305
return;
269306
}
270307

271-
// actually dispatch the callback
272-
e->cb(e + 1);
273-
274-
if (e->period < 0) {
275-
equeue_dealloc(q, e+1);
308+
if (deadline < 0 || diff < deadline) {
309+
deadline = diff;
276310
}
277311
}
278312

313+
// wait for events
279314
equeue_sema_wait(&q->eventsema, deadline);
315+
316+
// check if we were notified to break out of dispatch
317+
if (q->breaks) {
318+
equeue_mutex_lock(&q->queuelock);
319+
if (q->breaks > 0) {
320+
q->breaks--;
321+
equeue_mutex_unlock(&q->queuelock);
322+
return;
323+
}
324+
equeue_mutex_unlock(&q->queuelock);
325+
}
280326
}
281327
}
282328

equeue.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct equeue_event {
4040

4141
typedef struct equeue {
4242
struct equeue_event *queue;
43+
int breaks;
4344

4445
unsigned char *buffer;
4546
unsigned npw2;
@@ -56,8 +57,6 @@ typedef struct equeue {
5657
unsigned char *data;
5758
} slab;
5859

59-
struct equeue_event break_;
60-
6160
equeue_sema_t eventsema;
6261
equeue_mutex_t queuelock;
6362
equeue_mutex_t memlock;

0 commit comments

Comments
 (0)