[PATCH v2 2/3] mm: swap: bypass synchronous readahead unless the target needs I/O

From: Alexandre Ghiti

Date: Wed Jul 22 2026 - 13:19:38 EST


The overlap pass reads the deferred synchronous neighbours while the
first pass' device I/O is in flight, hiding the synchronous work behind
it. That only holds when the faulting target is itself served
asynchronously from a device.

When the target is synchronous too, the fault has no in-flight I/O to
hide behind, so reading the neighbours in the second pass is pure added
latency on the fault's own critical path, so skip it in that case to
preserve initial fault latency, this is a trade-off that slightly
impacts the throughput gain from patch 1.

Results [vm-scalability mix-rw-seq, 4G memcg / 8G region, ~256M zswap
cap (~73% disk), unthrottled NVMe, zswap=zstd, shrinker off, n=3]:

metric mm-new +patch delta
swap-in tput (KB/s) 118946 130364 +9.6%
major faults 261471 396648 +52%
swap_ra (pages) 1.56M 1.42M -9%

The other gain is from bypassing readahead entirely for zswap (like it
is already done for SWP_SYNCHRONOUS_IO devices).

Results [kbuild make -j16, 768M memcg, sustained pure-zswap thrash,
Sapphire Rapids, zswap=zstd, shrinker off, n=3]:

metric mm-new +patch delta
build time (s) 410 354 -13.7%
swap readahead (pages) 6.69M 0.28M -96%
zswap-in (GB) 83.3 56.5 -32%
zswap-out (GB) 152.4 123.2 -19%

Signed-off-by: Alexandre Ghiti <alex@xxxxxxxx>
---
mm/swap_state.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mm/swap_state.c b/mm/swap_state.c
index 3ff2896dd6d2..23137714d7cb 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -844,7 +844,7 @@ struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask,
blk_finish_plug(&plug);
swap_read_unplug(splug);

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

/* Overlap the in flight IOs with the synchronous neighbours reads */
@@ -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)
+ if (!nr_sync || swap_entry_synchronous(targ_entry))
goto drain;

/* Overlap the in flight IOs with the synchronous neighbours reads */
--
2.53.0-Meta