[PATCH v2 3/3] mm: swap: treat SWP_SYNCHRONOUS_IO devices as synchronous in readahead

From: Alexandre Ghiti

Date: Wed Jul 22 2026 - 13:31:32 EST


A readahead neighbour on an SWP_SYNCHRONOUS_IO device (zram) has no
asynchronous I/O to overlap either, exactly like a zswap-resident one.
Extend swap_entry_synchronous() to take the swap device and also return
true for such an entry, so it is deferred to the second pass too. A
same-type neighbour shares the target's device, which cannot be
SWP_SYNCHRONOUS_IO here (do_swap_page() routes synchronous-device
targets through swapin_sync()), so @si is only non-NULL for cross-device
neighbours, which is exactly where it matters.

This helps a mixed zram+disk workload when the faulting target is on
disk: that fault now overlaps its zram neighbours' reads behind its own
disk I/O, exactly as patch 1 does for zswap neighbours.

Results [vm-scalability mix-rw-seq, 1G zram (SWP_SYNCHRONOUS_IO, high
prio) + NVMe disk swap (~76% of swap-in from disk), zram=zstd, zswap
off, 4G memcg / 8G region, unthrottled NVMe, n=3]. With zswap off
patches 1-2 are inert, so the delta isolates this patch:

metric mm-new +patch delta
swap-in tput (KB/s) 171226 182046 +6.3%
major faults 431904 432657 +0.2%
swap-in pswpin (pages) 1778579 1778473 ~0%
swap_ra (pages) 1349299 1348382 -0.1%

Signed-off-by: Alexandre Ghiti <alex@xxxxxxxx>
---
include/linux/swap.h | 4 ++--
mm/swap_state.c | 10 +++++-----
mm/swapfile.c | 6 ++++--
3 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/include/linux/swap.h b/include/linux/swap.h
index 6bb35c093d77..e6e0d07a4f19 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -441,7 +441,7 @@ extern unsigned int count_swap_pages(int, int);
extern sector_t swapdev_block(int, pgoff_t);
extern int __swap_count(swp_entry_t entry);
extern bool swap_entry_swapped(struct swap_info_struct *si, swp_entry_t entry);
-extern bool swap_entry_synchronous(swp_entry_t entry);
+extern bool swap_entry_synchronous(struct swap_info_struct *si, swp_entry_t entry);
extern int swp_swapcount(swp_entry_t entry);
struct backing_dev_info;
extern struct swap_info_struct *get_swap_device(swp_entry_t entry);
@@ -520,7 +520,7 @@ static inline bool swap_entry_swapped(struct swap_info_struct *si, swp_entry_t e
return false;
}

-static inline bool swap_entry_synchronous(swp_entry_t entry)
+static inline bool swap_entry_synchronous(struct swap_info_struct *si, swp_entry_t entry)
{
return false;
}
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 23137714d7cb..12fe86218557 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -829,7 +829,7 @@ struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask,
blk_start_plug(&plug);
for (offset = start_offset; offset <= end_offset ; offset++) {
ra_entry = swp_entry(swp_type(entry), offset);
- if (swap_entry_synchronous(ra_entry)) {
+ if (swap_entry_synchronous(si, ra_entry)) {
has_sync = true;
continue;
}
@@ -844,13 +844,13 @@ struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask,
blk_finish_plug(&plug);
swap_read_unplug(splug);

- if (!has_sync || swap_entry_synchronous(entry))
+ if (!has_sync || swap_entry_synchronous(si, entry))
goto drain;

/* Overlap the in flight IOs with the synchronous neighbours reads */
for (offset = start_offset; offset <= end_offset; offset++) {
ra_entry = swp_entry(swp_type(entry), offset);
- if (!swap_entry_synchronous(ra_entry))
+ if (!swap_entry_synchronous(si, ra_entry))
continue;

folio = swap_cache_read_folio(ra_entry, gfp_mask, mpol, ilx,
@@ -968,7 +968,7 @@ static struct folio *swap_vma_readahead(swp_entry_t targ_entry, gfp_t gfp_mask,
continue;
}

- if (swap_entry_synchronous(entry)) {
+ if (swap_entry_synchronous(si, entry)) {
sync_entries[nr_sync] = entry;
sync_ilx[nr_sync] = ilx;
nr_sync++;
@@ -990,7 +990,7 @@ static struct folio *swap_vma_readahead(swp_entry_t targ_entry, gfp_t gfp_mask,
blk_finish_plug(&plug);
swap_read_unplug(splug);

- if (!nr_sync || swap_entry_synchronous(targ_entry))
+ if (!nr_sync || swap_entry_synchronous(NULL, targ_entry))
goto drain;

/* Overlap the in flight IOs with the synchronous neighbours reads */
diff --git a/mm/swapfile.c b/mm/swapfile.c
index ee2bbfdfc988..e8f88156b767 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1985,11 +1985,13 @@ bool swap_entry_swapped(struct swap_info_struct *si, swp_entry_t entry)

/**
* swap_entry_synchronous - Check if @entry is served by a synchronous source.
+ * @si: the swap device of @entry, or NULL if it is not needed/held.
* @entry: the swap entry.
*/
-bool swap_entry_synchronous(swp_entry_t entry)
+bool swap_entry_synchronous(struct swap_info_struct *si, swp_entry_t entry)
{
- return zswap_is_present(entry, 1);
+ return zswap_is_present(entry, 1) ||
+ (si && data_race(si->flags & SWP_SYNCHRONOUS_IO));
}

/*
--
2.53.0-Meta