mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-22 02:27:36 +03:00
Add TASKQ_DYNAMIC feature
Setting the TASKQ_DYNAMIC flag will create a taskq with dynamic semantics. Initially only a single worker thread will be created to service tasks dispatched to the queue. As additional threads are needed they will be dynamically spawned up to the max number specified by 'nthreads'. When the threads are no longer needed, because the taskq is empty, they will automatically terminate. Due to the low cost of creating and destroying threads under Linux by default new threads and spawned and terminated aggressively. There are two modules options which can be tuned to adjust this behavior if needed. * spl_taskq_thread_sequential - The number of sequential tasks, without interruption, which needed to be handled by a worker thread before a new worker thread is spawned. Default 4. * spl_taskq_thread_dynamic - Provides the ability to completely disable the use of dynamic taskqs on the system. This is provided for the purposes of debugging and troubleshooting. Default 1 (enabled). This behavior is fundamentally consistent with the dynamic taskq implementation found in both illumos and FreeBSD. Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Tim Chase <tim@chase2k.com> Closes #458
This commit is contained in:
+148
-99
@@ -28,6 +28,7 @@
|
||||
#include <sys/vmem.h>
|
||||
#include <sys/random.h>
|
||||
#include <sys/taskq.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/timer.h>
|
||||
#include <linux/delay.h>
|
||||
#include "splat-internal.h"
|
||||
@@ -75,6 +76,10 @@
|
||||
#define SPLAT_TASKQ_TEST10_NAME "cancel"
|
||||
#define SPLAT_TASKQ_TEST10_DESC "Cancel task execution"
|
||||
|
||||
#define SPLAT_TASKQ_TEST11_ID 0x020b
|
||||
#define SPLAT_TASKQ_TEST11_NAME "dynamic"
|
||||
#define SPLAT_TASKQ_TEST11_DESC "Dynamic task queue thread creation"
|
||||
|
||||
#define SPLAT_TASKQ_ORDER_MAX 8
|
||||
#define SPLAT_TASKQ_DEPTH_MAX 16
|
||||
|
||||
@@ -1052,11 +1057,104 @@ splat_taskq_test7(struct file *file, void *arg)
|
||||
|
||||
rc = splat_taskq_test7_impl(file, arg, B_FALSE);
|
||||
if (rc)
|
||||
return rc;
|
||||
return (rc);
|
||||
|
||||
rc = splat_taskq_test7_impl(file, arg, B_TRUE);
|
||||
|
||||
return rc;
|
||||
return (rc);
|
||||
}
|
||||
|
||||
static void
|
||||
splat_taskq_throughput_func(void *arg)
|
||||
{
|
||||
splat_taskq_arg_t *tq_arg = (splat_taskq_arg_t *)arg;
|
||||
ASSERT(tq_arg);
|
||||
|
||||
atomic_inc(tq_arg->count);
|
||||
}
|
||||
|
||||
static int
|
||||
splat_taskq_throughput(struct file *file, void *arg, const char *name,
|
||||
int nthreads, int minalloc, int maxalloc, int flags, int tasks,
|
||||
struct timespec *delta)
|
||||
{
|
||||
taskq_t *tq;
|
||||
taskqid_t id;
|
||||
splat_taskq_arg_t tq_arg;
|
||||
taskq_ent_t **tqes;
|
||||
atomic_t count;
|
||||
struct timespec start, stop;
|
||||
int i, j, rc = 0;
|
||||
|
||||
tqes = vmalloc(sizeof (*tqes) * tasks);
|
||||
if (tqes == NULL)
|
||||
return (-ENOMEM);
|
||||
|
||||
memset(tqes, 0, sizeof (*tqes) * tasks);
|
||||
|
||||
splat_vprint(file, name, "Taskq '%s' creating (%d/%d/%d/%d)\n",
|
||||
name, nthreads, minalloc, maxalloc, tasks);
|
||||
if ((tq = taskq_create(name, nthreads, maxclsyspri,
|
||||
minalloc, maxalloc, flags)) == NULL) {
|
||||
splat_vprint(file, name, "Taskq '%s' create failed\n", name);
|
||||
rc = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
tq_arg.file = file;
|
||||
tq_arg.name = name;
|
||||
tq_arg.count = &count;
|
||||
atomic_set(tq_arg.count, 0);
|
||||
|
||||
getnstimeofday(&start);
|
||||
|
||||
for (i = 0; i < tasks; i++) {
|
||||
tqes[i] = kmalloc(sizeof (taskq_ent_t), GFP_KERNEL);
|
||||
if (tqes[i] == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
taskq_init_ent(tqes[i]);
|
||||
taskq_dispatch_ent(tq, splat_taskq_throughput_func,
|
||||
&tq_arg, TQ_SLEEP, tqes[i]);
|
||||
id = tqes[i]->tqent_id;
|
||||
|
||||
if (id == 0) {
|
||||
splat_vprint(file, name, "Taskq '%s' function '%s' "
|
||||
"dispatch %d failed\n", tq_arg.name,
|
||||
sym2str(splat_taskq_throughput_func), i);
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
splat_vprint(file, name, "Taskq '%s' waiting for %d dispatches\n",
|
||||
tq_arg.name, tasks);
|
||||
|
||||
taskq_wait(tq);
|
||||
|
||||
if (delta != NULL) {
|
||||
getnstimeofday(&stop);
|
||||
*delta = timespec_sub(stop, start);
|
||||
}
|
||||
|
||||
splat_vprint(file, name, "Taskq '%s' %d/%d dispatches finished\n",
|
||||
tq_arg.name, atomic_read(tq_arg.count), tasks);
|
||||
|
||||
if (atomic_read(tq_arg.count) != tasks)
|
||||
rc = -ERANGE;
|
||||
|
||||
out:
|
||||
splat_vprint(file, name, "Taskq '%s' destroying\n", tq_arg.name);
|
||||
taskq_destroy(tq);
|
||||
out_free:
|
||||
for (j = 0; j < tasks && tqes[j] != NULL; j++)
|
||||
kfree(tqes[j]);
|
||||
|
||||
vfree(tqes);
|
||||
|
||||
return (rc);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1065,107 +1163,15 @@ splat_taskq_test7(struct file *file, void *arg)
|
||||
* pass. The purpose is to provide a benchmark for measuring the
|
||||
* effectiveness of taskq optimizations.
|
||||
*/
|
||||
static void
|
||||
splat_taskq_test8_func(void *arg)
|
||||
{
|
||||
splat_taskq_arg_t *tq_arg = (splat_taskq_arg_t *)arg;
|
||||
ASSERT(tq_arg);
|
||||
|
||||
atomic_inc(tq_arg->count);
|
||||
}
|
||||
|
||||
#define TEST8_NUM_TASKS 0x20000
|
||||
#define TEST8_THREADS_PER_TASKQ 100
|
||||
|
||||
static int
|
||||
splat_taskq_test8_common(struct file *file, void *arg, int minalloc,
|
||||
int maxalloc)
|
||||
{
|
||||
taskq_t *tq;
|
||||
taskqid_t id;
|
||||
splat_taskq_arg_t tq_arg;
|
||||
taskq_ent_t **tqes;
|
||||
atomic_t count;
|
||||
int i, j, rc = 0;
|
||||
|
||||
tqes = vmalloc(sizeof(*tqes) * TEST8_NUM_TASKS);
|
||||
if (tqes == NULL)
|
||||
return -ENOMEM;
|
||||
memset(tqes, 0, sizeof(*tqes) * TEST8_NUM_TASKS);
|
||||
|
||||
splat_vprint(file, SPLAT_TASKQ_TEST8_NAME,
|
||||
"Taskq '%s' creating (%d/%d/%d)\n",
|
||||
SPLAT_TASKQ_TEST8_NAME,
|
||||
minalloc, maxalloc, TEST8_NUM_TASKS);
|
||||
if ((tq = taskq_create(SPLAT_TASKQ_TEST8_NAME, TEST8_THREADS_PER_TASKQ,
|
||||
maxclsyspri, minalloc, maxalloc,
|
||||
TASKQ_PREPOPULATE)) == NULL) {
|
||||
splat_vprint(file, SPLAT_TASKQ_TEST8_NAME,
|
||||
"Taskq '%s' create failed\n",
|
||||
SPLAT_TASKQ_TEST8_NAME);
|
||||
rc = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
tq_arg.file = file;
|
||||
tq_arg.name = SPLAT_TASKQ_TEST8_NAME;
|
||||
tq_arg.count = &count;
|
||||
atomic_set(tq_arg.count, 0);
|
||||
|
||||
for (i = 0; i < TEST8_NUM_TASKS; i++) {
|
||||
tqes[i] = kmalloc(sizeof(taskq_ent_t), GFP_KERNEL);
|
||||
if (tqes[i] == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
taskq_init_ent(tqes[i]);
|
||||
|
||||
taskq_dispatch_ent(tq, splat_taskq_test8_func,
|
||||
&tq_arg, TQ_SLEEP, tqes[i]);
|
||||
|
||||
id = tqes[i]->tqent_id;
|
||||
|
||||
if (id == 0) {
|
||||
splat_vprint(file, SPLAT_TASKQ_TEST8_NAME,
|
||||
"Taskq '%s' function '%s' dispatch "
|
||||
"%d failed\n", tq_arg.name,
|
||||
sym2str(splat_taskq_test8_func), i);
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
splat_vprint(file, SPLAT_TASKQ_TEST8_NAME, "Taskq '%s' "
|
||||
"waiting for %d dispatches\n", tq_arg.name,
|
||||
TEST8_NUM_TASKS);
|
||||
taskq_wait(tq);
|
||||
splat_vprint(file, SPLAT_TASKQ_TEST8_NAME, "Taskq '%s' "
|
||||
"%d/%d dispatches finished\n", tq_arg.name,
|
||||
atomic_read(tq_arg.count), TEST8_NUM_TASKS);
|
||||
|
||||
if (atomic_read(tq_arg.count) != TEST8_NUM_TASKS)
|
||||
rc = -ERANGE;
|
||||
|
||||
out:
|
||||
splat_vprint(file, SPLAT_TASKQ_TEST8_NAME, "Taskq '%s' destroying\n",
|
||||
tq_arg.name);
|
||||
taskq_destroy(tq);
|
||||
out_free:
|
||||
for (j = 0; j < TEST8_NUM_TASKS && tqes[j] != NULL; j++)
|
||||
kfree(tqes[j]);
|
||||
vfree(tqes);
|
||||
|
||||
return rc;
|
||||
}
|
||||
#define TEST8_NUM_TASKS 0x20000
|
||||
#define TEST8_THREADS_PER_TASKQ 100
|
||||
|
||||
static int
|
||||
splat_taskq_test8(struct file *file, void *arg)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = splat_taskq_test8_common(file, arg, 1, 100);
|
||||
|
||||
return rc;
|
||||
return (splat_taskq_throughput(file, arg,
|
||||
SPLAT_TASKQ_TEST8_NAME, TEST8_THREADS_PER_TASKQ,
|
||||
1, INT_MAX, TASKQ_PREPOPULATE, TEST8_NUM_TASKS, NULL));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1433,6 +1439,46 @@ out_free:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a dynamic taskq with 100 threads and dispatch a huge number of
|
||||
* trivial tasks. This will cause the taskq to grow quickly to its max
|
||||
* thread count. This test should always pass. The purpose is to provide
|
||||
* a benchmark for measuring the performance of dynamic taskqs.
|
||||
*/
|
||||
#define TEST11_NUM_TASKS 100000
|
||||
#define TEST11_THREADS_PER_TASKQ 100
|
||||
|
||||
static int
|
||||
splat_taskq_test11(struct file *file, void *arg)
|
||||
{
|
||||
struct timespec normal, dynamic;
|
||||
int error;
|
||||
|
||||
error = splat_taskq_throughput(file, arg, SPLAT_TASKQ_TEST11_NAME,
|
||||
TEST11_THREADS_PER_TASKQ, 1, INT_MAX,
|
||||
TASKQ_PREPOPULATE, TEST11_NUM_TASKS, &normal);
|
||||
if (error)
|
||||
return (error);
|
||||
|
||||
error = splat_taskq_throughput(file, arg, SPLAT_TASKQ_TEST11_NAME,
|
||||
TEST11_THREADS_PER_TASKQ, 1, INT_MAX,
|
||||
TASKQ_PREPOPULATE | TASKQ_DYNAMIC, TEST11_NUM_TASKS, &dynamic);
|
||||
if (error)
|
||||
return (error);
|
||||
|
||||
splat_vprint(file, SPLAT_TASKQ_TEST11_NAME,
|
||||
"Timing taskq_wait(): normal=%ld.%09lds, dynamic=%ld.%09lds\n",
|
||||
normal.tv_sec, normal.tv_nsec,
|
||||
dynamic.tv_sec, dynamic.tv_nsec);
|
||||
|
||||
/* A 10x increase in runtime is used to indicate a core problem. */
|
||||
if ((dynamic.tv_sec * NANOSEC + dynamic.tv_nsec) >
|
||||
((normal.tv_sec * NANOSEC + normal.tv_nsec) * 10))
|
||||
error = -ETIME;
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
splat_subsystem_t *
|
||||
splat_taskq_init(void)
|
||||
{
|
||||
@@ -1470,6 +1516,8 @@ splat_taskq_init(void)
|
||||
SPLAT_TASKQ_TEST9_ID, splat_taskq_test9);
|
||||
SPLAT_TEST_INIT(sub, SPLAT_TASKQ_TEST10_NAME, SPLAT_TASKQ_TEST10_DESC,
|
||||
SPLAT_TASKQ_TEST10_ID, splat_taskq_test10);
|
||||
SPLAT_TEST_INIT(sub, SPLAT_TASKQ_TEST11_NAME, SPLAT_TASKQ_TEST11_DESC,
|
||||
SPLAT_TASKQ_TEST11_ID, splat_taskq_test11);
|
||||
|
||||
return sub;
|
||||
}
|
||||
@@ -1478,6 +1526,7 @@ void
|
||||
splat_taskq_fini(splat_subsystem_t *sub)
|
||||
{
|
||||
ASSERT(sub);
|
||||
SPLAT_TEST_FINI(sub, SPLAT_TASKQ_TEST11_ID);
|
||||
SPLAT_TEST_FINI(sub, SPLAT_TASKQ_TEST10_ID);
|
||||
SPLAT_TEST_FINI(sub, SPLAT_TASKQ_TEST9_ID);
|
||||
SPLAT_TEST_FINI(sub, SPLAT_TASKQ_TEST8_ID);
|
||||
|
||||
Reference in New Issue
Block a user