diff options
author | Alexander Motin <mav@FreeBSD.org> | 2019-11-01 22:49:44 +0000 |
---|---|---|
committer | Alexander Motin <mav@FreeBSD.org> | 2019-11-01 22:49:44 +0000 |
commit | 3db35ffa2a1822ca92a7e7ca7a1cc2094a398cef (patch) | |
tree | fd900e52e222b097766a8dbc5ae1becd8a0d43db /sys/kern/subr_gtaskqueue.c | |
parent | 08b86a1a8a107274f97ace292e91fc82228dde7e (diff) |
Some more taskqueue optimizations.
- Optimize enqueue for two task priority values by adding new tq_hint
field, pointing to the last task inserted into the middle of the list.
In case of more then two priority values it should halve average search.
- Move tq_active insert/remove out of the taskqueue_run_locked loop.
Instead of dirtying few shared cache lines per task introduce different
mechanism to drain active tasks, based on task sequence number counter,
that uses only cache lines already present in cache. Since the new
mechanism does not need ordering, switch tq_active from TAILQ to LIST.
- Move static and dynamic struct taskqueue fields into different cache
lines. Move lock into its own cache line, so that heavy lock spinning
by multiple waiting threads would not affect the running thread.
- While there, correct some TQ_SLEEP() wait messages.
This change fixes certain ZFS write workloads, causing huge congestion
on taskqueue lock. Those workloads combine some large block writes to
saturate the pool and trigger allocation throttling, which uses higher
priority tasks to requeue the delayed I/Os, with many small blocks to
generate deep queue of small tasks for taskqueue to sort.
MFC after: 1 week
Sponsored by: iXsystems, Inc.
Notes
Notes:
svn path=/head/; revision=354241
Diffstat (limited to 'sys/kern/subr_gtaskqueue.c')
-rw-r--r-- | sys/kern/subr_gtaskqueue.c | 89 |
1 files changed, 34 insertions, 55 deletions
diff --git a/sys/kern/subr_gtaskqueue.c b/sys/kern/subr_gtaskqueue.c index 8be973cddb26..f2227150d3a9 100644 --- a/sys/kern/subr_gtaskqueue.c +++ b/sys/kern/subr_gtaskqueue.c @@ -57,26 +57,26 @@ TASKQGROUP_DEFINE(softirq, mp_ncpus, 1); TASKQGROUP_DEFINE(config, 1, 1); struct gtaskqueue_busy { - struct gtask *tb_running; - TAILQ_ENTRY(gtaskqueue_busy) tb_link; + struct gtask *tb_running; + u_int tb_seq; + LIST_ENTRY(gtaskqueue_busy) tb_link; }; -static struct gtask * const TB_DRAIN_WAITER = (struct gtask *)0x1; - typedef void (*gtaskqueue_enqueue_fn)(void *context); struct gtaskqueue { STAILQ_HEAD(, gtask) tq_queue; + LIST_HEAD(, gtaskqueue_busy) tq_active; + u_int tq_seq; + int tq_callouts; + struct mtx_padalign tq_mutex; gtaskqueue_enqueue_fn tq_enqueue; void *tq_context; char *tq_name; - TAILQ_HEAD(, gtaskqueue_busy) tq_active; - struct mtx tq_mutex; struct thread **tq_threads; int tq_tcount; int tq_spin; int tq_flags; - int tq_callouts; taskqueue_callback_fn tq_callbacks[TASKQUEUE_NUM_CALLBACKS]; void *tq_cb_contexts[TASKQUEUE_NUM_CALLBACKS]; }; @@ -115,12 +115,11 @@ gtask_dump(struct gtask *gtask) #endif static __inline int -TQ_SLEEP(struct gtaskqueue *tq, void *p, struct mtx *m, int pri, const char *wm, - int t) +TQ_SLEEP(struct gtaskqueue *tq, void *p, const char *wm) { if (tq->tq_spin) - return (msleep_spin(p, m, wm, t)); - return (msleep(p, m, pri, wm, t)); + return (msleep_spin(p, (struct mtx *)&tq->tq_mutex, wm, 0)); + return (msleep(p, &tq->tq_mutex, 0, wm, 0)); } static struct gtaskqueue * @@ -144,7 +143,7 @@ _gtaskqueue_create(const char *name, int mflags, } STAILQ_INIT(&queue->tq_queue); - TAILQ_INIT(&queue->tq_active); + LIST_INIT(&queue->tq_active); queue->tq_enqueue = enqueue; queue->tq_context = context; queue->tq_name = tq_name; @@ -167,7 +166,7 @@ gtaskqueue_terminate(struct thread **pp, struct gtaskqueue *tq) while (tq->tq_tcount > 0 || tq->tq_callouts > 0) { wakeup(tq); - TQ_SLEEP(tq, pp, &tq->tq_mutex, PWAIT, "taskqueue_destroy", 0); + TQ_SLEEP(tq, pp, "gtq_destroy"); } } @@ -178,7 +177,7 @@ gtaskqueue_free(struct gtaskqueue *queue) TQ_LOCK(queue); queue->tq_flags &= ~TQ_FLAGS_ACTIVE; gtaskqueue_terminate(queue->tq_threads, queue); - KASSERT(TAILQ_EMPTY(&queue->tq_active), ("Tasks still running?")); + KASSERT(LIST_EMPTY(&queue->tq_active), ("Tasks still running?")); KASSERT(queue->tq_callouts == 0, ("Armed timeout tasks")); mtx_destroy(&queue->tq_mutex); free(queue->tq_threads, M_GTASKQUEUE); @@ -285,7 +284,7 @@ gtaskqueue_drain_tq_queue(struct gtaskqueue *queue) * have completed or are currently executing. */ while (t_barrier.ta_flags & TASK_ENQUEUED) - TQ_SLEEP(queue, &t_barrier, &queue->tq_mutex, PWAIT, "-", 0); + TQ_SLEEP(queue, &t_barrier, "gtq_qdrain"); } /* @@ -296,31 +295,24 @@ gtaskqueue_drain_tq_queue(struct gtaskqueue *queue) static void gtaskqueue_drain_tq_active(struct gtaskqueue *queue) { - struct gtaskqueue_busy tb_marker, *tb_first; + struct gtaskqueue_busy *tb; + u_int seq; - if (TAILQ_EMPTY(&queue->tq_active)) + if (LIST_EMPTY(&queue->tq_active)) return; /* Block taskq_terminate().*/ queue->tq_callouts++; - /* - * Wait for all currently executing taskqueue threads - * to go idle. - */ - tb_marker.tb_running = TB_DRAIN_WAITER; - TAILQ_INSERT_TAIL(&queue->tq_active, &tb_marker, tb_link); - while (TAILQ_FIRST(&queue->tq_active) != &tb_marker) - TQ_SLEEP(queue, &tb_marker, &queue->tq_mutex, PWAIT, "-", 0); - TAILQ_REMOVE(&queue->tq_active, &tb_marker, tb_link); - - /* - * Wakeup any other drain waiter that happened to queue up - * without any intervening active thread. - */ - tb_first = TAILQ_FIRST(&queue->tq_active); - if (tb_first != NULL && tb_first->tb_running == TB_DRAIN_WAITER) - wakeup(tb_first); + /* Wait for any active task with sequence from the past. */ + seq = queue->tq_seq; +restart: + LIST_FOREACH(tb, &queue->tq_active, tb_link) { + if ((int)(tb->tb_seq - seq) <= 0) { + TQ_SLEEP(queue, tb->tb_running, "gtq_adrain"); + goto restart; + } + } /* Release taskqueue_terminate(). */ queue->tq_callouts--; @@ -352,40 +344,27 @@ static void gtaskqueue_run_locked(struct gtaskqueue *queue) { struct gtaskqueue_busy tb; - struct gtaskqueue_busy *tb_first; struct gtask *gtask; KASSERT(queue != NULL, ("tq is NULL")); TQ_ASSERT_LOCKED(queue); tb.tb_running = NULL; + LIST_INSERT_HEAD(&queue->tq_active, &tb, tb_link); - while (STAILQ_FIRST(&queue->tq_queue)) { - TAILQ_INSERT_TAIL(&queue->tq_active, &tb, tb_link); - - /* - * Carefully remove the first task from the queue and - * clear its TASK_ENQUEUED flag - */ - gtask = STAILQ_FIRST(&queue->tq_queue); - KASSERT(gtask != NULL, ("task is NULL")); + while ((gtask = STAILQ_FIRST(&queue->tq_queue)) != NULL) { STAILQ_REMOVE_HEAD(&queue->tq_queue, ta_link); gtask->ta_flags &= ~TASK_ENQUEUED; tb.tb_running = gtask; + tb.tb_seq = ++queue->tq_seq; TQ_UNLOCK(queue); KASSERT(gtask->ta_func != NULL, ("task->ta_func is NULL")); gtask->ta_func(gtask->ta_context); TQ_LOCK(queue); - tb.tb_running = NULL; wakeup(gtask); - - TAILQ_REMOVE(&queue->tq_active, &tb, tb_link); - tb_first = TAILQ_FIRST(&queue->tq_active); - if (tb_first != NULL && - tb_first->tb_running == TB_DRAIN_WAITER) - wakeup(tb_first); } + LIST_REMOVE(&tb, tb_link); } static int @@ -394,7 +373,7 @@ task_is_running(struct gtaskqueue *queue, struct gtask *gtask) struct gtaskqueue_busy *tb; TQ_ASSERT_LOCKED(queue); - TAILQ_FOREACH(tb, &queue->tq_active, tb_link) { + LIST_FOREACH(tb, &queue->tq_active, tb_link) { if (tb->tb_running == gtask) return (1); } @@ -427,7 +406,7 @@ static void gtaskqueue_drain_locked(struct gtaskqueue *queue, struct gtask *gtask) { while ((gtask->ta_flags & TASK_ENQUEUED) || task_is_running(queue, gtask)) - TQ_SLEEP(queue, gtask, &queue->tq_mutex, PWAIT, "-", 0); + TQ_SLEEP(queue, gtask, "gtq_drain"); } void @@ -563,7 +542,7 @@ gtaskqueue_thread_loop(void *arg) */ if ((tq->tq_flags & TQ_FLAGS_ACTIVE) == 0) break; - TQ_SLEEP(tq, tq, &tq->tq_mutex, 0, "-", 0); + TQ_SLEEP(tq, tq, "-"); } gtaskqueue_run_locked(tq); /* @@ -589,7 +568,7 @@ gtaskqueue_thread_enqueue(void *context) tqp = context; tq = *tqp; - wakeup_one(tq); + wakeup_any(tq); } |