From 01278b28318a984e76a3a805ff7d639d736f0f65 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 27 Nov 2014 01:11:56 +0100 Subject: MAJOR: session: only wake up as many sessions as available buffers permit We've already experimented with two wake up algorithms when releasing buffers : the first naive one used to wake up far too many sessions, causing many of them not to get any buffer. The second approach which was still in use prior to this patch consisted in waking up either 1 or 2 sessions depending on the number of FDs we had released. And this was still inaccurate because we didn't take into account the number of FDs the sessions would be willing to use, thus most of the time we ended up waking up too many of them for nothing. The new approach relies on the following basis : - process_session() needs 2 buffers, eventhough it may allocate 0, 1, or 2. - I/O callbacks need 1 buffer but will often have 2 (one in use, one allocated on the fly for for recv). - many of the buffers that are still available are likely to be used either for woken up sessions or for pending I/Os Thus, we take the number of available buffers, we subtract twice the number of FDs in the cache, then we compare that to the double of the run queue. Indeed, the run queue roughly equals the number of already woken up sessions, which includes the number of sessions which were previously offered buffers but which didn't have the opportunity to use them yet. This mechanism is quite efficient, we don't perform too many wake up calls. For 1 million sessions elapsed during massive memory contention, we observe 4.5M calls to process_session() compared to 4.0M without memory constraints. Previously we used to observe up to 16M calls, which rougly means 12M failures. During a test run under high memory constraints (limit enforced to 27 MB instead of the 58 MB normally needed), performance used to drop by 53% prior to this patch. Now with this patch instead it *increases* by about 1.5%. The best effect of this change is that by limiting the memory usage to about 2/3 to 3/4 of what is needed by default, it's possible to increase performance by up to about 10% mainly due to the fact that pools are reused more often and remain hot in the CPU cache. --- include/proto/session.h | 2 +- src/session.c | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/include/proto/session.h b/include/proto/session.h index 6cec6de..13544c1 100644 --- a/include/proto/session.h +++ b/include/proto/session.h @@ -54,7 +54,7 @@ int parse_track_counters(char **args, int *arg, /* Update the session's backend and server time stats */ void session_update_time_stats(struct session *s); -void session_offer_buffers(int count); +void session_offer_buffers(); int session_alloc_buffers(struct session *s); void session_release_buffers(struct session *s); int session_alloc_one_buffer(struct session *s, struct buffer **buf); diff --git a/src/session.c b/src/session.c index 6c5ce99..adc2cbe 100644 --- a/src/session.c +++ b/src/session.c @@ -621,7 +621,7 @@ static void session_free(struct session *s) b_drop(&s->req->buf); b_drop(&s->rep->buf); if (!LIST_ISEMPTY(&buffer_wq)) - session_offer_buffers(1); + session_offer_buffers(); pool_free2(pool2_channel, s->req); pool_free2(pool2_channel, s->rep); @@ -726,10 +726,6 @@ int session_alloc_buffers(struct session *s) */ void session_release_buffers(struct session *s) { - int release_count = 0; - - release_count = !!s->req->buf->size + !!s->rep->buf->size; - if (s->req->buf->size && buffer_empty(s->req->buf)) b_free(&s->req->buf); @@ -739,27 +735,40 @@ void session_release_buffers(struct session *s) /* if we're certain to have at least 1 buffer available, and there is * someone waiting, we can wake up a waiter and offer them. */ - if (release_count >= 1 && !LIST_ISEMPTY(&buffer_wq)) - session_offer_buffers(release_count); + if (!LIST_ISEMPTY(&buffer_wq)) + session_offer_buffers(); } /* run across the list of pending sessions waiting for a buffer and wake * one up if buffers are available. */ -void session_offer_buffers(int count) +void session_offer_buffers() { struct session *sess, *bak; + int avail; + + /* all sessions will need 1 or 2 buffers, so we can stop + * waking up sessions once we have enough of them to eat + * all the buffers. Note that we don't really know if they + * are sessions or just other tasks, but that's a rough + * estimate. Similarly, for each cached event we'll need + * 1 or 2 buffers, so we set this limit as well. + */ + + avail = pool2_buffer->allocated - pool2_buffer->used; + avail /= 2; + avail -= fd_cache_num; list_for_each_entry_safe(sess, bak, &buffer_wq, buffer_wait) { + if (avail <= (int)(run_queue)) + break; + if (sess->task->state & TASK_RUNNING) continue; LIST_DEL(&sess->buffer_wait); LIST_INIT(&sess->buffer_wait); - task_wakeup(sess->task, TASK_WOKEN_RES); - if (--count <= 0) - break; } } -- 1.7.12.1