mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-12-26 03:09:34 +03:00
Add a delay to tearing down threads.
It's been observed that in certain workloads (zvol-related being a big one), ZFS will end up spending a large amount of time spinning up taskqs only to tear them down again almost immediately, then spin them up again... I noticed this when I looked at what my mostly-idle system was doing and wondered how on earth taskq creation/destroy was a bunch of time... So I added a configurable delay to avoid it tearing down tasks the first time it notices them idle, and the total number of threads at steady state went up, but the amount of time being burned just tearing down/turning up new ones almost vanished. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Rich Ercolani <rincebrain@gmail.com> Closes #14938
This commit is contained in:
parent
8e8acabdca
commit
35a6247c5f
@ -104,6 +104,7 @@ typedef struct taskq {
|
|||||||
/* list node for the cpu hotplug callback */
|
/* list node for the cpu hotplug callback */
|
||||||
struct hlist_node tq_hp_cb_node;
|
struct hlist_node tq_hp_cb_node;
|
||||||
boolean_t tq_hp_support;
|
boolean_t tq_hp_support;
|
||||||
|
unsigned long lastshouldstop; /* when to purge dynamic */
|
||||||
} taskq_t;
|
} taskq_t;
|
||||||
|
|
||||||
typedef struct taskq_ent {
|
typedef struct taskq_ent {
|
||||||
|
@ -193,4 +193,19 @@ The proc file will walk the lists with lock held,
|
|||||||
reading it could cause a lock-up if the list grow too large
|
reading it could cause a lock-up if the list grow too large
|
||||||
without limiting the output.
|
without limiting the output.
|
||||||
"(truncated)" will be shown if the list is larger than the limit.
|
"(truncated)" will be shown if the list is larger than the limit.
|
||||||
|
.
|
||||||
|
.It Sy spl_taskq_thread_timeout_ms Ns = Ns Sy 10000 Pq uint
|
||||||
|
(Linux-only)
|
||||||
|
How long a taskq has to have had no work before we tear it down.
|
||||||
|
Previously, we would tear down a dynamic taskq worker as soon
|
||||||
|
as we noticed it had no work, but it was observed that this led
|
||||||
|
to a lot of churn in tearing down things we then immediately
|
||||||
|
spawned anew.
|
||||||
|
In practice, it seems any nonzero value will remove the vast
|
||||||
|
majority of this churn, while the nontrivially larger value
|
||||||
|
was chosen to help filter out the little remaining churn on
|
||||||
|
a mostly idle system.
|
||||||
|
Setting this value to
|
||||||
|
.Sy 0
|
||||||
|
will revert to the previous behavior.
|
||||||
.El
|
.El
|
||||||
|
@ -36,6 +36,12 @@ static int spl_taskq_thread_bind = 0;
|
|||||||
module_param(spl_taskq_thread_bind, int, 0644);
|
module_param(spl_taskq_thread_bind, int, 0644);
|
||||||
MODULE_PARM_DESC(spl_taskq_thread_bind, "Bind taskq thread to CPU by default");
|
MODULE_PARM_DESC(spl_taskq_thread_bind, "Bind taskq thread to CPU by default");
|
||||||
|
|
||||||
|
static uint_t spl_taskq_thread_timeout_ms = 10000;
|
||||||
|
/* BEGIN CSTYLED */
|
||||||
|
module_param(spl_taskq_thread_timeout_ms, uint, 0644);
|
||||||
|
/* END CSTYLED */
|
||||||
|
MODULE_PARM_DESC(spl_taskq_thread_timeout_ms,
|
||||||
|
"Time to require a dynamic thread be idle before it gets cleaned up");
|
||||||
|
|
||||||
static int spl_taskq_thread_dynamic = 1;
|
static int spl_taskq_thread_dynamic = 1;
|
||||||
module_param(spl_taskq_thread_dynamic, int, 0444);
|
module_param(spl_taskq_thread_dynamic, int, 0444);
|
||||||
@ -848,12 +854,37 @@ taskq_thread_should_stop(taskq_t *tq, taskq_thread_t *tqt)
|
|||||||
tqt_thread_list) == tqt)
|
tqt_thread_list) == tqt)
|
||||||
return (0);
|
return (0);
|
||||||
|
|
||||||
return
|
int no_work =
|
||||||
((tq->tq_nspawn == 0) && /* No threads are being spawned */
|
((tq->tq_nspawn == 0) && /* No threads are being spawned */
|
||||||
(tq->tq_nactive == 0) && /* No threads are handling tasks */
|
(tq->tq_nactive == 0) && /* No threads are handling tasks */
|
||||||
(tq->tq_nthreads > 1) && /* More than 1 thread is running */
|
(tq->tq_nthreads > 1) && /* More than 1 thread is running */
|
||||||
(!taskq_next_ent(tq)) && /* There are no pending tasks */
|
(!taskq_next_ent(tq)) && /* There are no pending tasks */
|
||||||
(spl_taskq_thread_dynamic)); /* Dynamic taskqs are allowed */
|
(spl_taskq_thread_dynamic)); /* Dynamic taskqs are allowed */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we would have said stop before, let's instead wait a bit, maybe
|
||||||
|
* we'll see more work come our way soon...
|
||||||
|
*/
|
||||||
|
if (no_work) {
|
||||||
|
/* if it's 0, we want the old behavior. */
|
||||||
|
/* if the taskq is being torn down, we also want to go away. */
|
||||||
|
if (spl_taskq_thread_timeout_ms == 0 ||
|
||||||
|
!(tq->tq_flags & TASKQ_ACTIVE))
|
||||||
|
return (1);
|
||||||
|
unsigned long lasttime = tq->lastshouldstop;
|
||||||
|
if (lasttime > 0) {
|
||||||
|
if (time_after(jiffies, lasttime +
|
||||||
|
msecs_to_jiffies(spl_taskq_thread_timeout_ms)))
|
||||||
|
return (1);
|
||||||
|
else
|
||||||
|
return (0);
|
||||||
|
} else {
|
||||||
|
tq->lastshouldstop = jiffies;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tq->lastshouldstop = 0;
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -1091,6 +1122,7 @@ taskq_create(const char *name, int threads_arg, pri_t pri,
|
|||||||
tq->tq_flags = (flags | TASKQ_ACTIVE);
|
tq->tq_flags = (flags | TASKQ_ACTIVE);
|
||||||
tq->tq_next_id = TASKQID_INITIAL;
|
tq->tq_next_id = TASKQID_INITIAL;
|
||||||
tq->tq_lowest_id = TASKQID_INITIAL;
|
tq->tq_lowest_id = TASKQID_INITIAL;
|
||||||
|
tq->lastshouldstop = 0;
|
||||||
INIT_LIST_HEAD(&tq->tq_free_list);
|
INIT_LIST_HEAD(&tq->tq_free_list);
|
||||||
INIT_LIST_HEAD(&tq->tq_pend_list);
|
INIT_LIST_HEAD(&tq->tq_pend_list);
|
||||||
INIT_LIST_HEAD(&tq->tq_prio_list);
|
INIT_LIST_HEAD(&tq->tq_prio_list);
|
||||||
|
Loading…
Reference in New Issue
Block a user