Commit 0bd9289c authored by Qu Wenruo's avatar Qu Wenruo Committed by Josef Bacik

btrfs: Add threshold workqueue based on kernel workqueue

The original btrfs_workers has thresholding functions to dynamically
create or destroy kthreads.

Though there is no such function in kernel workqueue because the worker
is not created manually, we can still use the workqueue_set_max_active
to simulated the behavior, mainly to achieve a better HDD performance by
setting a high threshold on submit_workers.
(Sadly, no resource can be saved)

So in this patch, extra workqueue pending counters are introduced to
dynamically change the max active of each btrfs_workqueue_struct, hoping
to restore the behavior of the original thresholding function.

Also, workqueue_set_max_active use a mutex to protect workqueue_struct,
which is not meant to be called too frequently, so a new interval
mechanism is applied, that will only call workqueue_set_max_active after
a count of work is queued. Hoping to balance both the random and
sequence performance on HDD.
Signed-off-by: default avatarQu Wenruo <quwenruo@cn.fujitsu.com>
Tested-by: default avatarDavid Sterba <dsterba@suse.cz>
Signed-off-by: default avatarJosef Bacik <jbacik@fb.com>
parent 1ca08976
...@@ -30,6 +30,9 @@ ...@@ -30,6 +30,9 @@
#define WORK_ORDER_DONE_BIT 2 #define WORK_ORDER_DONE_BIT 2
#define WORK_HIGH_PRIO_BIT 3 #define WORK_HIGH_PRIO_BIT 3
#define NO_THRESHOLD (-1)
#define DFT_THRESHOLD (32)
/* /*
* container for the kthread task pointer and the list of pending work * container for the kthread task pointer and the list of pending work
* One of these is allocated per thread. * One of these is allocated per thread.
...@@ -737,6 +740,14 @@ struct __btrfs_workqueue_struct { ...@@ -737,6 +740,14 @@ struct __btrfs_workqueue_struct {
/* Spinlock for ordered_list */ /* Spinlock for ordered_list */
spinlock_t list_lock; spinlock_t list_lock;
/* Thresholding related variants */
atomic_t pending;
int max_active;
int current_max;
int thresh;
unsigned int count;
spinlock_t thres_lock;
}; };
struct btrfs_workqueue_struct { struct btrfs_workqueue_struct {
...@@ -745,19 +756,34 @@ struct btrfs_workqueue_struct { ...@@ -745,19 +756,34 @@ struct btrfs_workqueue_struct {
}; };
static inline struct __btrfs_workqueue_struct static inline struct __btrfs_workqueue_struct
*__btrfs_alloc_workqueue(char *name, int flags, int max_active) *__btrfs_alloc_workqueue(char *name, int flags, int max_active, int thresh)
{ {
struct __btrfs_workqueue_struct *ret = kzalloc(sizeof(*ret), GFP_NOFS); struct __btrfs_workqueue_struct *ret = kzalloc(sizeof(*ret), GFP_NOFS);
if (unlikely(!ret)) if (unlikely(!ret))
return NULL; return NULL;
ret->max_active = max_active;
atomic_set(&ret->pending, 0);
if (thresh == 0)
thresh = DFT_THRESHOLD;
/* For low threshold, disabling threshold is a better choice */
if (thresh < DFT_THRESHOLD) {
ret->current_max = max_active;
ret->thresh = NO_THRESHOLD;
} else {
ret->current_max = 1;
ret->thresh = thresh;
}
if (flags & WQ_HIGHPRI) if (flags & WQ_HIGHPRI)
ret->normal_wq = alloc_workqueue("%s-%s-high", flags, ret->normal_wq = alloc_workqueue("%s-%s-high", flags,
max_active, "btrfs", name); ret->max_active,
"btrfs", name);
else else
ret->normal_wq = alloc_workqueue("%s-%s", flags, ret->normal_wq = alloc_workqueue("%s-%s", flags,
max_active, "btrfs", name); ret->max_active, "btrfs",
name);
if (unlikely(!ret->normal_wq)) { if (unlikely(!ret->normal_wq)) {
kfree(ret); kfree(ret);
return NULL; return NULL;
...@@ -765,6 +791,7 @@ static inline struct __btrfs_workqueue_struct ...@@ -765,6 +791,7 @@ static inline struct __btrfs_workqueue_struct
INIT_LIST_HEAD(&ret->ordered_list); INIT_LIST_HEAD(&ret->ordered_list);
spin_lock_init(&ret->list_lock); spin_lock_init(&ret->list_lock);
spin_lock_init(&ret->thres_lock);
return ret; return ret;
} }
...@@ -773,7 +800,8 @@ __btrfs_destroy_workqueue(struct __btrfs_workqueue_struct *wq); ...@@ -773,7 +800,8 @@ __btrfs_destroy_workqueue(struct __btrfs_workqueue_struct *wq);
struct btrfs_workqueue_struct *btrfs_alloc_workqueue(char *name, struct btrfs_workqueue_struct *btrfs_alloc_workqueue(char *name,
int flags, int flags,
int max_active) int max_active,
int thresh)
{ {
struct btrfs_workqueue_struct *ret = kzalloc(sizeof(*ret), GFP_NOFS); struct btrfs_workqueue_struct *ret = kzalloc(sizeof(*ret), GFP_NOFS);
...@@ -781,14 +809,15 @@ struct btrfs_workqueue_struct *btrfs_alloc_workqueue(char *name, ...@@ -781,14 +809,15 @@ struct btrfs_workqueue_struct *btrfs_alloc_workqueue(char *name,
return NULL; return NULL;
ret->normal = __btrfs_alloc_workqueue(name, flags & ~WQ_HIGHPRI, ret->normal = __btrfs_alloc_workqueue(name, flags & ~WQ_HIGHPRI,
max_active); max_active, thresh);
if (unlikely(!ret->normal)) { if (unlikely(!ret->normal)) {
kfree(ret); kfree(ret);
return NULL; return NULL;
} }
if (flags & WQ_HIGHPRI) { if (flags & WQ_HIGHPRI) {
ret->high = __btrfs_alloc_workqueue(name, flags, max_active); ret->high = __btrfs_alloc_workqueue(name, flags, max_active,
thresh);
if (unlikely(!ret->high)) { if (unlikely(!ret->high)) {
__btrfs_destroy_workqueue(ret->normal); __btrfs_destroy_workqueue(ret->normal);
kfree(ret); kfree(ret);
...@@ -798,6 +827,66 @@ struct btrfs_workqueue_struct *btrfs_alloc_workqueue(char *name, ...@@ -798,6 +827,66 @@ struct btrfs_workqueue_struct *btrfs_alloc_workqueue(char *name,
return ret; return ret;
} }
/*
* Hook for threshold which will be called in btrfs_queue_work.
* This hook WILL be called in IRQ handler context,
* so workqueue_set_max_active MUST NOT be called in this hook
*/
static inline void thresh_queue_hook(struct __btrfs_workqueue_struct *wq)
{
if (wq->thresh == NO_THRESHOLD)
return;
atomic_inc(&wq->pending);
}
/*
* Hook for threshold which will be called before executing the work,
* This hook is called in kthread content.
* So workqueue_set_max_active is called here.
*/
static inline void thresh_exec_hook(struct __btrfs_workqueue_struct *wq)
{
int new_max_active;
long pending;
int need_change = 0;
if (wq->thresh == NO_THRESHOLD)
return;
atomic_dec(&wq->pending);
spin_lock(&wq->thres_lock);
/*
* Use wq->count to limit the calling frequency of
* workqueue_set_max_active.
*/
wq->count++;
wq->count %= (wq->thresh / 4);
if (!wq->count)
goto out;
new_max_active = wq->current_max;
/*
* pending may be changed later, but it's OK since we really
* don't need it so accurate to calculate new_max_active.
*/
pending = atomic_read(&wq->pending);
if (pending > wq->thresh)
new_max_active++;
if (pending < wq->thresh / 2)
new_max_active--;
new_max_active = clamp_val(new_max_active, 1, wq->max_active);
if (new_max_active != wq->current_max) {
need_change = 1;
wq->current_max = new_max_active;
}
out:
spin_unlock(&wq->thres_lock);
if (need_change) {
workqueue_set_max_active(wq->normal_wq, wq->current_max);
}
}
static void run_ordered_work(struct __btrfs_workqueue_struct *wq) static void run_ordered_work(struct __btrfs_workqueue_struct *wq)
{ {
struct list_head *list = &wq->ordered_list; struct list_head *list = &wq->ordered_list;
...@@ -858,6 +947,7 @@ static void normal_work_helper(struct work_struct *arg) ...@@ -858,6 +947,7 @@ static void normal_work_helper(struct work_struct *arg)
need_order = 1; need_order = 1;
wq = work->wq; wq = work->wq;
thresh_exec_hook(wq);
work->func(work); work->func(work);
if (need_order) { if (need_order) {
set_bit(WORK_DONE_BIT, &work->flags); set_bit(WORK_DONE_BIT, &work->flags);
...@@ -884,6 +974,7 @@ static inline void __btrfs_queue_work(struct __btrfs_workqueue_struct *wq, ...@@ -884,6 +974,7 @@ static inline void __btrfs_queue_work(struct __btrfs_workqueue_struct *wq,
unsigned long flags; unsigned long flags;
work->wq = wq; work->wq = wq;
thresh_queue_hook(wq);
if (work->ordered_func) { if (work->ordered_func) {
spin_lock_irqsave(&wq->list_lock, flags); spin_lock_irqsave(&wq->list_lock, flags);
list_add_tail(&work->ordered_list, &wq->ordered_list); list_add_tail(&work->ordered_list, &wq->ordered_list);
...@@ -922,9 +1013,9 @@ void btrfs_destroy_workqueue(struct btrfs_workqueue_struct *wq) ...@@ -922,9 +1013,9 @@ void btrfs_destroy_workqueue(struct btrfs_workqueue_struct *wq)
void btrfs_workqueue_set_max(struct btrfs_workqueue_struct *wq, int max) void btrfs_workqueue_set_max(struct btrfs_workqueue_struct *wq, int max)
{ {
workqueue_set_max_active(wq->normal->normal_wq, max); wq->normal->max_active = max;
if (wq->high) if (wq->high)
workqueue_set_max_active(wq->high->normal_wq, max); wq->high->max_active = max;
} }
void btrfs_set_work_high_priority(struct btrfs_work_struct *work) void btrfs_set_work_high_priority(struct btrfs_work_struct *work)
......
...@@ -138,7 +138,8 @@ struct btrfs_work_struct { ...@@ -138,7 +138,8 @@ struct btrfs_work_struct {
struct btrfs_workqueue_struct *btrfs_alloc_workqueue(char *name, struct btrfs_workqueue_struct *btrfs_alloc_workqueue(char *name,
int flags, int flags,
int max_active); int max_active,
int thresh);
void btrfs_init_work(struct btrfs_work_struct *work, void btrfs_init_work(struct btrfs_work_struct *work,
void (*func)(struct btrfs_work_struct *), void (*func)(struct btrfs_work_struct *),
void (*ordered_func)(struct btrfs_work_struct *), void (*ordered_func)(struct btrfs_work_struct *),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment