dm: optimize REQ_PREFLUSH with data when using the linear target

If the table has only linear targets and there is just one underlying
device, we can optimize REQ_PREFLUSH with data - we don't have to split
it to two bios - a flush and a write. We can pass it to the linear target
directly.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Tested-by: Bart Van Assche <bvanassche@acm.org>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
This commit is contained in:
Mikulas Patocka 2025-09-15 17:42:45 +02:00
parent 4466dd3d71
commit 2b1c6d7a89
2 changed files with 25 additions and 8 deletions

View File

@ -292,6 +292,7 @@ struct dm_io {
struct dm_io *next; struct dm_io *next;
struct dm_stats_aux stats_aux; struct dm_stats_aux stats_aux;
blk_status_t status; blk_status_t status;
bool requeue_flush_with_data;
atomic_t io_count; atomic_t io_count;
struct mapped_device *md; struct mapped_device *md;

View File

@ -490,18 +490,13 @@ u64 dm_start_time_ns_from_clone(struct bio *bio)
} }
EXPORT_SYMBOL_GPL(dm_start_time_ns_from_clone); EXPORT_SYMBOL_GPL(dm_start_time_ns_from_clone);
static inline bool bio_is_flush_with_data(struct bio *bio)
{
return ((bio->bi_opf & REQ_PREFLUSH) && bio->bi_iter.bi_size);
}
static inline unsigned int dm_io_sectors(struct dm_io *io, struct bio *bio) static inline unsigned int dm_io_sectors(struct dm_io *io, struct bio *bio)
{ {
/* /*
* If REQ_PREFLUSH set, don't account payload, it will be * If REQ_PREFLUSH set, don't account payload, it will be
* submitted (and accounted) after this flush completes. * submitted (and accounted) after this flush completes.
*/ */
if (bio_is_flush_with_data(bio)) if (io->requeue_flush_with_data)
return 0; return 0;
if (unlikely(dm_io_flagged(io, DM_IO_WAS_SPLIT))) if (unlikely(dm_io_flagged(io, DM_IO_WAS_SPLIT)))
return io->sectors; return io->sectors;
@ -590,6 +585,7 @@ static struct dm_io *alloc_io(struct mapped_device *md, struct bio *bio, gfp_t g
io = container_of(tio, struct dm_io, tio); io = container_of(tio, struct dm_io, tio);
io->magic = DM_IO_MAGIC; io->magic = DM_IO_MAGIC;
io->status = BLK_STS_OK; io->status = BLK_STS_OK;
io->requeue_flush_with_data = false;
/* one ref is for submission, the other is for completion */ /* one ref is for submission, the other is for completion */
atomic_set(&io->io_count, 2); atomic_set(&io->io_count, 2);
@ -948,6 +944,7 @@ static void __dm_io_complete(struct dm_io *io, bool first_stage)
struct mapped_device *md = io->md; struct mapped_device *md = io->md;
blk_status_t io_error; blk_status_t io_error;
bool requeued; bool requeued;
bool requeue_flush_with_data;
requeued = dm_handle_requeue(io, first_stage); requeued = dm_handle_requeue(io, first_stage);
if (requeued && first_stage) if (requeued && first_stage)
@ -964,6 +961,7 @@ static void __dm_io_complete(struct dm_io *io, bool first_stage)
__dm_start_io_acct(io); __dm_start_io_acct(io);
dm_end_io_acct(io); dm_end_io_acct(io);
} }
requeue_flush_with_data = io->requeue_flush_with_data;
free_io(io); free_io(io);
smp_wmb(); smp_wmb();
this_cpu_dec(*md->pending_io); this_cpu_dec(*md->pending_io);
@ -976,7 +974,7 @@ static void __dm_io_complete(struct dm_io *io, bool first_stage)
if (requeued) if (requeued)
return; return;
if (bio_is_flush_with_data(bio)) { if (unlikely(requeue_flush_with_data)) {
/* /*
* Preflush done for flush with data, reissue * Preflush done for flush with data, reissue
* without REQ_PREFLUSH. * without REQ_PREFLUSH.
@ -1996,12 +1994,30 @@ static void dm_split_and_process_bio(struct mapped_device *md,
} }
init_clone_info(&ci, io, map, bio, is_abnormal); init_clone_info(&ci, io, map, bio, is_abnormal);
if (bio->bi_opf & REQ_PREFLUSH) { if (unlikely((bio->bi_opf & REQ_PREFLUSH) != 0)) {
/*
* The "flush_bypasses_map" is set on targets where it is safe
* to skip the map function and submit bios directly to the
* underlying block devices - currently, it is set for dm-linear
* and dm-stripe.
*
* If we have just one underlying device (i.e. there is one
* linear target or multiple linear targets pointing to the same
* device), we can send the flush with data directly to it.
*/
if (map->flush_bypasses_map) {
struct list_head *devices = dm_table_get_devices(map);
if (devices->next == devices->prev)
goto send_preflush_with_data;
}
if (bio->bi_iter.bi_size)
io->requeue_flush_with_data = true;
__send_empty_flush(&ci); __send_empty_flush(&ci);
/* dm_io_complete submits any data associated with flush */ /* dm_io_complete submits any data associated with flush */
goto out; goto out;
} }
send_preflush_with_data:
if (static_branch_unlikely(&zoned_enabled) && if (static_branch_unlikely(&zoned_enabled) &&
(bio_op(bio) == REQ_OP_ZONE_RESET_ALL)) { (bio_op(bio) == REQ_OP_ZONE_RESET_ALL)) {
error = __send_zone_reset_all(&ci); error = __send_zone_reset_all(&ci);