diff options
| author | eunhhu <[email protected]> | 2026-09-28 20:10:49 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-28 20:10:49 +0100 |
| commit | ee7f97001b0e193a766a0f889ae16f9d5af40fea (patch) | |
| tree | 1e92063bcdeca959605cc34a436764e7a5b07d12 /serve | |
| parent | 91754a57ad7b7a92f9935509367590db951f903b (diff) | |
| download | pure-ee7f97001b0e193a766a0f889ae16f9d5af40fea.tar.gz pure-ee7f97001b0e193a766a0f889ae16f9d5af40fea.zip | |
Wake a link's thread only when it sleeps, once
Every queued record wrote the link's eventfd, a system call each, and woke
the thread to send a packet of a few records. A record queued now wakes
the thread only when it sleeps and nobody woke it yet; one queued while it
sends marks the link, and the thread looks again before it sleeps. With
records queued one at a time as fast as a thread can, 200,000 small ones
went at 400k a second, and now at 730k; queuing one costs 0.43 us, not
1.0.
research/experiments/019-idle-links/transport.c measures the transport
alone: a link and a listener in one process, no app, no store. A backlog
of 200,000 records of 61 bytes drains at 3 to 4 million a second, 0.24 us
a record on each side.
Diffstat (limited to 'serve')
| -rw-r--r-- | serve/link.c | 16 |
1 files changed, 13 insertions, 3 deletions
diff --git a/serve/link.c b/serve/link.c index 016f306..1dde29d 100644 --- a/serve/link.c +++ b/serve/link.c @@ -195,6 +195,9 @@ struct PureLink { size_t head, count, capacity; uint64_t queued, stored, told; /* the last queued; saved by the receiver; told the owner */ bool stopping, started; + /* The thread sleeps (in poll), and has been woken since; records were + * queued since it last sent. A queue wakes it only when it sleeps, once. */ + bool sleeping, signaled, queued_new; int fd, wake; pthread_t thread; void (*notify)(void *); @@ -316,9 +319,13 @@ bool pure_link_queue(PureLink *l, uint64_t seq, PureSpan payload) { } else ok = false; } + bool signal = false; if (ok) { *record_at(l, l->count++) = r; l->queued = seq; + l->queued_new = true; + signal = l->sleeping && !l->signaled; + l->signaled = l->signaled || signal; } pthread_mutex_unlock(&l->lock); if (!ok) { @@ -326,7 +333,7 @@ bool pure_link_queue(PureLink *l, uint64_t seq, PureSpan payload) { free(r.piece); return false; } - wake(l->wake); + if (signal) wake(l->wake); return true; } @@ -648,6 +655,7 @@ static bool transmit(PureLink *l, Flight *f, const unsigned char *body, size_t s static void send_packets(PureLink *l, uint64_t now) { unsigned char body[BODY]; + l->queued_new = false; /* all queued is looked at now */ for (unsigned n = 0; n < 128; ++n) { bool probe = l->probes > 0; if (!probe && l->in_flight + DATAGRAM > l->window) break; @@ -721,10 +729,11 @@ static void *run_link(void *context) { for (;;) { pthread_mutex_lock(&l->lock); uint64_t now = now_ns(), due = next_timer(l); - bool stopping = l->stopping; + bool stopping = l->stopping, more = l->queued_new; /* queued while it sent: no sleep */ + l->sleeping = !more; pthread_mutex_unlock(&l->lock); if (stopping) break; - int timeout = due ? (int)((due > now ? due - now : 0) / MS) + 1 : -1; + int timeout = more ? 0 : due ? (int)((due > now ? due - now : 0) / MS) + 1 : -1; struct pollfd waits[2] = {{l->fd, POLLIN, 0}, {l->wake, POLLIN, 0}}; if (poll(waits, 2, timeout) < 0 && errno != EINTR) break; if (waits[1].revents & POLLIN) { @@ -732,6 +741,7 @@ static void *run_link(void *context) { if (read(l->wake, &signals, sizeof(signals)) < 0) { /* cleared already */ } } pthread_mutex_lock(&l->lock); + l->sleeping = l->signaled = false; now = now_ns(); if (waits[0].revents) take_answers(l, now); run_timers(l, now); |
