[Changes since v3]
- newly add patch 7 and patch 11, as a new optimization improving
performance of multiple polling processes. Now performance of multiple
polling processes can be as scalable as single polling process (~30%).
Refer to the following [Performance] chapter for more details.
[Intention]
Bio-based polling (e.g., for dm/md devices) is one indispensable part of
high performance IO stack. As far as I know, dm (e.g., dm-stripe) is
widely used in database, splicing several NVMe disks as one whole disk,
in hope of achieving better performance. With this patch set, io_uring
could be used upon dm devices.
[Optimizations]
Basically, there are three paths for IO polling.
1. fastpath (patch 9/10)
The polling routine will go into this path when bio submitted to dm
device is not split.
In this case, there will be only one bio submitted to only one polling
hw queue of one underlying mq device, and thus we don't need to track
all split bios or iterate through all polling hw queues. The pointer to
the polling hw queue the bio submitted to is returned here as the
returned cookie. In this case, the polling routine will call
mq_ops->poll() directly with the hw queue converted from the input
cookie.
- One process reading dm-linear (mapping to three underlying NVMe devices,
with one polling hw queue per NVMe device).
(ioengine=io_uring, iodepth=128, numjobs=1, rw=randread, sqthread_poll=0
direct=1, bs=4k)
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
---------------- | --------------- | -------------------- | ----
with patchset | 212k | 284k | ~32%
- Three processes reading dm-linear (mapping to three underlying NVMe
devices, with one polling hw queue per NVMe device).
(ioengine=io_uring, iodepth=128, numjobs=3, rw=randread, sqthread_poll=0
direct=1, bs=4k)
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
---------------- | --------------- | -------------------- | ----
with patchset | 615k | 735k | ~16%
- Three processes reading dm-linear (mapping to three underlying NVMe
devices, with three polling hw queues per NVMe device), with every
process pinned to one CPU and mapped to one exclusive hw queue.
(ioengine=io_uring, iodepth=128, numjobs=3, rw=randread, sqthread_poll=0
direct=1, bs=4k)
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
---------------- | --------------- | -------------------- | ----
with patchset | 631k | 833k | ~32%
2. sub-fastpath (patch 7/11)
The polling routine will go into this path when bio submitted to dm
device gets split and enqueued into multiple hw queues, while the IO
submission process has not been migrated to another CPU.
In this case, the IO submission routine will return the CPU number on
which the IO submission happened as the returned cookie, while the
polling routine will only iterate and poll on hw queues that this CPU
number maps, instead of iterating *all* hw queues.
This optimization can dramatically reduce cache ping-pong and thus
improve the polling performance, when multiple hw queues in polling mode
per device could be reserved when there are multiple polling processes.
- Three processes reading dm-stripe (mapping to three underlying NVMe
devices, with three polling hw queues per NVMe device), with every
process pinned to one CPU and mapped to one exclusive hw queue.
(ioengine=io_uring, iodepth=128, numjobs=3, rw=randread, sqthread_poll=0
direct=1, bs=12k(4k for every NVMe device))
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
---------------- | --------------- | -------------------- | ----
with patchset | 307k | 412k | ~34%
3. default path
It will fall back to iterating all hw queues in polling mode, once bio
submitted to dm device gets split and enqueued into multiple hw queues,
and the IO process has ever been migrated to another CPU during the IO
submission phase.
[Remained Issue]
It has been mentioned in patch 4 that, users could change the state of
the underlying devices through '/sys/block/<dev>/io_poll', bypassing
the dm device above. Thus it can cause a situation where QUEUE_FLAG_POLL
is still set for the request_queue of dm device, while one of the
underlying mq device may has cleared this flag.
In this case, it will pass the 'test_bit(QUEUE_FLAG_POLL, &q->queue_flags)'
check in blk_poll(), while the input cookie may actually points to a hw
queue in IRQ mode since patch 11. Thus for this hw queue (in IRQ mode),
the bio-based polling routine will handle this hw queue acquiring
'spin_lock(&nvmeq->cq_poll_lock)' (refer
drivers/nvme/host/pci.c:nvme_poll), which is not adequate since this hw
queue may also be accessed in IRQ context. In other words,
spin_lock_irq() should be used here.
I have not come up one simple way to fix it. I don't want to do sanity
check (e.g., the type of the hw queue is HCTX_TYPE_POLL or not) in the
IO path (submit_bio()/blk_poll()), i.e., fast path.
We'd better fix it in the control path, i.e., dm could be aware of the
change when attribute (e.g., support io_poll or not) of one of the
underlying devices changed at runtime.
[Changes since v2]
Patchset v2 caches all hw queues (in polling mode) of underlying mq
devices in dm layer. The polling routine actually iterates through all
these cached hw queues.
However, mq may change the queue mapping at runtime (e.g., NVMe RESET
command), thus the cached hw queues in dm layer may be out-of-date. Thus
patchset v3 falls back to the implementation of the very first RFC
version, in which the mq layer needs to export one interface iterating
all polling hw queues (patch 5), and the bio-based polling routine just
calls this interface to iterate all polling hw queues.
Besides, several new optimization is proposed.
- patch 1,2,7
same as v2, untouched
- patch 3
Considering advice from Christoph Hellwig, while refactoring blk_poll(),
split mq and bio-based polling routine from the very beginning. Now
blk_poll() is just a simple entry. blk_bio_poll() is simply copied from
blk_mq_poll(), while the loop structure is some sort of duplication
though.
- patch 4
This patch is newly added to support turning on/off polling through
'/sys/block/<dev>/queue/io_poll' dynamiclly for bio-based devices.
Patchset v2 implemented this functionality by added one new queue flag,
which is not preferred since the queue flag resource is quite short of
nowadays.
- patch 5
This patch is newly added, preparing for the following bio-based
polling. The following bio-based polling will call this helper function,
accounting on the corresponding hw queue.
- patch 6
It's from the very first RFC version, preparing for the following
bio-based polling.
- patch 8
One fixing patch needed by the following bio-based polling. It's
actually a v2 of [1]. I had sent the v2 singly in-reply-to [1], though
it has not been visible on the mailing list maybe due to the delay.
- patch 9
It's from the very first RFC version.
- patch 10
This patch is newly added. Patchset v2 had ever proposed one
optimization that, skipping the **busy** hw queues during the iteration
phase. Back upon that time, one flag of 'atomic_t' is specifically
maintained in dm layer, representing if the corresponding hw queue is
busy or not. The idea is inherited, while the implementation changes.
Now @nvmeq->cq_poll_lock is used directly here, no need for extra flag
anymore.
This optimization can significantly reduce the competition for one hw
queue between multiple polling instances. Following statistics is the
test result when 3 threads concurrently randread (bs=4k, direct=1) one
dm-linear device, which is built upon 3 nvme devices, with one polling
hw queue per nvme device.
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
----------- | --------------- | -------------------- | ----
without opt | 318k | 256k | ~-20%
with opt | 314k | 354k | ~13%
- patch 11
This is another newly added optimizatin for bio-based polling.
One intuitive insight is that, when the original bio submitted to dm
device doesn't get split, then the bio gets enqueued into only one hw
queue of one of the underlying mq devices. In this case, we no longer
need to track all split bios, and one cookie (for the only split bio)
is enough. It is implemented by returning the pointer to the
corresponding hw queue in this case.
It should be safe by directly returning the pointer to the hw queue,
since 'struct blk_mq_hw_ctx' won't be freed during the whole lifetime of
'struct request_queue'. Even when the number of hw queues may decrease
when NVMe RESET happens, the 'struct request_queue' structure of decreased
hw queues won't be freed, instead it's buffered into
&q->unused_hctx_list list.
Though this optimization seems quite intuitive, the performance test
shows that it does no benefit nor harm to the performance, while 3
threads concurrently randreading (bs=4k, direct=1) one dm-linear
device, which is built upon 3 nvme devices, with one polling hw queue
per nvme device.
I'm not sure why it doesn't work, maybe because the number of devices,
or the depth of the devcice stack is to low in my test case?
changes since v1:
- patch 1,2,4 is the same as v1 and have already been reviewed
- patch 3 is refactored a bit on the basis of suggestions from
Mike Snitzer.
- patch 5 is newly added and introduces one new queue flag
representing if the queue is capable of IO polling. This mainly
simplifies the logic in queue_poll_store().
- patch 6 implements the core mechanism supporting IO polling.
The sanity check checking if the dm device supports IO polling is
also folded into this patch, and the queue flag will be cleared if
it doesn't support, in case of table reloading.
Jeffle Xu (12):
block: move definition of blk_qc_t to types.h
block: add queue_to_disk() to get gendisk from request_queue
block: add poll method to support bio-based IO polling
block: add poll_capable method to support bio-based IO polling
blk-mq: extract one helper function polling hw queue
blk-mq: add iterator for polling hw queues
blk-mq: add one helper function getting hw queue
dm: always return BLK_QC_T_NONE for bio-based device
nvme/pci: don't wait for locked polling queue
block: fastpath for bio-based polling
block: sub-fastpath for bio-based polling
dm: support IO polling for bio-based dm device
block/blk-core.c | 112 +++++++++++++++++++++++++++++++++-
block/blk-mq.c | 37 ++++-------
block/blk-sysfs.c | 14 ++++-
drivers/md/dm-table.c | 26 ++++++++
drivers/md/dm.c | 102 ++++++++++++++++++++++++++-----
drivers/nvme/host/pci.c | 4 +-
include/linux/blk-mq.h | 23 +++++++
include/linux/blk_types.h | 66 +++++++++++++++++++-
include/linux/blkdev.h | 4 ++
include/linux/device-mapper.h | 1 +
include/linux/fs.h | 2 +-
include/linux/types.h | 3 +
include/trace/events/kyber.h | 6 +-
13 files changed, 350 insertions(+), 50 deletions(-)
--
2.27.0
So that kiocb.ki_cookie can be defined as blk_qc_t, which will enforce
the encapsulation.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Mike Snitzer <redacted>
---
include/linux/blk_types.h | 2 +-
include/linux/fs.h | 2 +-
include/linux/types.h | 3 +++
3 files changed, 5 insertions(+), 2 deletions(-)
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(-)
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 the returned cookie of bio-based device is not used at all.
Cookie of bio-based device will be refactored in the following patch.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
Reviewed-by: Mike Snitzer <redacted>
---
drivers/md/dm.c | 26 ++++++++++----------------
1 file changed, 10 insertions(+), 16 deletions(-)
@@ -1252,14 +1252,13 @@ void dm_accept_partial_bio(struct bio *bio, unsigned n_sectors)}EXPORT_SYMBOL_GPL(dm_accept_partial_bio);-staticblk_qc_t__map_bio(structdm_target_io*tio)+staticvoid__map_bio(structdm_target_io*tio){intr;sector_tsector;structbio*clone=&tio->clone;structdm_io*io=tio->io;structdm_target*ti=tio->ti;-blk_qc_tret=BLK_QC_T_NONE;clone->bi_end_io=clone_endio;
@@ -1278,7 +1277,7 @@ static blk_qc_t __map_bio(struct dm_target_io *tio)caseDM_MAPIO_REMAPPED:/* the bio has been remapped so dispatch it */trace_block_bio_remap(clone,bio_dev(io->orig_bio),sector);-ret=submit_bio_noacct(clone);+submit_bio_noacct(clone);break;caseDM_MAPIO_KILL:free_tio(tio);
@@ -1621,13 +1617,11 @@ static blk_qc_t __split_and_process_bio(struct mapped_device *md,/* drop the extra reference count */dec_pending(ci.io,errno_to_blk_status(error));-returnret;}staticblk_qc_tdm_submit_bio(structbio*bio){structmapped_device*md=bio->bi_disk->private_data;-blk_qc_tret=BLK_QC_T_NONE;intsrcu_idx;structdm_table*map;
@@ -1657,10 +1651,10 @@ static blk_qc_t dm_submit_bio(struct bio *bio)if(is_abnormal_io(bio))blk_queue_split(&bio);-ret=__split_and_process_bio(md,map,bio);+__split_and_process_bio(md,map,bio);out:dm_put_live_table(md,srcu_idx);-returnret;+returnBLK_QC_T_NONE;}/*-----------------------------------------------------------------
Offer one fastpath for bio-based polling when bio submitted to dm
device is not split.
In this case, there will be only one bio submitted to only one polling
hw queue of one underlying mq device, and thus we don't need to track
all split bios or iterate through all polling hw queues. The pointer to
the polling hw queue the bio submitted to is returned here as the
returned cookie. In this case, the polling routine will call
mq_ops->poll() directly with the hw queue converted from the input
cookie.
If the original bio submitted to dm device is split to multiple bios and
thus submitted to multiple polling hw queues, the polling routine will
fall back to iterating all hw queues (in polling mode) of all underlying
mq devices.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
---
block/blk-core.c | 38 ++++++++++++++++++++++++++++++++++++--
include/linux/blk_types.h | 8 ++++++++
include/linux/types.h | 2 +-
3 files changed, 45 insertions(+), 3 deletions(-)
@@ -956,11 +956,19 @@ static blk_qc_t __submit_bio(struct bio *bio)*bio_list_on_stack[0]containsbiossubmittedbythecurrent->submit_bio.*bio_list_on_stack[1]containsbiosthatweresubmittedbeforethecurrent*->submit_bio_bio,butthathaven'tbeenprocessedyet.+*+*Return:+*-BLK_QC_T_NONE,noneedforIOpolling.+*-BLK_QC_T_BIO_POLL_ALL,@biogetssplitandenqueuedintomultihwqueues.+*-Otherwise,@bioisnotsplit,returningthepointertothecorresponding+*hwqueuethatthebioenqueuedintoasthereturnedcookie.*/staticblk_qc_t__submit_bio_noacct(structbio*bio){structbio_listbio_list_on_stack[2];blk_qc_tret=BLK_QC_T_NONE;+structrequest_queue*top_q=bio->bi_disk->queue;+boolpoll_on=test_bit(QUEUE_FLAG_POLL,&top_q->queue_flags);BUG_ON(bio->bi_next);
@@ -968,6 +976,7 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio)current->bio_list=bio_list_on_stack;do{+blk_qc_tcookie;structrequest_queue*q=bio->bi_disk->queue;structbio_listlower,same;
@@ -980,7 +989,23 @@ 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);+cookie=__submit_bio(bio);++if(poll_on&&blk_qc_t_valid(cookie)){+unsignedintqueue_num=blk_qc_t_to_queue_num(cookie);+structblk_mq_hw_ctx*hctx=q->queue_hw_ctx[queue_num];++cookie=(blk_qc_t)hctx;++if(!blk_qc_t_valid(ret)){+/* set initial value */+ret=cookie;+}elseif(ret!=cookie){+/* bio gets split and enqueued to multi hctxs */+ret=BLK_QC_T_BIO_POLL_ALL;+poll_on=false;+}+}/**Sortnewbiosintothoseforalowerlevelandthoseforthe
@@ -1003,6 +1028,7 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio)}while((bio=bio_list_pop(&bio_list_on_stack[0])));current->bio_list=NULL;+returnret;}
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>
---
drivers/md/dm-table.c | 26 ++++++++++++
drivers/md/dm.c | 78 +++++++++++++++++++++++++++++++++++
include/linux/device-mapper.h | 1 +
3 files changed, 105 insertions(+)
Offer one sub-fastpath for bio-based polling when bio submitted to dm
device gets split and enqueued into multiple hw queues, while the IO
submission process has not been migrated to another CPU.
In this case, the IO submission routine will return the CPU number on
which the IO submission happened as the returned cookie, while the
polling routine will only iterate and poll on hw queues that this CPU
number maps, instead of iterating *all* hw queues.
This optimization can dramatically reduce cache ping-pong and thus
improve the polling performance, when multiple hw queues in polling mode
per device could be reserved when there are multiple polling processes.
It will fall back to iterating all hw queues in polling mode, once the
process has ever been migrated to another CPU during the IO submission
phase.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
---
block/blk-core.c | 24 ++++++++++++++--
include/linux/blk_types.h | 60 +++++++++++++++++++++++++++++++++++++--
2 files changed, 79 insertions(+), 5 deletions(-)
@@ -959,7 +959,10 @@ static blk_qc_t __submit_bio(struct bio *bio)**Return:*-BLK_QC_T_NONE,noneedforIOpolling.-*-BLK_QC_T_BIO_POLL_ALL,@biogetssplitandenqueuedintomultihwqueues.+*-When@biogetssplitandenqueuedintomultihwqueues,returnthe+*correspondingCPUnumberwithBLK_QC_T_BIO_POLL_CPUflagsetwhenthe+*currentprocesshasnotbeenmigratedtoanotherCPU,+*BLK_QC_T_BIO_POLL_ALLotherwise.*-Otherwise,@bioisnotsplit,returningthepointertothecorresponding*hwqueuethatthebioenqueuedintoasthereturnedcookie.*/
@@ -968,13 +971,17 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio)structbio_listbio_list_on_stack[2];blk_qc_tret=BLK_QC_T_NONE;structrequest_queue*top_q=bio->bi_disk->queue;-boolpoll_on=test_bit(QUEUE_FLAG_POLL,&top_q->queue_flags);+boolorig_poll_on,poll_on;+u64old_nr_migrations;BUG_ON(bio->bi_next);bio_list_init(&bio_list_on_stack[0]);current->bio_list=bio_list_on_stack;+orig_poll_on=poll_on=test_bit(QUEUE_FLAG_POLL,&top_q->queue_flags);+old_nr_migrations=READ_ONCE(current->se.nr_migrations);+do{blk_qc_tcookie;structrequest_queue*q=bio->bi_disk->queue;
@@ -1002,7 +1009,7 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio)ret=cookie;}elseif(ret!=cookie){/* bio gets split and enqueued to multi hctxs */-ret=BLK_QC_T_BIO_POLL_ALL;+ret=blk_qc_t_get();poll_on=false;}}
@@ -1029,6 +1036,17 @@ static blk_qc_t __submit_bio_noacct(struct bio *bio)current->bio_list=NULL;+/*+*Forcaseswhenbiogetssplitandenqueuedintomultihctxs,return+*thecorrespondingCPUnumberwhencurrentprocesshasnotbeen+*migratedtoanotherCPU.ReturnBLK_QC_T_BIO_POLL_ALLotherwise,+*fallingbacktoiteratingandpollingonallhwqueues,sincesplit+*biosaresubmittedtodifferentCPUsinthiscase.+*/+if(orig_poll_on!=poll_on&&+old_nr_migrations!=READ_ONCE(current->se.nr_migrations))+ret=BLK_QC_T_BIO_POLL_ALL;+returnret;}
@@ -557,12 +557,68 @@ static inline bool blk_qc_t_is_internal(blk_qc_t cookie)return(cookie&BLK_QC_T_INTERNAL)!=0;}-/* Macros for blk_qc_t used for bio-based polling */+/*+*Macrosforblk_qc_tusedforbio-basedpolling.+*+*1.When@bioisnotsplit,thereturnedcookieisactuallythepointertothe+*correspondinghwqueuethatthebioisenqueuedinto,thusthelowtwo+*bitscouldbereusedasotherflags.+*+-----------------------------------------++*|structblk_mq_hw_ctx*|0|0|+*+-----------------------------------------++*+*2.When@biogetssplitandenqueuedintomultihwqueues,andcurrent+*processhasnotbeenmigratedtoanotherCPU,thereturnedcookieactually+*storesthecorrespondingCPUnumberonwhichtheIOsubmissionhappened,+*AlsowithBLK_QC_T_BIO_POLL_CPUflagset.+*Inthiscase,bio-basedpollingcouldonlyiterateandpollonhwqueues+*thatthisCPUnumbermaps.+*+-----------------------------------------++*|CPU||1||+*+-----------------------------------------++*^+*BLK_QC_T_BIO_POLL_CPU+*+*3.When@biogetssplitandenqueuedintomultihwqueues,andcurrent+*processhaseverbeenmigratedtoanotherCPU,thereturnedcookieisjust+*BLK_QC_T_BIO_POLL_ALLflag.+*Inthiscase,bio-basedpollingshoulditerateandpollonallhwqueues+*inpollingmode.+*+-----------------------------------------++*|||1|+*+-----------------------------------------++*^+*BLK_QC_T_BIO_POLL_ALL+*+*4.Otherwise,returnBLK_QC_T_NONEasthecookie.+*NoneedforIOpollinginthiscase.+*+-----------------------------------------++*|BLK_QC_T_NONE|+*+-----------------------------------------++*/++/* The low two bits of cookie for bio-based polling are reused for flags. */#define BLK_QC_T_BIO_POLL_ALL 1U+#define BLK_QC_T_BIO_POLL_CPU 2Ustaticinlineboolblk_qc_t_is_poll_multi(blk_qc_tcookie){-returncookie&BLK_QC_T_BIO_POLL_ALL;+returncookie&(BLK_QC_T_BIO_POLL_ALL|BLK_QC_T_BIO_POLL_CPU);+}++staticinlineboolblk_qc_t_is_poll_cpu(blk_qc_tcookie)+{+returncookie&BLK_QC_T_BIO_POLL_CPU;+}++staticinlineblk_qc_tblk_qc_t_get(void)+{+return(raw_smp_processor_id()<<BLK_QC_T_SHIFT)|BLK_QC_T_BIO_POLL_CPU;+}++staticinlineintblk_qc_t_to_cpu(blk_qc_tcookie)+{+returncookie>>BLK_QC_T_SHIFT;}structblk_rq_stat{
Add one helper function getting hw queue mapping to specific CPU, and of
specific type.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
---
block/blk-mq.c | 10 ++++++++++
include/linux/blk-mq.h | 2 ++
2 files changed, 12 insertions(+)
There's no sense waiting for the hw queue when it currently has been
locked by another polling instance. The polling instance currently
occupying the hw queue will help reap the completion events.
It shall be safe to surrender the hw queue, as long as we could reapply
for polling later. For Synchronous polling, blk_poll() will reapply for
polling, since @spin is always True in this case. While For asynchronous
polling, i.e. io_uring itself will reapply for polling when the previous
polling returns 0.
Besides, it shall do no harm to the polling performance of mq devices.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
---
drivers/nvme/host/pci.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
->poll_fn was introduced in commit ea435e1b9392 ("block: add a poll_fn
callback to struct request_queue") to support bio-based queues such as
nvme multipath, but was later removed in commit 529262d56dbe ("block:
remove ->poll_fn").
Given commit c62b37d96b6e ("block: move ->make_request_fn to struct
block_device_operations") restore the possibility of bio-based IO
polling support by adding an ->poll method to gendisk->fops.
Make blk_mq_poll() specific to blk-mq, while blk_bio_poll() is
originally a copy from blk_mq_poll(), and is specific to bio-based
polling. Currently hybrid polling is not supported by bio-based polling.
Signed-off-by: Jeffle Xu <jefflexu@linux.alibaba.com>
---
block/blk-core.c | 58 ++++++++++++++++++++++++++++++++++++++++++
block/blk-mq.c | 22 +---------------
include/linux/blk-mq.h | 1 +
include/linux/blkdev.h | 1 +
4 files changed, 61 insertions(+), 21 deletions(-)
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>
---
block/blk-mq.c | 5 +----
include/linux/blk-mq.h | 13 +++++++++++++
2 files changed, 14 insertions(+), 4 deletions(-)
[Changes since v3]
- newly add patch 7 and patch 11, as a new optimization improving
performance of multiple polling processes. Now performance of multiple
polling processes can be as scalable as single polling process (~30%).
Refer to the following [Performance] chapter for more details.
Hi Mike, would please evaluate this new version patch set? I think this
mechanism is near maturity, since multi-thread performance is as
scalable as single-thread (~30%) now.
Thanks
Jeffle
[Intention]
Bio-based polling (e.g., for dm/md devices) is one indispensable part of
high performance IO stack. As far as I know, dm (e.g., dm-stripe) is
widely used in database, splicing several NVMe disks as one whole disk,
in hope of achieving better performance. With this patch set, io_uring
could be used upon dm devices.
[Optimizations]
Basically, there are three paths for IO polling.
1. fastpath (patch 9/10)
The polling routine will go into this path when bio submitted to dm
device is not split.
In this case, there will be only one bio submitted to only one polling
hw queue of one underlying mq device, and thus we don't need to track
all split bios or iterate through all polling hw queues. The pointer to
the polling hw queue the bio submitted to is returned here as the
returned cookie. In this case, the polling routine will call
mq_ops->poll() directly with the hw queue converted from the input
cookie.
- One process reading dm-linear (mapping to three underlying NVMe devices,
with one polling hw queue per NVMe device).
(ioengine=io_uring, iodepth=128, numjobs=1, rw=randread, sqthread_poll=0
direct=1, bs=4k)
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
---------------- | --------------- | -------------------- | ----
with patchset | 212k | 284k | ~32%
- Three processes reading dm-linear (mapping to three underlying NVMe
devices, with one polling hw queue per NVMe device).
(ioengine=io_uring, iodepth=128, numjobs=3, rw=randread, sqthread_poll=0
direct=1, bs=4k)
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
---------------- | --------------- | -------------------- | ----
with patchset | 615k | 735k | ~16%
- Three processes reading dm-linear (mapping to three underlying NVMe
devices, with three polling hw queues per NVMe device), with every
process pinned to one CPU and mapped to one exclusive hw queue.
(ioengine=io_uring, iodepth=128, numjobs=3, rw=randread, sqthread_poll=0
direct=1, bs=4k)
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
---------------- | --------------- | -------------------- | ----
with patchset | 631k | 833k | ~32%
2. sub-fastpath (patch 7/11)
The polling routine will go into this path when bio submitted to dm
device gets split and enqueued into multiple hw queues, while the IO
submission process has not been migrated to another CPU.
In this case, the IO submission routine will return the CPU number on
which the IO submission happened as the returned cookie, while the
polling routine will only iterate and poll on hw queues that this CPU
number maps, instead of iterating *all* hw queues.
This optimization can dramatically reduce cache ping-pong and thus
improve the polling performance, when multiple hw queues in polling mode
per device could be reserved when there are multiple polling processes.
- Three processes reading dm-stripe (mapping to three underlying NVMe
devices, with three polling hw queues per NVMe device), with every
process pinned to one CPU and mapped to one exclusive hw queue.
(ioengine=io_uring, iodepth=128, numjobs=3, rw=randread, sqthread_poll=0
direct=1, bs=12k(4k for every NVMe device))
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
---------------- | --------------- | -------------------- | ----
with patchset | 307k | 412k | ~34%
3. default path
It will fall back to iterating all hw queues in polling mode, once bio
submitted to dm device gets split and enqueued into multiple hw queues,
and the IO process has ever been migrated to another CPU during the IO
submission phase.
[Remained Issue]
It has been mentioned in patch 4 that, users could change the state of
the underlying devices through '/sys/block/<dev>/io_poll', bypassing
the dm device above. Thus it can cause a situation where QUEUE_FLAG_POLL
is still set for the request_queue of dm device, while one of the
underlying mq device may has cleared this flag.
In this case, it will pass the 'test_bit(QUEUE_FLAG_POLL, &q->queue_flags)'
check in blk_poll(), while the input cookie may actually points to a hw
queue in IRQ mode since patch 11. Thus for this hw queue (in IRQ mode),
the bio-based polling routine will handle this hw queue acquiring
'spin_lock(&nvmeq->cq_poll_lock)' (refer
drivers/nvme/host/pci.c:nvme_poll), which is not adequate since this hw
queue may also be accessed in IRQ context. In other words,
spin_lock_irq() should be used here.
I have not come up one simple way to fix it. I don't want to do sanity
check (e.g., the type of the hw queue is HCTX_TYPE_POLL or not) in the
IO path (submit_bio()/blk_poll()), i.e., fast path.
We'd better fix it in the control path, i.e., dm could be aware of the
change when attribute (e.g., support io_poll or not) of one of the
underlying devices changed at runtime.
[Changes since v2]
Patchset v2 caches all hw queues (in polling mode) of underlying mq
devices in dm layer. The polling routine actually iterates through all
these cached hw queues.
However, mq may change the queue mapping at runtime (e.g., NVMe RESET
command), thus the cached hw queues in dm layer may be out-of-date. Thus
patchset v3 falls back to the implementation of the very first RFC
version, in which the mq layer needs to export one interface iterating
all polling hw queues (patch 5), and the bio-based polling routine just
calls this interface to iterate all polling hw queues.
Besides, several new optimization is proposed.
- patch 1,2,7
same as v2, untouched
- patch 3
Considering advice from Christoph Hellwig, while refactoring blk_poll(),
split mq and bio-based polling routine from the very beginning. Now
blk_poll() is just a simple entry. blk_bio_poll() is simply copied from
blk_mq_poll(), while the loop structure is some sort of duplication
though.
- patch 4
This patch is newly added to support turning on/off polling through
'/sys/block/<dev>/queue/io_poll' dynamiclly for bio-based devices.
Patchset v2 implemented this functionality by added one new queue flag,
which is not preferred since the queue flag resource is quite short of
nowadays.
- patch 5
This patch is newly added, preparing for the following bio-based
polling. The following bio-based polling will call this helper function,
accounting on the corresponding hw queue.
- patch 6
It's from the very first RFC version, preparing for the following
bio-based polling.
- patch 8
One fixing patch needed by the following bio-based polling. It's
actually a v2 of [1]. I had sent the v2 singly in-reply-to [1], though
it has not been visible on the mailing list maybe due to the delay.
- patch 9
It's from the very first RFC version.
- patch 10
This patch is newly added. Patchset v2 had ever proposed one
optimization that, skipping the **busy** hw queues during the iteration
phase. Back upon that time, one flag of 'atomic_t' is specifically
maintained in dm layer, representing if the corresponding hw queue is
busy or not. The idea is inherited, while the implementation changes.
Now @nvmeq->cq_poll_lock is used directly here, no need for extra flag
anymore.
This optimization can significantly reduce the competition for one hw
queue between multiple polling instances. Following statistics is the
test result when 3 threads concurrently randread (bs=4k, direct=1) one
dm-linear device, which is built upon 3 nvme devices, with one polling
hw queue per nvme device.
| IOPS (IRQ mode) | IOPS (iopoll=1 mode) | diff
----------- | --------------- | -------------------- | ----
without opt | 318k | 256k | ~-20%
with opt | 314k | 354k | ~13%
- patch 11
This is another newly added optimizatin for bio-based polling.
One intuitive insight is that, when the original bio submitted to dm
device doesn't get split, then the bio gets enqueued into only one hw
queue of one of the underlying mq devices. In this case, we no longer
need to track all split bios, and one cookie (for the only split bio)
is enough. It is implemented by returning the pointer to the
corresponding hw queue in this case.
It should be safe by directly returning the pointer to the hw queue,
since 'struct blk_mq_hw_ctx' won't be freed during the whole lifetime of
'struct request_queue'. Even when the number of hw queues may decrease
when NVMe RESET happens, the 'struct request_queue' structure of decreased
hw queues won't be freed, instead it's buffered into
&q->unused_hctx_list list.
Though this optimization seems quite intuitive, the performance test
shows that it does no benefit nor harm to the performance, while 3
threads concurrently randreading (bs=4k, direct=1) one dm-linear
device, which is built upon 3 nvme devices, with one polling hw queue
per nvme device.
I'm not sure why it doesn't work, maybe because the number of devices,
or the depth of the devcice stack is to low in my test case?
changes since v1:
- patch 1,2,4 is the same as v1 and have already been reviewed
- patch 3 is refactored a bit on the basis of suggestions from
Mike Snitzer.
- patch 5 is newly added and introduces one new queue flag
representing if the queue is capable of IO polling. This mainly
simplifies the logic in queue_poll_store().
- patch 6 implements the core mechanism supporting IO polling.
The sanity check checking if the dm device supports IO polling is
also folded into this patch, and the queue flag will be cleared if
it doesn't support, in case of table reloading.
Jeffle Xu (12):
block: move definition of blk_qc_t to types.h
block: add queue_to_disk() to get gendisk from request_queue
block: add poll method to support bio-based IO polling
block: add poll_capable method to support bio-based IO polling
blk-mq: extract one helper function polling hw queue
blk-mq: add iterator for polling hw queues
blk-mq: add one helper function getting hw queue
dm: always return BLK_QC_T_NONE for bio-based device
nvme/pci: don't wait for locked polling queue
block: fastpath for bio-based polling
block: sub-fastpath for bio-based polling
dm: support IO polling for bio-based dm device
block/blk-core.c | 112 +++++++++++++++++++++++++++++++++-
block/blk-mq.c | 37 ++++-------
block/blk-sysfs.c | 14 ++++-
drivers/md/dm-table.c | 26 ++++++++
drivers/md/dm.c | 102 ++++++++++++++++++++++++++-----
drivers/nvme/host/pci.c | 4 +-
include/linux/blk-mq.h | 23 +++++++
include/linux/blk_types.h | 66 +++++++++++++++++++-
include/linux/blkdev.h | 4 ++
include/linux/device-mapper.h | 1 +
include/linux/fs.h | 2 +-
include/linux/types.h | 3 +
include/trace/events/kyber.h | 6 +-
13 files changed, 350 insertions(+), 50 deletions(-)
From: Mike Snitzer <hidden> Date: 2021-02-23 20:56:41
On Mon, Feb 22 2021 at 10:55pm -0500,
JeffleXu [off-list ref] wrote:
On 2/20/21 7:06 PM, Jeffle Xu wrote:
quoted
[Changes since v3]
- newly add patch 7 and patch 11, as a new optimization improving
performance of multiple polling processes. Now performance of multiple
polling processes can be as scalable as single polling process (~30%).
Refer to the following [Performance] chapter for more details.
Hi Mike, would please evaluate this new version patch set? I think this
mechanism is near maturity, since multi-thread performance is as
scalable as single-thread (~30%) now.
On Mon, Feb 22 2021 at 10:55pm -0500,
JeffleXu [off-list ref] wrote:
quoted
On 2/20/21 7:06 PM, Jeffle Xu wrote:
quoted
[Changes since v3]
- newly add patch 7 and patch 11, as a new optimization improving
performance of multiple polling processes. Now performance of multiple
polling processes can be as scalable as single polling process (~30%).
Refer to the following [Performance] chapter for more details.
Hi Mike, would please evaluate this new version patch set? I think this
mechanism is near maturity, since multi-thread performance is as
scalable as single-thread (~30%) now.