Skip to content

Commit

Permalink
block: strip out locking optimization in put_io_context()
Browse files Browse the repository at this point in the history
put_io_context() performed a complex trylock dancing to avoid
deferring ioc release to workqueue.  It was also broken on UP because
trylock was always assumed to succeed which resulted in unbalanced
preemption count.

While there are ways to fix the UP breakage, even the most
pathological microbench (forced ioc allocation and tight fork/exit
loop) fails to show any appreciable performance benefit of the
optimization.  Strip it out.  If there turns out to be workloads which
are affected by this change, simpler optimization from the discussion
thread can be applied later.

Signed-off-by: Tejun Heo <[email protected]>
LKML-Reference: <1328514611.21268.66.camel@sli10-conroe>
Signed-off-by: Jens Axboe <[email protected]>
  • Loading branch information
htejun authored and axboe committed Feb 7, 2012
1 parent 822bfa5 commit 11a3122
Show file tree
Hide file tree
Showing 8 changed files with 18 additions and 92 deletions.
2 changes: 1 addition & 1 deletion block/blk-cgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -1659,7 +1659,7 @@ static void blkiocg_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
ioc = get_task_io_context(task, GFP_ATOMIC, NUMA_NO_NODE);
if (ioc) {
ioc_cgroup_changed(ioc);
put_io_context(ioc, NULL);
put_io_context(ioc);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion block/blk-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ static inline void blk_free_request(struct request_queue *q, struct request *rq)
if (rq->cmd_flags & REQ_ELVPRIV) {
elv_put_request(q, rq);
if (rq->elv.icq)
put_io_context(rq->elv.icq->ioc, q);
put_io_context(rq->elv.icq->ioc);
}

mempool_free(rq, q->rq.rq_pool);
Expand Down
92 changes: 11 additions & 81 deletions block/blk-ioc.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,6 @@ void get_io_context(struct io_context *ioc)
}
EXPORT_SYMBOL(get_io_context);

/*
* Releasing ioc may nest into another put_io_context() leading to nested
* fast path release. As the ioc's can't be the same, this is okay but
* makes lockdep whine. Keep track of nesting and use it as subclass.
*/
#ifdef CONFIG_LOCKDEP
#define ioc_release_depth(q) ((q) ? (q)->ioc_release_depth : 0)
#define ioc_release_depth_inc(q) (q)->ioc_release_depth++
#define ioc_release_depth_dec(q) (q)->ioc_release_depth--
#else
#define ioc_release_depth(q) 0
#define ioc_release_depth_inc(q) do { } while (0)
#define ioc_release_depth_dec(q) do { } while (0)
#endif

