Hi,
Add per-task io poll context for holding HIPRI blk-mq/underlying bios
queued from bio based driver's io submission context, and reuse one bio
padding field for storing 'cookie' returned from submit_bio() for these
bios. Also explicitly end these bios in poll context by adding two
new bio flags.
In this way, we needn't to poll all underlying hw queues any more,
which is implemented in Jeffle's patches. And we can just poll hw queues
in which there is HIPRI IO queued.
Usually io submission and io poll share same context, so the added io
poll context data is just like one stack variable, and the cost for
saving bios is cheap.
Any comments are welcome.
Jeffle Xu (4):
block/mq: extract one helper function polling hw queue
block: add queue_to_disk() to get gendisk from request_queue
block: add poll_capable method to support bio-based IO polling
dm: support IO polling for bio-based dm device
Ming Lei (7):
block: add helper of blk_queue_poll
block: add one helper to free io_context
block: add helper of blk_create_io_context
block: create io poll context for submission and poll task
block: add req flag of REQ_TAG
block: add new field into 'struct bvec_iter'
block: use per-task poll context to implement bio based io poll
block/bio.c | 5 +
block/blk-core.c | 161 ++++++++++++++++++++++++++---
block/blk-ioc.c | 12 ++-
block/blk-mq.c | 189 ++++++++++++++++++++++++++++++++--
block/blk-sysfs.c | 14 ++-
block/blk.h | 45 ++++++++
drivers/md/dm-table.c | 24 +++++
drivers/md/dm.c | 14 +++
drivers/nvme/host/core.c | 2 +-
include/linux/blk_types.h | 7 ++
include/linux/blkdev.h | 4 +
include/linux/bvec.h | 9 ++
include/linux/device-mapper.h | 1 +
include/linux/iocontext.h | 2 +
include/trace/events/kyber.h | 6 +-
15 files changed, 466 insertions(+), 29 deletions(-)
--
2.29.2
Prepare for putting bio poll queue into io_context, so add one helper
for free io_context.
Signed-off-by: Ming Lei <redacted>
---
block/blk-ioc.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
There has been 3 users, and will be more, so add one such helper.
Signed-off-by: Ming Lei <redacted>
---
block/blk-core.c | 2 +-
block/blk-mq.c | 3 +--
drivers/nvme/host/core.c | 2 +-
include/linux/blkdev.h | 1 +
4 files changed, 4 insertions(+), 4 deletions(-)
Add one helper for creating io context and prepare for supporting
efficient bio based io poll.
Meantime move the code of creating io_context before checking bio's
REQ_HIPRI flag because the following patch may change to clear REQ_HIPRI
if io_context can't be created.
Signed-off-by: Ming Lei <redacted>
---
block/blk-core.c | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
Create per-task io poll context for both IO submission and poll task
if the queue is bio based and supports polling.
This io polling context includes two queues: submission queue(sq) for
storing HIPRI bio submission result(cookie) and the bio, written
by submission task and read by poll task; polling queue(pq) for holding
data moved from sq, only used in poll context for running bio polling.
Following patches will support bio poll.
Signed-off-by: Ming Lei <redacted>
---
block/blk-core.c | 59 +++++++++++++++++++++++++++++++--------
block/blk-ioc.c | 1 +
block/blk-mq.c | 14 ++++++++++
block/blk.h | 45 +++++++++++++++++++++++++++++
include/linux/iocontext.h | 2 ++
5 files changed, 109 insertions(+), 12 deletions(-)
@@ -357,4 +358,48 @@ int bio_add_hw_page(struct request_queue *q, struct bio *bio,structpage*page,unsignedintlen,unsignedintoffset,unsignedintmax_sectors,bool*same_page);+#define BLK_BIO_POLL_SQ_SZ 64U+#define BLK_BIO_POLL_PQ_SZ (BLK_BIO_POLL_SQ_SZ * 2)++/* result of submit_bio */+structblk_bio_poll_data{+structbio*bio;+};++/* Per-task bio poll queue data and attached to io context */+structblk_bio_poll_ctx{+spinlock_tlock;+/*+*SubmissionqueueforstoringHIPRIbiosubmissionresult,written+*bysubmissiontaskandreadbypolltask+*/+DECLARE_KFIFO(sq,structblk_bio_poll_data,BLK_BIO_POLL_SQ_SZ);++/* Holding poll data moved from sq, only used in poll task */+structmutexpq_lock;+structblk_bio_poll_datapq[BLK_BIO_POLL_PQ_SZ];+};++voidbio_poll_ctx_alloc(structio_context*ioc);++staticinlinevoidblk_create_io_context(structrequest_queue*q,+boolneed_poll_ctx)+{+structio_context*ioc;++/*+*Variousblockpartswant%current->io_context,soallocateitup+*frontratherthandealingwithlotsofpaintoallocateitonly+*whereneeded.Thismayfailandtheblocklayerknowshowtolive+*withit.+*/+if(unlikely(!current->io_context))+create_task_io_context(current,GFP_ATOMIC,q->node);++ioc=current->io_context;+if(need_poll_ctx&&unlikely(ioc&&!ioc->data))+bio_poll_ctx_alloc(ioc);+}++#endif /* BLK_INTERNAL_H */
From: Jeffle Xu <jefflexu@linux.alibaba.com>
Extract the logic of polling one hw queue and related statistics
handling out as the helper function.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
Signed-off-by: Ming Lei <redacted>
---
block/blk-mq.c | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
There is a hole at the end of 'struct bvec_iter', so put a new field
here and we can save cookie returned from submit_bio() here for
supporting bio based polling.
This way can avoid to extend bio unnecessarily.
Signed-off-by: Ming Lei <redacted>
---
include/linux/bvec.h | 9 +++++++++
1 file changed, 9 insertions(+)
Add one req flag REQ_TAG which will be used in the following patch for
supporting bio based IO polling.
Exactly this flag can help us to do:
1) request flag is cloned in bio_fast_clone(), so if we mark one FS bio
as REQ_TAG, all bios cloned from this FS bio will be marked as REQ_TAG.
2)create per-task io polling context if the bio based queue supports polling
and the submitted bio is HIPRI. This per-task io polling context will be
created during submit_bio() before marking this HIPRI bio as REQ_TAG. Then
we can avoid to create such io polling context if one cloned bio with REQ_TAG
is submitted from another kernel context.
3) for supporting bio based io polling, we need to poll IOs from all
underlying queues of bio device/driver, this way help us to recognize which
IOs need to polled in bio based style, which will be implemented in next
patch.
Signed-off-by: Ming Lei <redacted>
---
block/blk-core.c | 29 +++++++++++++++++++++++++++--
include/linux/blk_types.h | 4 ++++
2 files changed, 31 insertions(+), 2 deletions(-)
@@ -394,6 +394,9 @@ enum req_flag_bits {__REQ_HIPRI,+/* for marking IOs originated from same FS bio in same context */+__REQ_TAG,+/* for driver use */__REQ_DRV,__REQ_SWAP,/* swapping request. */
From: Jeffle Xu <jefflexu@linux.alibaba.com>
Sometimes we need to get the corresponding gendisk from request_queue.
It is preferred that block drivers store private data in
gendisk->private_data rather than request_queue->queuedata, e.g. see:
commit c4a59c4e5db3 ("dm: stop using ->queuedata").
So if only request_queue is given, we need to get its corresponding
gendisk to get the private data stored in that gendisk.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
Reviewed-by: Mike Snitzer <redacted>
---
include/linux/blkdev.h | 2 ++
include/trace/events/kyber.h | 6 +++---
2 files changed, 5 insertions(+), 3 deletions(-)
From: Jeffle Xu <jefflexu@linux.alibaba.com>
This method can be used to check if bio-based device supports IO polling
or not. For mq devices, checking for hw queue in polling mode is
adequate, while the sanity check shall be implementation specific for
bio-based devices. For example, dm device needs to check if all
underlying devices are capable of IO polling.
Though bio-based device may have done the sanity check during the
device initialization phase, cacheing the result of this sanity check
(such as by cacheing in the queue_flags) may not work. Because for dm
devices, users could change the state of the underlying devices through
'/sys/block/<dev>/io_poll', bypassing the dm device above. In this case,
the cached result of the very beginning sanity check could be
out-of-date. Thus the sanity check needs to be done every time 'io_poll'
is to be modified.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
---
block/blk-sysfs.c | 14 +++++++++++---
include/linux/blkdev.h | 1 +
2 files changed, 12 insertions(+), 3 deletions(-)
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}++staticvoidblk_bio_poll_post_submit(structbio*bio,blk_qc_tcookie)+{+bio->bi_iter.bi_private_data=cookie;+}+staticnoinline_for_stackboolsubmit_bio_checks(structbio*bio){structblock_device*bdev=bio->bi_bdev;
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio)*bio_list_on_stack[1]containsbiosthatweresubmittedbeforethecurrent*->submit_bio_bio,butthathaven'tbeenprocessedyet.*/-staticblk_qc_t__submit_bio_noacct(structbio*bio)+staticblk_qc_t__submit_bio_noacct_int(structbio*bio,structio_context*ioc){structbio_listbio_list_on_stack[2];blk_qc_tret=BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio)bio_list_on_stack[1]=bio_list_on_stack[0];bio_list_init(&bio_list_on_stack[0]);-ret=__submit_bio(bio);+if(ioc&&queue_is_mq(q)&&+(bio->bi_opf&(REQ_HIPRI|REQ_TAG))){+boolqueued=blk_bio_poll_prep_submit(ioc,bio);++ret=__submit_bio(bio);+if(queued)+blk_bio_poll_post_submit(bio,ret);+}else{+ret=__submit_bio(bio);+}/**Sortnewbiosintothoseforalowerlevelandthoseforthe
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio)returnret;}+staticinlineblk_qc_t__submit_bio_noacct_poll(structbio*bio,+structio_context*ioc)+{+structblk_bio_poll_ctx*pc=ioc->data;+intentries=kfifo_len(&pc->sq);++__submit_bio_noacct_int(bio,ioc);++/* bio submissions queued to per-task poll context */+if(kfifo_len(&pc->sq)>entries)+returncurrent->pid;++/* swapper's pid is 0, but it can't submit poll IO for us */+return0;+}++staticinlineblk_qc_t__submit_bio_noacct(structbio*bio)+{+structio_context*ioc=current->io_context;++if(ioc&&ioc->data&&(bio->bi_opf&REQ_HIPRI))+return__submit_bio_noacct_poll(bio,ioc);++return__submit_bio_noacct_int(bio,NULL);+}++staticblk_qc_t__submit_bio_noacct_mq(structbio*bio){structbio_listbio_list[2]={};
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}++staticint__blk_bio_poll_io(structrequest_queue*q,+structblk_bio_poll_ctx*submit_ctx,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+inti;++/*+*MoveIOsubmissionresultfromsubmissionqueueinsubmission+*contexttopollqueueofpollcontext.+*+*Theremaybemorethanonereadersonpollqueueofthesame+*submissioncontext,sohavetolockhere.+*/+spin_lock(&submit_ctx->lock);+for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+if(poll_data[i].bio==NULL&&+!kfifo_get(&submit_ctx->sq,&poll_data[i]))+break;+}+spin_unlock(&submit_ctx->lock);++returnblk_bio_poll_and_end_io(q,poll_ctx);+}++staticintblk_bio_poll_io(structrequest_queue*q,+structio_context*submit_ioc,+structio_context*poll_ioc)+{+structblk_bio_poll_ctx*submit_ctx=submit_ioc->data;+structblk_bio_poll_ctx*poll_ctx=poll_ioc->data;+intret;++if(unlikely(atomic_read(&poll_ioc->nr_tasks)>1)){+mutex_lock(&poll_ctx->pq_lock);+ret=__blk_bio_poll_io(q,submit_ctx,poll_ctx);+mutex_unlock(&poll_ctx->pq_lock);+}else{+ret=__blk_bio_poll_io(q,submit_ctx,poll_ctx);+}+returnret;+}++staticboolblk_bio_ioc_valid(structtask_struct*t)+{+if(!t)+returnfalse;++if(!t->io_context)+returnfalse;++if(!t->io_context->data)+returnfalse;++returntrue;+}++staticint__blk_bio_poll(structrequest_queue*q,blk_qc_tcookie)+{+structio_context*poll_ioc=current->io_context;+pid_tpid;+structtask_struct*submit_task;+intret;++pid=(pid_t)cookie;++/* io poll often share io submission context */+if(likely(current->pid==pid&&blk_bio_ioc_valid(current)))+returnblk_bio_poll_io(q,poll_ioc,poll_ioc);++submit_task=find_get_task_by_vpid(pid);+if(likely(blk_bio_ioc_valid(submit_task)))+ret=blk_bio_poll_io(q,submit_task->io_context,+poll_ioc);+else+ret=0;++put_task_struct(submit_task);++returnret;+}+staticintblk_bio_poll(structrequest_queue*q,blk_qc_tcookie,boolspin){+longstate;++/* no need to poll */+if(cookie==0)+return0;+/**Createpollqueueforstoringpollbioanditscookiefrom*submissionqueue*/blk_create_io_context(q,true);+state=current->state;+do{+intret;++ret=__blk_bio_poll(q,cookie);+if(ret>0){+__set_current_state(TASK_RUNNING);+returnret;+}++if(signal_pending_state(state,current))+__set_current_state(TASK_RUNNING);++if(current->state==TASK_RUNNING)+return1;+if(ret<0||!spin)+break;+cpu_relax();+}while(!need_resched());++__set_current_state(TASK_RUNNING);return0;}
@@ -304,6 +304,9 @@ enum {BIO_CGROUP_ACCT,/* has been accounted to a cgroup */BIO_TRACKED,/* set if bio goes through the rq_qos path */BIO_REMAPPED,+BIO_END_BY_POLL,/* end by blk_bio_poll() explicitly */+/* set when bio can be ended, used for bio with BIO_END_BY_POLL */+BIO_DONE,BIO_FLAG_LAST};
From: Jeffle Xu <jefflexu@linux.alibaba.com>
IO polling is enabled when all underlying target devices are capable
of IO polling. The sanity check supports the stacked device model, in
which one dm device may be build upon another dm device. In this case,
the mapped device will check if the underlying dm target device
supports IO polling.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
Signed-off-by: Ming Lei <redacted>
---
drivers/md/dm-table.c | 24 ++++++++++++++++++++++++
drivers/md/dm.c | 14 ++++++++++++++
include/linux/device-mapper.h | 1 +
3 files changed, 39 insertions(+)
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted hunk
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio) * bio_list_on_stack[1] contains bios that were submitted before the current * ->submit_bio_bio, but that haven't been processed yet. */-static blk_qc_t __submit_bio_noacct(struct bio *bio)+static blk_qc_t __submit_bio_noacct_int(struct bio *bio, struct io_context *ioc) { struct bio_list bio_list_on_stack[2]; blk_qc_t ret = BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) bio_list_on_stack[1] = bio_list_on_stack[0]; bio_list_init(&bio_list_on_stack[0]);- ret = __submit_bio(bio);+ if (ioc && queue_is_mq(q) &&+ (bio->bi_opf & (REQ_HIPRI | REQ_TAG))) {+ bool queued = blk_bio_poll_prep_submit(ioc, bio);++ ret = __submit_bio(bio);+ if (queued)+ blk_bio_poll_post_submit(bio, ret);+ } else {+ ret = __submit_bio(bio);+ } /* * Sort new bios into those for a lower level and those for the
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) return ret; }+static inline blk_qc_t __submit_bio_noacct_poll(struct bio *bio,+ struct io_context *ioc)+{+ struct blk_bio_poll_ctx *pc = ioc->data;+ int entries = kfifo_len(&pc->sq);++ __submit_bio_noacct_int(bio, ioc);++ /* bio submissions queued to per-task poll context */+ if (kfifo_len(&pc->sq) > entries)+ return current->pid;++ /* swapper's pid is 0, but it can't submit poll IO for us */+ return 0;+}++static inline blk_qc_t __submit_bio_noacct(struct bio *bio)+{+ struct io_context *ioc = current->io_context;++ if (ioc && ioc->data && (bio->bi_opf & REQ_HIPRI))+ return __submit_bio_noacct_poll(bio, ioc);++ return __submit_bio_noacct_int(bio, NULL);+}++ static blk_qc_t __submit_bio_noacct_mq(struct bio *bio) { struct bio_list bio_list[2] = { };
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}
When there are multiple threads polling, saying thread A and thread B,
then there's one bio which should be polled by thread A (the pid is
passed to thread A), while it's actually completed by thread B. In this
case, when the bio is completed by thread B, the bio is not really
completed and one extra blk_poll() still needs to be called.
quoted hunk
+
+static int __blk_bio_poll_io(struct request_queue *q,
+ struct blk_bio_poll_ctx *submit_ctx,
+ struct blk_bio_poll_ctx *poll_ctx)
+{
+ struct blk_bio_poll_data *poll_data = &poll_ctx->pq[0];
+ int i;
+
+ /*
+ * Move IO submission result from submission queue in submission
+ * context to poll queue of poll context.
+ *
+ * There may be more than one readers on poll queue of the same
+ * submission context, so have to lock here.
+ */
+ spin_lock(&submit_ctx->lock);
+ for (i = 0; i < BLK_BIO_POLL_PQ_SZ; i++) {
+ if (poll_data[i].bio == NULL &&
+ !kfifo_get(&submit_ctx->sq, &poll_data[i]))
+ break;
+ }
+ spin_unlock(&submit_ctx->lock);
+
+ return blk_bio_poll_and_end_io(q, poll_ctx);
+}
+
+static int blk_bio_poll_io(struct request_queue *q,
+ struct io_context *submit_ioc,
+ struct io_context *poll_ioc)
+{
+ struct blk_bio_poll_ctx *submit_ctx = submit_ioc->data;
+ struct blk_bio_poll_ctx *poll_ctx = poll_ioc->data;
+ int ret;
+
+ if (unlikely(atomic_read(&poll_ioc->nr_tasks) > 1)) {
+ mutex_lock(&poll_ctx->pq_lock);
+ ret = __blk_bio_poll_io(q, submit_ctx, poll_ctx);
+ mutex_unlock(&poll_ctx->pq_lock);
+ } else {
+ ret = __blk_bio_poll_io(q, submit_ctx, poll_ctx);
+ }
+ return ret;
+}
+
+static bool blk_bio_ioc_valid(struct task_struct *t)
+{
+ if (!t)
+ return false;
+
+ if (!t->io_context)
+ return false;
+
+ if (!t->io_context->data)
+ return false;
+
+ return true;
+}
+
+static int __blk_bio_poll(struct request_queue *q, blk_qc_t cookie)
+{
+ struct io_context *poll_ioc = current->io_context;
+ pid_t pid;
+ struct task_struct *submit_task;
+ int ret;
+
+ pid = (pid_t)cookie;
+
+ /* io poll often share io submission context */
+ if (likely(current->pid == pid && blk_bio_ioc_valid(current)))
+ return blk_bio_poll_io(q, poll_ioc, poll_ioc);
+
+ submit_task = find_get_task_by_vpid(pid);
+ if (likely(blk_bio_ioc_valid(submit_task)))
+ ret = blk_bio_poll_io(q, submit_task->io_context,
+ poll_ioc);
+ else
+ ret = 0;
+
+ put_task_struct(submit_task);
+
+ return ret;
+}
+
static int blk_bio_poll(struct request_queue *q, blk_qc_t cookie, bool spin)
{
+ long state;
+
+ /* no need to poll */
+ if (cookie == 0)
+ return 0;
+
/*
* Create poll queue for storing poll bio and its cookie from
* submission queue
*/
blk_create_io_context(q, true);
+ state = current->state;
+ do {
+ int ret;
+
+ ret = __blk_bio_poll(q, cookie);
+ if (ret > 0) {
+ __set_current_state(TASK_RUNNING);
+ return ret;
+ }
+
+ if (signal_pending_state(state, current))
+ __set_current_state(TASK_RUNNING);
+
+ if (current->state == TASK_RUNNING)
+ return 1;
+ if (ret < 0 || !spin)
+ break;
+ cpu_relax();
+ } while (!need_resched());
+
+ __set_current_state(TASK_RUNNING);
return 0;
}
@@ -3893,7 +4047,7 @@ int blk_poll(struct request_queue *q, blk_qc_t cookie, bool spin) struct blk_mq_hw_ctx *hctx; long state;- if (!blk_qc_t_valid(cookie) || !blk_queue_poll(q))+ if (!blk_queue_poll(q) || (queue_is_mq(q) && !blk_qc_t_valid(cookie))) return 0; if (current->plug)
@@ -304,6 +304,9 @@ enum {BIO_CGROUP_ACCT,/* has been accounted to a cgroup */BIO_TRACKED,/* set if bio goes through the rq_qos path */BIO_REMAPPED,+BIO_END_BY_POLL,/* end by blk_bio_poll() explicitly */+/* set when bio can be ended, used for bio with BIO_END_BY_POLL */+BIO_DONE,BIO_FLAG_LAST};
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio) * bio_list_on_stack[1] contains bios that were submitted before the current * ->submit_bio_bio, but that haven't been processed yet. */-static blk_qc_t __submit_bio_noacct(struct bio *bio)+static blk_qc_t __submit_bio_noacct_int(struct bio *bio, struct io_context *ioc) { struct bio_list bio_list_on_stack[2]; blk_qc_t ret = BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) bio_list_on_stack[1] = bio_list_on_stack[0]; bio_list_init(&bio_list_on_stack[0]);- ret = __submit_bio(bio);+ if (ioc && queue_is_mq(q) &&+ (bio->bi_opf & (REQ_HIPRI | REQ_TAG))) {+ bool queued = blk_bio_poll_prep_submit(ioc, bio);++ ret = __submit_bio(bio);+ if (queued)+ blk_bio_poll_post_submit(bio, ret);+ } else {+ ret = __submit_bio(bio);+ } /* * Sort new bios into those for a lower level and those for the
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) return ret; }+static inline blk_qc_t __submit_bio_noacct_poll(struct bio *bio,+ struct io_context *ioc)+{+ struct blk_bio_poll_ctx *pc = ioc->data;+ int entries = kfifo_len(&pc->sq);++ __submit_bio_noacct_int(bio, ioc);++ /* bio submissions queued to per-task poll context */+ if (kfifo_len(&pc->sq) > entries)+ return current->pid;++ /* swapper's pid is 0, but it can't submit poll IO for us */+ return 0;+}++static inline blk_qc_t __submit_bio_noacct(struct bio *bio)+{+ struct io_context *ioc = current->io_context;++ if (ioc && ioc->data && (bio->bi_opf & REQ_HIPRI))+ return __submit_bio_noacct_poll(bio, ioc);++ return __submit_bio_noacct_int(bio, NULL);+}++ static blk_qc_t __submit_bio_noacct_mq(struct bio *bio) { struct bio_list bio_list[2] = { };
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}
When there are multiple threads polling, saying thread A and thread B,
then there's one bio which should be polled by thread A (the pid is
passed to thread A), while it's actually completed by thread B. In this
case, when the bio is completed by thread B, the bio is not really
completed and one extra blk_poll() still needs to be called.
When this happens, the dm bio can't be completed, and the associated
kiocb can't be completed too, io_uring or other poll code context will
keep calling blk_poll() by passing thread A's pid until this dm bio is
done, since the dm bio is submitted from thread A.
Thanks,
Ming
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
It seems that the queue depth of kfifo will affect the performance as I
did a fast test.
Test Result:
BLK_BIO_POLL_SQ_SZ | iodepth | IOPS
------------------ | ------- | ----
64 | 128 | 301k (IRQ) -> 340k (iopoll)
64 | 16 | 304k (IRQ) -> 392k (iopoll)
128 | 128 | 204k (IRQ) -> 317k (iopoll)
256 | 128 | 241k (IRQ) -> 391k (iopoll)
It seems that BLK_BIO_POLL_SQ_SZ need to be increased accordingly when
iodepth is quite large. But I don't know why the performance in IRQ mode
decreases when BLK_BIO_POLL_SQ_SZ is increased.
Test Environment:
nvme.poll_queues = 1,
dmsetup create testdev --table '0 2097152 linear /dev/nvme0n1 0',
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio) * bio_list_on_stack[1] contains bios that were submitted before the current * ->submit_bio_bio, but that haven't been processed yet. */-static blk_qc_t __submit_bio_noacct(struct bio *bio)+static blk_qc_t __submit_bio_noacct_int(struct bio *bio, struct io_context *ioc) { struct bio_list bio_list_on_stack[2]; blk_qc_t ret = BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) bio_list_on_stack[1] = bio_list_on_stack[0]; bio_list_init(&bio_list_on_stack[0]);- ret = __submit_bio(bio);+ if (ioc && queue_is_mq(q) &&+ (bio->bi_opf & (REQ_HIPRI | REQ_TAG))) {+ bool queued = blk_bio_poll_prep_submit(ioc, bio);++ ret = __submit_bio(bio);+ if (queued)+ blk_bio_poll_post_submit(bio, ret);+ } else {+ ret = __submit_bio(bio);+ } /* * Sort new bios into those for a lower level and those for the
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) return ret; }+static inline blk_qc_t __submit_bio_noacct_poll(struct bio *bio,+ struct io_context *ioc)+{+ struct blk_bio_poll_ctx *pc = ioc->data;+ int entries = kfifo_len(&pc->sq);++ __submit_bio_noacct_int(bio, ioc);++ /* bio submissions queued to per-task poll context */+ if (kfifo_len(&pc->sq) > entries)+ return current->pid;++ /* swapper's pid is 0, but it can't submit poll IO for us */+ return 0;+}++static inline blk_qc_t __submit_bio_noacct(struct bio *bio)+{+ struct io_context *ioc = current->io_context;++ if (ioc && ioc->data && (bio->bi_opf & REQ_HIPRI))+ return __submit_bio_noacct_poll(bio, ioc);++ return __submit_bio_noacct_int(bio, NULL);+}++ static blk_qc_t __submit_bio_noacct_mq(struct bio *bio) { struct bio_list bio_list[2] = { };
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}
When there are multiple threads polling, saying thread A and thread B,
then there's one bio which should be polled by thread A (the pid is
passed to thread A), while it's actually completed by thread B. In this
case, when the bio is completed by thread B, the bio is not really
completed and one extra blk_poll() still needs to be called.
When this happens, the dm bio can't be completed, and the associated
kiocb can't be completed too, io_uring or other poll code context will
keep calling blk_poll() by passing thread A's pid until this dm bio is
done, since the dm bio is submitted from thread A.
Thanks,
Ming
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio) * bio_list_on_stack[1] contains bios that were submitted before the current * ->submit_bio_bio, but that haven't been processed yet. */-static blk_qc_t __submit_bio_noacct(struct bio *bio)+static blk_qc_t __submit_bio_noacct_int(struct bio *bio, struct io_context *ioc) { struct bio_list bio_list_on_stack[2]; blk_qc_t ret = BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) bio_list_on_stack[1] = bio_list_on_stack[0]; bio_list_init(&bio_list_on_stack[0]);- ret = __submit_bio(bio);+ if (ioc && queue_is_mq(q) &&+ (bio->bi_opf & (REQ_HIPRI | REQ_TAG))) {+ bool queued = blk_bio_poll_prep_submit(ioc, bio);++ ret = __submit_bio(bio);+ if (queued)+ blk_bio_poll_post_submit(bio, ret);+ } else {+ ret = __submit_bio(bio);+ } /* * Sort new bios into those for a lower level and those for the
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) return ret; }+static inline blk_qc_t __submit_bio_noacct_poll(struct bio *bio,+ struct io_context *ioc)+{+ struct blk_bio_poll_ctx *pc = ioc->data;+ int entries = kfifo_len(&pc->sq);++ __submit_bio_noacct_int(bio, ioc);++ /* bio submissions queued to per-task poll context */+ if (kfifo_len(&pc->sq) > entries)+ return current->pid;++ /* swapper's pid is 0, but it can't submit poll IO for us */+ return 0;+}++static inline blk_qc_t __submit_bio_noacct(struct bio *bio)+{+ struct io_context *ioc = current->io_context;++ if (ioc && ioc->data && (bio->bi_opf & REQ_HIPRI))+ return __submit_bio_noacct_poll(bio, ioc);++ return __submit_bio_noacct_int(bio, NULL);+}++ static blk_qc_t __submit_bio_noacct_mq(struct bio *bio) { struct bio_list bio_list[2] = { };
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}
When there are multiple threads polling, saying thread A and thread B,
then there's one bio which should be polled by thread A (the pid is
passed to thread A), while it's actually completed by thread B. In this
case, when the bio is completed by thread B, the bio is not really
completed and one extra blk_poll() still needs to be called.
When this happens, the dm bio can't be completed, and the associated
kiocb can't be completed too, io_uring or other poll code context will
keep calling blk_poll() by passing thread A's pid until this dm bio is
done, since the dm bio is submitted from thread A.
This will affect the multi-thread polling performance. I tested
dm-stripe, in which every bio will be split and enqueued into all
underlying devices, and thus amplify the interference between multiple
threads.
Test Result:
IOPS: 332k (IRQ) -> 363k (iopoll), aka ~10% performance gain
Test Environment:
nvme.poll_queues = 3
BLK_BIO_POLL_SQ_SZ = 128
dmsetup create testdev --table "0 629145600 striped 3 8 /dev/nvme0n1 0
/dev/nvme1n1 0 /dev/nvme4n1 0"
On Tue, Mar 16, 2021 at 04:52:36PM +0800, JeffleXu wrote:
On 3/16/21 3:17 PM, Ming Lei wrote:
quoted
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
It seems that the queue depth of kfifo will affect the performance as I
did a fast test.
Test Result:
BLK_BIO_POLL_SQ_SZ | iodepth | IOPS
------------------ | ------- | ----
64 | 128 | 301k (IRQ) -> 340k (iopoll)
64 | 16 | 304k (IRQ) -> 392k (iopoll)
128 | 128 | 204k (IRQ) -> 317k (iopoll)
256 | 128 | 241k (IRQ) -> 391k (iopoll)
It seems that BLK_BIO_POLL_SQ_SZ need to be increased accordingly when
iodepth is quite large. But I don't know why the performance in IRQ mode
decreases when BLK_BIO_POLL_SQ_SZ is increased.
This patchset is supposed to not affect IRQ mode because HIPRI isn't set
at IRQ mode. Or you mean '--hipri' & io_uring is setup but setting
nvme.poll_queues as 0 at your 'IRQ' mode test?
Thanks for starting to run performance test, and so far I just run test
in KVM, not start performance test yet.
thanks,
Ming
On Tue, Mar 16, 2021 at 07:00:49PM +0800, JeffleXu wrote:
On 3/16/21 3:17 PM, Ming Lei wrote:
quoted
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio) * bio_list_on_stack[1] contains bios that were submitted before the current * ->submit_bio_bio, but that haven't been processed yet. */-static blk_qc_t __submit_bio_noacct(struct bio *bio)+static blk_qc_t __submit_bio_noacct_int(struct bio *bio, struct io_context *ioc) { struct bio_list bio_list_on_stack[2]; blk_qc_t ret = BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) bio_list_on_stack[1] = bio_list_on_stack[0]; bio_list_init(&bio_list_on_stack[0]);- ret = __submit_bio(bio);+ if (ioc && queue_is_mq(q) &&+ (bio->bi_opf & (REQ_HIPRI | REQ_TAG))) {+ bool queued = blk_bio_poll_prep_submit(ioc, bio);++ ret = __submit_bio(bio);+ if (queued)+ blk_bio_poll_post_submit(bio, ret);+ } else {+ ret = __submit_bio(bio);+ } /* * Sort new bios into those for a lower level and those for the
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) return ret; }+static inline blk_qc_t __submit_bio_noacct_poll(struct bio *bio,+ struct io_context *ioc)+{+ struct blk_bio_poll_ctx *pc = ioc->data;+ int entries = kfifo_len(&pc->sq);++ __submit_bio_noacct_int(bio, ioc);++ /* bio submissions queued to per-task poll context */+ if (kfifo_len(&pc->sq) > entries)+ return current->pid;++ /* swapper's pid is 0, but it can't submit poll IO for us */+ return 0;+}++static inline blk_qc_t __submit_bio_noacct(struct bio *bio)+{+ struct io_context *ioc = current->io_context;++ if (ioc && ioc->data && (bio->bi_opf & REQ_HIPRI))+ return __submit_bio_noacct_poll(bio, ioc);++ return __submit_bio_noacct_int(bio, NULL);+}++ static blk_qc_t __submit_bio_noacct_mq(struct bio *bio) { struct bio_list bio_list[2] = { };
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}
When there are multiple threads polling, saying thread A and thread B,
then there's one bio which should be polled by thread A (the pid is
passed to thread A), while it's actually completed by thread B. In this
case, when the bio is completed by thread B, the bio is not really
completed and one extra blk_poll() still needs to be called.
When this happens, the dm bio can't be completed, and the associated
kiocb can't be completed too, io_uring or other poll code context will
keep calling blk_poll() by passing thread A's pid until this dm bio is
done, since the dm bio is submitted from thread A.
This will affect the multi-thread polling performance. I tested
dm-stripe, in which every bio will be split and enqueued into all
underlying devices, and thus amplify the interference between multiple
threads.
Test Result:
IOPS: 332k (IRQ) -> 363k (iopoll), aka ~10% performance gain
Test Environment:
nvme.poll_queues = 3
BLK_BIO_POLL_SQ_SZ = 128
dmsetup create testdev --table "0 629145600 striped 3 8 /dev/nvme0n1 0
/dev/nvme1n1 0 /dev/nvme4n1 0"
If numjobs is 1, there can't be the queue interference issue because
there is only one submission job.
If numjobs is 3 and nvme.poll_queues is 3, there are two cases:
1) each job is assigned with different hw queue, so no queue
interference
2) two jobs share one same hw queue, and there is queue interference
The behavior is decided by scheduler since the queue mapping between
cpu vs. poll queue is fixed.
You can compare the above two cases by passing different 'cpus_allowed'
to each job in fio.
It depends if cpu of 14,16,84 is mapped to same hw poll queue or
not.
If all 3 cpus are mapped to same hw poll queue, there will be lock
contention in submission path, see nvme_submit_cmd(), and the hw queue
data is concurrent polled in 3 cpus too.
Thanks,
Ming
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio) * bio_list_on_stack[1] contains bios that were submitted before the current * ->submit_bio_bio, but that haven't been processed yet. */-static blk_qc_t __submit_bio_noacct(struct bio *bio)+static blk_qc_t __submit_bio_noacct_int(struct bio *bio, struct io_context *ioc) { struct bio_list bio_list_on_stack[2]; blk_qc_t ret = BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) bio_list_on_stack[1] = bio_list_on_stack[0]; bio_list_init(&bio_list_on_stack[0]);- ret = __submit_bio(bio);+ if (ioc && queue_is_mq(q) &&+ (bio->bi_opf & (REQ_HIPRI | REQ_TAG))) {+ bool queued = blk_bio_poll_prep_submit(ioc, bio);++ ret = __submit_bio(bio);+ if (queued)+ blk_bio_poll_post_submit(bio, ret);+ } else {+ ret = __submit_bio(bio);+ } /* * Sort new bios into those for a lower level and those for the
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) return ret; }+static inline blk_qc_t __submit_bio_noacct_poll(struct bio *bio,+ struct io_context *ioc)+{+ struct blk_bio_poll_ctx *pc = ioc->data;+ int entries = kfifo_len(&pc->sq);++ __submit_bio_noacct_int(bio, ioc);++ /* bio submissions queued to per-task poll context */+ if (kfifo_len(&pc->sq) > entries)+ return current->pid;++ /* swapper's pid is 0, but it can't submit poll IO for us */+ return 0;+}++static inline blk_qc_t __submit_bio_noacct(struct bio *bio)+{+ struct io_context *ioc = current->io_context;++ if (ioc && ioc->data && (bio->bi_opf & REQ_HIPRI))+ return __submit_bio_noacct_poll(bio, ioc);++ return __submit_bio_noacct_int(bio, NULL);+}++ static blk_qc_t __submit_bio_noacct_mq(struct bio *bio) { struct bio_list bio_list[2] = { };
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}
When there are multiple threads polling, saying thread A and thread B,
then there's one bio which should be polled by thread A (the pid is
passed to thread A), while it's actually completed by thread B. In this
case, when the bio is completed by thread B, the bio is not really
completed and one extra blk_poll() still needs to be called.
When this happens, the dm bio can't be completed, and the associated
kiocb can't be completed too, io_uring or other poll code context will
keep calling blk_poll() by passing thread A's pid until this dm bio is
done, since the dm bio is submitted from thread A.
This will affect the multi-thread polling performance. I tested
dm-stripe, in which every bio will be split and enqueued into all
underlying devices, and thus amplify the interference between multiple
threads.
Test Result:
IOPS: 332k (IRQ) -> 363k (iopoll), aka ~10% performance gain
Sorry this performance drop is not related to this bio refcount issue
here. Still it's due to the limited kfifo size.
I did another through test on another machine (aarch64 with more nvme
disks).
- Without mentioned specifically, the configuration is 'iodepth=128,
kfifo queue depth =128'.
- The number before '->' indicates the IOPS in IRQ mode, i.e.,
'hipri=0', while the number after '->' indicates the IOPS in polling
mode, i.e., 'hipri=1'.
Some hints:
1. When configured as 'iodepth=128, kfifo queue depth =128', dm-stripe
doesn't perform well in polling mode. It's because it's more likely that
the original bio will be split into split bios in dm-stripe, and thus
kfifo will be more likely used up in this case. So the size of kfifo
need to be tuned according to iodepth and the IO load. Thus exporting
the size of kfifo as a sysfs entry may be need in the following patch.
2. It indicates a performance drop of my patch in IRQ mode, compared to
the original 5.12-rc1. I doubt maybe it's due to extra code mixed in
blk-core, such as __submit_bio_noacct()...
On Tue, Mar 16, 2021 at 04:52:36PM +0800, JeffleXu wrote:
quoted
On 3/16/21 3:17 PM, Ming Lei wrote:
quoted
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
It seems that the queue depth of kfifo will affect the performance as I
did a fast test.
Test Result:
BLK_BIO_POLL_SQ_SZ | iodepth | IOPS
------------------ | ------- | ----
64 | 128 | 301k (IRQ) -> 340k (iopoll)
64 | 16 | 304k (IRQ) -> 392k (iopoll)
128 | 128 | 204k (IRQ) -> 317k (iopoll)
256 | 128 | 241k (IRQ) -> 391k (iopoll)
It seems that BLK_BIO_POLL_SQ_SZ need to be increased accordingly when
iodepth is quite large. But I don't know why the performance in IRQ mode
decreases when BLK_BIO_POLL_SQ_SZ is increased.
This patchset is supposed to not affect IRQ mode because HIPRI isn't set
at IRQ mode. Or you mean '--hipri' & io_uring is setup but setting
nvme.poll_queues as 0 at your 'IRQ' mode test?
Thanks for starting to run performance test, and so far I just run test
in KVM, not start performance test yet.
'IRQ' means 'hipri=0' of fio configuration.
The above performance test was performed on one x86 machine with one
single nvme disk. I did the test on another aarch64 machine with more
nvme disks, showing that this performance drop didn't occure...
Please see my reply in another thread for detailed test results.
--
Thanks,
Jeffle
On Wed, Mar 17, 2021 at 11:53:12AM +0800, JeffleXu wrote:
On 3/17/21 10:54 AM, Ming Lei wrote:
quoted
On Tue, Mar 16, 2021 at 04:52:36PM +0800, JeffleXu wrote:
quoted
On 3/16/21 3:17 PM, Ming Lei wrote:
quoted
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
It seems that the queue depth of kfifo will affect the performance as I
did a fast test.
Test Result:
BLK_BIO_POLL_SQ_SZ | iodepth | IOPS
------------------ | ------- | ----
64 | 128 | 301k (IRQ) -> 340k (iopoll)
64 | 16 | 304k (IRQ) -> 392k (iopoll)
128 | 128 | 204k (IRQ) -> 317k (iopoll)
256 | 128 | 241k (IRQ) -> 391k (iopoll)
It seems that BLK_BIO_POLL_SQ_SZ need to be increased accordingly when
iodepth is quite large. But I don't know why the performance in IRQ mode
decreases when BLK_BIO_POLL_SQ_SZ is increased.
This patchset is supposed to not affect IRQ mode because HIPRI isn't set
at IRQ mode. Or you mean '--hipri' & io_uring is setup but setting
nvme.poll_queues as 0 at your 'IRQ' mode test?
Thanks for starting to run performance test, and so far I just run test
in KVM, not start performance test yet.
'IRQ' means 'hipri=0' of fio configuration.
'hipri=0' isn't supposed to be affected by this patchset.
thanks,
Ming
On Wed, Mar 17, 2021 at 11:49:00AM +0800, JeffleXu wrote:
On 3/16/21 7:00 PM, JeffleXu wrote:
quoted
On 3/16/21 3:17 PM, Ming Lei wrote:
quoted
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio) * bio_list_on_stack[1] contains bios that were submitted before the current * ->submit_bio_bio, but that haven't been processed yet. */-static blk_qc_t __submit_bio_noacct(struct bio *bio)+static blk_qc_t __submit_bio_noacct_int(struct bio *bio, struct io_context *ioc) { struct bio_list bio_list_on_stack[2]; blk_qc_t ret = BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) bio_list_on_stack[1] = bio_list_on_stack[0]; bio_list_init(&bio_list_on_stack[0]);- ret = __submit_bio(bio);+ if (ioc && queue_is_mq(q) &&+ (bio->bi_opf & (REQ_HIPRI | REQ_TAG))) {+ bool queued = blk_bio_poll_prep_submit(ioc, bio);++ ret = __submit_bio(bio);+ if (queued)+ blk_bio_poll_post_submit(bio, ret);+ } else {+ ret = __submit_bio(bio);+ } /* * Sort new bios into those for a lower level and those for the
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) return ret; }+static inline blk_qc_t __submit_bio_noacct_poll(struct bio *bio,+ struct io_context *ioc)+{+ struct blk_bio_poll_ctx *pc = ioc->data;+ int entries = kfifo_len(&pc->sq);++ __submit_bio_noacct_int(bio, ioc);++ /* bio submissions queued to per-task poll context */+ if (kfifo_len(&pc->sq) > entries)+ return current->pid;++ /* swapper's pid is 0, but it can't submit poll IO for us */+ return 0;+}++static inline blk_qc_t __submit_bio_noacct(struct bio *bio)+{+ struct io_context *ioc = current->io_context;++ if (ioc && ioc->data && (bio->bi_opf & REQ_HIPRI))+ return __submit_bio_noacct_poll(bio, ioc);++ return __submit_bio_noacct_int(bio, NULL);+}++ static blk_qc_t __submit_bio_noacct_mq(struct bio *bio) { struct bio_list bio_list[2] = { };
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}
When there are multiple threads polling, saying thread A and thread B,
then there's one bio which should be polled by thread A (the pid is
passed to thread A), while it's actually completed by thread B. In this
case, when the bio is completed by thread B, the bio is not really
completed and one extra blk_poll() still needs to be called.
When this happens, the dm bio can't be completed, and the associated
kiocb can't be completed too, io_uring or other poll code context will
keep calling blk_poll() by passing thread A's pid until this dm bio is
done, since the dm bio is submitted from thread A.
This will affect the multi-thread polling performance. I tested
dm-stripe, in which every bio will be split and enqueued into all
underlying devices, and thus amplify the interference between multiple
threads.
Test Result:
IOPS: 332k (IRQ) -> 363k (iopoll), aka ~10% performance gain
Sorry this performance drop is not related to this bio refcount issue
here. Still it's due to the limited kfifo size.
I did another through test on another machine (aarch64 with more nvme
disks).
- Without mentioned specifically, the configuration is 'iodepth=128,
kfifo queue depth =128'.
- The number before '->' indicates the IOPS in IRQ mode, i.e.,
'hipri=0', while the number after '->' indicates the IOPS in polling
mode, i.e., 'hipri=1'.
Some hints:
1. When configured as 'iodepth=128, kfifo queue depth =128', dm-stripe
doesn't perform well in polling mode. It's because it's more likely that
the original bio will be split into split bios in dm-stripe, and thus
kfifo will be more likely used up in this case. So the size of kfifo
need to be tuned according to iodepth and the IO load. Thus exporting
the size of kfifo as a sysfs entry may be need in the following patch.
Yeah, I think your analysis is right.
On simple approach to address the scalability issue is to put submitted
bio into a per-task list, however one new field(8bytes) needs to be
added to bio, or something like below:
1) disable hipri bio merge, then we can reuse bio->bi_next
or
2) track request instead of bio, then it should be easier to get one
field from 'struct request' for such purpose, such as 'ipi_list'.
Seems 2) is possible, will try it and see if the approach is really doable.
thanks,
Ming
From: Mike Snitzer <hidden> Date: 2021-03-18 14:52:38
On Wed, Mar 17 2021 at 3:19am -0400,
Ming Lei [off-list ref] wrote:
On Wed, Mar 17, 2021 at 11:49:00AM +0800, JeffleXu wrote:
quoted
On 3/16/21 7:00 PM, JeffleXu wrote:
quoted
On 3/16/21 3:17 PM, Ming Lei wrote:
quoted
On Tue, Mar 16, 2021 at 02:46:08PM +0800, JeffleXu wrote:
quoted
It is a giant progress to gather all split bios that need to be polled
in a per-task queue. Still some comments below.
On 3/16/21 11:15 AM, Ming Lei wrote:
quoted
Currently bio based IO poll needs to poll all hw queue blindly, this way
is very inefficient, and the big reason is that we can't pass bio
submission result to io poll task.
In IO submission context, store associated underlying bios into the
submission queue and save 'cookie' poll data in bio->bi_iter.bi_private_data,
and return current->pid to caller of submit_bio() for any DM or bio based
driver's IO, which is submitted from FS.
In IO poll context, the passed cookie tells us the PID of submission
context, and we can find the bio from that submission context. Moving
bio from submission queue to poll queue of the poll context, and keep
polling until these bios are ended. Remove bio from poll queue if the
bio is ended. Add BIO_DONE and BIO_END_BY_POLL for such purpose.
Usually submission shares context with io poll. The per-task poll context
is just like stack variable, and it is cheap to move data between the two
per-task queues.
Signed-off-by: Ming Lei <redacted>
---
block/bio.c | 5 ++
block/blk-core.c | 74 +++++++++++++++++-
block/blk-mq.c | 156 +++++++++++++++++++++++++++++++++++++-
include/linux/blk_types.h | 3 +
4 files changed, 235 insertions(+), 3 deletions(-)
@@ -1402,6 +1402,11 @@ static inline bool bio_remaining_done(struct bio *bio)**/voidbio_endio(structbio*bio){+/* BIO_END_BY_POLL has to be set before calling submit_bio */+if(bio_flagged(bio,BIO_END_BY_POLL)){+bio_set_flag(bio,BIO_DONE);+return;+}again:if(!bio_remaining_done(bio))return;
@@ -854,6 +854,40 @@ static inline void blk_bio_poll_preprocess(struct request_queue *q,bio->bi_opf|=REQ_TAG;}+staticboolblk_bio_poll_prep_submit(structio_context*ioc,structbio*bio)+{+structblk_bio_poll_datadata={+.bio=bio,+};+structblk_bio_poll_ctx*pc=ioc->data;+unsignedintqueued;++/* lock is required if there is more than one writer */+if(unlikely(atomic_read(&ioc->nr_tasks)>1)){+spin_lock(&pc->lock);+queued=kfifo_put(&pc->sq,data);+spin_unlock(&pc->lock);+}else{+queued=kfifo_put(&pc->sq,data);+}++/*+*Nowthebioisaddedper-taskfifo,markitasEND_BY_POLL,+*sowecansavecookieintothisbioaftersubmit_bio().+*/+if(queued)+bio_set_flag(bio,BIO_END_BY_POLL);+else+bio->bi_opf&=~(REQ_HIPRI|REQ_TAG);++returnqueued;+}
The size of kfifo is limited, and it seems that once the sq of kfifio is
full, REQ_HIPRI flag is cleared and the corresponding bio is actually
enqueued into the default hw queue, which is IRQ driven.
Yeah, this patch starts with 64 queue depth, and we can increase it to
128, which should cover most of cases.
@@ -1008,7 +1042,7 @@ static blk_qc_t __submit_bio(struct bio *bio) * bio_list_on_stack[1] contains bios that were submitted before the current * ->submit_bio_bio, but that haven't been processed yet. */-static blk_qc_t __submit_bio_noacct(struct bio *bio)+static blk_qc_t __submit_bio_noacct_int(struct bio *bio, struct io_context *ioc) { struct bio_list bio_list_on_stack[2]; blk_qc_t ret = BLK_QC_T_NONE;
@@ -1031,7 +1065,16 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) bio_list_on_stack[1] = bio_list_on_stack[0]; bio_list_init(&bio_list_on_stack[0]);- ret = __submit_bio(bio);+ if (ioc && queue_is_mq(q) &&+ (bio->bi_opf & (REQ_HIPRI | REQ_TAG))) {+ bool queued = blk_bio_poll_prep_submit(ioc, bio);++ ret = __submit_bio(bio);+ if (queued)+ blk_bio_poll_post_submit(bio, ret);+ } else {+ ret = __submit_bio(bio);+ } /* * Sort new bios into those for a lower level and those for the
@@ -1057,6 +1100,33 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio) return ret; }+static inline blk_qc_t __submit_bio_noacct_poll(struct bio *bio,+ struct io_context *ioc)+{+ struct blk_bio_poll_ctx *pc = ioc->data;+ int entries = kfifo_len(&pc->sq);++ __submit_bio_noacct_int(bio, ioc);++ /* bio submissions queued to per-task poll context */+ if (kfifo_len(&pc->sq) > entries)+ return current->pid;++ /* swapper's pid is 0, but it can't submit poll IO for us */+ return 0;+}++static inline blk_qc_t __submit_bio_noacct(struct bio *bio)+{+ struct io_context *ioc = current->io_context;++ if (ioc && ioc->data && (bio->bi_opf & REQ_HIPRI))+ return __submit_bio_noacct_poll(bio, ioc);++ return __submit_bio_noacct_int(bio, NULL);+}++ static blk_qc_t __submit_bio_noacct_mq(struct bio *bio) { struct bio_list bio_list[2] = { };
@@ -3865,14 +3865,168 @@ static inline int blk_mq_poll_hctx(struct request_queue *q,returnret;}+staticblk_qc_tbio_get_poll_cookie(structbio*bio)+{+returnbio->bi_iter.bi_private_data;+}++staticintblk_mq_poll_io(structbio*bio)+{+structrequest_queue*q=bio->bi_bdev->bd_disk->queue;+blk_qc_tcookie=bio_get_poll_cookie(bio);+intret=0;++if(!bio_flagged(bio,BIO_DONE)&&blk_qc_t_valid(cookie)){+structblk_mq_hw_ctx*hctx=+q->queue_hw_ctx[blk_qc_t_to_queue_num(cookie)];++ret+=blk_mq_poll_hctx(q,hctx);+}+returnret;+}++staticintblk_bio_poll_and_end_io(structrequest_queue*q,+structblk_bio_poll_ctx*poll_ctx)+{+structblk_bio_poll_data*poll_data=&poll_ctx->pq[0];+intret=0;+inti;++for(i=0;i<BLK_BIO_POLL_PQ_SZ;i++){+structbio*bio=poll_data[i].bio;++if(!bio)+continue;++ret+=blk_mq_poll_io(bio);+if(bio_flagged(bio,BIO_DONE)){+poll_data[i].bio=NULL;++/* clear BIO_END_BY_POLL and end me really */+bio_clear_flag(bio,BIO_END_BY_POLL);+bio_endio(bio);+}+}+returnret;+}
When there are multiple threads polling, saying thread A and thread B,
then there's one bio which should be polled by thread A (the pid is
passed to thread A), while it's actually completed by thread B. In this
case, when the bio is completed by thread B, the bio is not really
completed and one extra blk_poll() still needs to be called.
When this happens, the dm bio can't be completed, and the associated
kiocb can't be completed too, io_uring or other poll code context will
keep calling blk_poll() by passing thread A's pid until this dm bio is
done, since the dm bio is submitted from thread A.
This will affect the multi-thread polling performance. I tested
dm-stripe, in which every bio will be split and enqueued into all
underlying devices, and thus amplify the interference between multiple
threads.
Test Result:
IOPS: 332k (IRQ) -> 363k (iopoll), aka ~10% performance gain
Sorry this performance drop is not related to this bio refcount issue
here. Still it's due to the limited kfifo size.
I did another through test on another machine (aarch64 with more nvme
disks).
- Without mentioned specifically, the configuration is 'iodepth=128,
kfifo queue depth =128'.
- The number before '->' indicates the IOPS in IRQ mode, i.e.,
'hipri=0', while the number after '->' indicates the IOPS in polling
mode, i.e., 'hipri=1'.
Some hints:
1. When configured as 'iodepth=128, kfifo queue depth =128', dm-stripe
doesn't perform well in polling mode. It's because it's more likely that
the original bio will be split into split bios in dm-stripe, and thus
kfifo will be more likely used up in this case. So the size of kfifo
need to be tuned according to iodepth and the IO load. Thus exporting
the size of kfifo as a sysfs entry may be need in the following patch.
Yeah, I think your analysis is right.
On simple approach to address the scalability issue is to put submitted
bio into a per-task list, however one new field(8bytes) needs to be
added to bio, or something like below:
1) disable hipri bio merge, then we can reuse bio->bi_next
or
2) track request instead of bio, then it should be easier to get one
field from 'struct request' for such purpose, such as 'ipi_list'.
Seems 2) is possible, will try it and see if the approach is really doable.
Not (yet) seeing how making tracking (either requests or bios) per-task
will help. Though tracking in terms of requests reduces the amount of
polling (due to hopeful merging, at least in sequential IO case) it
doesn't _really_ make the task cookie -> polled_object mapping any more
efficient for the single thread test-case Jeffle ran: the fan-out of
bio-splits for _random_ IO issued to 3-way dm-stripe is inherently messy
to track.
Basically I'm just wondering where you see your per-task request-based
tracking approach helping? Multithreaded sequential workloads?
Feels like the poll cookie being a task id is just extremely coarse.
Doesn't really allow polling to be done more precisely... what am I
missing?
Thanks,
Mike