static void icq_free_icq_rcu(struct rcu_head *head)
{
struct io_cq *icq = container_of(head, struct io_cq, __rcu_head);
Expand Down Expand Up @@ -75,11 +60,8 @@ static void ioc_exit_icq(struct io_cq *icq)
if (rcu_dereference_raw(ioc->icq_hint) == icq)
rcu_assign_pointer(ioc->icq_hint, NULL);

if (et->ops.elevator_exit_icq_fn) {
ioc_release_depth_inc(q);
if (et->ops.elevator_exit_icq_fn)
et->ops.elevator_exit_icq_fn(icq);
ioc_release_depth_dec(q);
}

/*
* @icq->q might have gone away by the time RCU callback runs
Expand Down Expand Up @@ -149,81 +131,29 @@ static void ioc_release_fn(struct work_struct *work)
/**
* put_io_context - put a reference of io_context
* @ioc: io_context to put
* @locked_q: request_queue the caller is holding queue_lock of (hint)
*
* Decrement reference count of @ioc and release it if the count reaches
* zero. If the caller is holding queue_lock of a queue, it can indicate
* that with @locked_q. This is an optimization hint and the caller is
* allowed to pass in %NULL even when it's holding a queue_lock.
* zero.
*/
void put_io_context(struct io_context *ioc, struct request_queue *locked_q)
void put_io_context(struct io_context *ioc)
{
struct request_queue *last_q = locked_q;
unsigned long flags;

if (ioc == NULL)
return;

BUG_ON(atomic_long_read(&ioc->refcount) <= 0);
if (locked_q)
lockdep_assert_held(locked_q->queue_lock);

if (!atomic_long_dec_and_test(&ioc->refcount))
return;

/*
* Destroy @ioc. This is a bit messy because icq's are chained
* from both ioc and queue, and ioc->lock nests inside queue_lock.
* The inner ioc->lock should be held to walk our icq_list and then
* for each icq the outer matching queue_lock should be grabbed.
* ie. We need to do reverse-order double lock dancing.
*
* Another twist is that we are often called with one of the
* matching queue_locks held as indicated by @locked_q, which
* prevents performing double-lock dance for other queues.
*
* So, we do it in two stages. The fast path uses the queue_lock
* the caller is holding and, if other queues need to be accessed,
* uses trylock to avoid introducing locking dependency. This can
* handle most cases, especially if @ioc was performing IO on only
* single device.
*
* If trylock doesn't cut it, we defer to @ioc->release_work which
* can do all the double-locking dancing.
* Releasing ioc requires reverse order double locking and we may
* already be holding a queue_lock. Do it asynchronously from wq.
*/
spin_lock_irqsave_nested(&ioc->lock, flags,
ioc_release_depth(locked_q));

while (!hlist_empty(&ioc->icq_list)) {
struct io_cq *icq = hlist_entry(ioc->icq_list.first,
struct io_cq, ioc_node);
struct request_queue *this_q = icq->q;

if (this_q != last_q) {
if (last_q && last_q != locked_q)
spin_unlock(last_q->queue_lock);
last_q = NULL;

/* spin_trylock() always successes in UP case */
if (this_q != locked_q &&
!spin_trylock(this_q->queue_lock))
break;
last_q = this_q;
continue;
}
ioc_exit_icq(icq);
if (atomic_long_dec_and_test(&ioc->refcount)) {
spin_lock_irqsave(&ioc->lock, flags);
if (!hlist_empty(&ioc->icq_list))
schedule_work(&ioc->release_work);
spin_unlock_irqrestore(&ioc->lock, flags);
}

if (last_q && last_q != locked_q)
spin_unlock(last_q->queue_lock);

spin_unlock_irqrestore(&ioc->lock, flags);

/* if no icq is left, we're done; otherwise, kick release_work */
if (hlist_empty(&ioc->icq_list))
kmem_cache_free(iocontext_cachep, ioc);
else
schedule_work(&ioc->release_work);
}
EXPORT_SYMBOL(put_io_context);

Expand All @@ -238,7 +168,7 @@ void exit_io_context(struct task_struct *task)
task_unlock(task);

atomic_dec(&ioc->nr_tasks);
put_io_context(ioc, NULL);
put_io_context(ioc);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion block/cfq-iosched.c
Original file line number Diff line number Diff line change
Expand Up @@ -1794,7 +1794,7 @@ __cfq_slice_expired(struct cfq_data *cfqd, struct cfq_queue *cfqq,
cfqd->active_queue = NULL;

if (cfqd->active_cic) {
put_io_context(cfqd->active_cic->icq.ioc, cfqd->queue);
put_io_context(cfqd->active_cic->icq.ioc);
cfqd->active_cic = NULL;
}
}
Expand Down
2 changes: 1 addition & 1 deletion fs/ioprio.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ int set_task_ioprio(struct task_struct *task, int ioprio)
ioc = get_task_io_context(task, GFP_ATOMIC, NUMA_NO_NODE);
if (ioc) {
ioc_ioprio_changed(ioc, ioprio);
put_io_context(ioc, NULL);
put_io_context(ioc);
}

return err;
Expand Down
3 changes: 0 additions & 3 deletions include/linux/blkdev.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,6 @@ struct request_queue {
/* Throttle data */
struct throtl_data *td;
#endif
#ifdef CONFIG_LOCKDEP
int ioc_release_depth;
#endif
};

#define QUEUE_FLAG_QUEUED 1 /* uses generic tag queueing */
Expand Down
5 changes: 2 additions & 3 deletions include/linux/iocontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,15 @@ static inline struct io_context *ioc_task_link(struct io_context *ioc)

struct task_struct;
#ifdef CONFIG_BLOCK
void put_io_context(struct io_context *ioc, struct request_queue *locked_q);
void put_io_context(struct io_context *ioc);
void exit_io_context(struct task_struct *task);
struct io_context *get_task_io_context(struct task_struct *task,
gfp_t gfp_flags, int node);
void ioc_ioprio_changed(struct io_context *ioc, int ioprio);
void ioc_cgroup_changed(struct io_context *ioc);
#else
struct io_context;
static inline void put_io_context(struct io_context *ioc,
struct request_queue *locked_q) { }
static inline void put_io_context(struct io_context *ioc) { }
static inline void exit_io_context(struct task_struct *task) { }
#endif

Expand Down
2 changes: 1 addition & 1 deletion kernel/fork.c
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ static int copy_io(unsigned long clone_flags, struct task_struct *tsk)
return -ENOMEM;

new_ioc->ioprio = ioc->ioprio;
put_io_context(new_ioc, NULL);
put_io_context(new_ioc);
}
#endif
return 0;
Expand Down

0 comments on commit 11a3122

Please sign in to comment.