From: Kairui Song <ryncsn@gmail.com>
To: Christoph Hellwig <hch@lst.de>
Cc: baoquan.he@linux.dev, akpm@linux-foundation.org,
chrisl@kernel.org, usama.arif@linux.dev, kasong@tencent.com,
nphamcs@gmail.com, shikemeng@huaweicloud.com,
youngjun.park@lge.com, linux-mm@kvack.org
Subject: Re: [PATCH 3/7] mm/swap: also use struct swap_iocb for block I/O
Date: Thu, 16 Jul 2026 00:03:22 +0800 [thread overview]
Message-ID: <alZ0fgRSy0RpHxcT@KASONG-MC4> (raw)
In-Reply-To: <20260713093350.2154226-4-hch@lst.de>
On Mon, Jul 13, 2026 at 11:33:40AM +0800, Christoph Hellwig wrote:
> Block I/O benefits from batching just as much as remote file systems.
> Extend struct swap_iocb to support building a bio on the fly as well,
> and rewrite the block based swap code for it. This especially benefits
> submit_bio based drivers that do not have the block plugging available,
> but also saves allocating extra bios for blk-mq drivers.
>
> Add a pre-allocated bio to struct swap_iocb in a union with kiocb
> used for file system based swap so that struct swap_iocb can be used
> for all swap I/O, and initialize the pool for it unconditionally.
>
> Various low-level bdev and fs functions are now replaced with a unified
> can_merge/add/submit scheme.
>
> Note that the block based swap code now uses the same memcg-based
> check previously added for file system based swap as well.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
> mm/page_io.c | 526 ++++++++++++++++++++++++--------------------------
> mm/swap.h | 1 +
> mm/swapfile.c | 9 +-
> 3 files changed, 254 insertions(+), 282 deletions(-)
Hi, thanks for the great work, sorry for the joining this late as I spent
quite some time on the new swap priority list, now checking this series.
>
> diff --git a/mm/page_io.c b/mm/page_io.c
> index fe24a49e034c..0195c25a77eb 100644
> --- a/mm/page_io.c
> +++ b/mm/page_io.c
> @@ -28,54 +28,6 @@
> #include "swap.h"
> #include "swap_table.h"
>
...
> @@ -336,11 +306,18 @@ static void bio_associate_blkg_from_page(struct bio *bio, struct folio *folio)
> css_put(css);
> }
> #else
> +static bool folio_blkg_can_merge(struct folio *folio, struct folio *prev_folio)
> +{
> + return true;
> +}
> #define bio_associate_blkg_from_page(bio, folio) do { } while (0)
> #endif /* CONFIG_MEMCG && CONFIG_BLK_CGROUP */
>
> struct swap_iocb {
> - struct kiocb iocb;
> + union {
> + struct kiocb iocb;
> + struct bio bio;
> + };
> struct bio_vec bvecs[SWAP_CLUSTER_MAX];
> int nr_bvecs;
> int len;
> @@ -360,171 +337,70 @@ int sio_pool_init(void)
> return 0;
> }
>
> -static void sio_write_complete(struct kiocb *iocb, long ret)
> +static bool swap_can_merge(struct swap_io_ctx *ctx, struct folio *folio,
> + int rw)
Will a bool rw suits better?
> {
> - struct swap_iocb *sio = container_of(iocb, struct swap_iocb, iocb);
> - struct page *page = sio->bvecs[0].bv_page;
> - int p;
> + struct swap_info_struct *sis = __swap_entry_to_info(folio->swap);
> + struct bio_vec *last_bv = &ctx->sio->bvecs[ctx->sio->nr_bvecs - 1];
> + struct folio *prev_folio = bvec_folio(last_bv);
> + size_t prev_folio_size = folio_size(prev_folio);
>
> - if (ret != sio->len) {
> - /*
> - * In the case of swap-over-nfs, this can be a
> - * temporary failure if the system has limited
> - * memory for allocating transmit buffers.
> - * Mark the page dirty and avoid
> - * folio_rotate_reclaimable but rate-limit the
> - * messages.
> - */
> - pr_err_ratelimited("Write error %ld on dio swapfile (%llu)\n",
> - ret, swap_dev_pos(page_swap_entry(page)));
> - for (p = 0; p < sio->nr_bvecs; p++) {
> - page = sio->bvecs[p].bv_page;
> - set_page_dirty(page);
> - ClearPageReclaim(page);
> - }
> - }
> + if (ctx->sis != sis)
> + return false;
>
> - for (p = 0; p < sio->nr_bvecs; p++)
> - end_page_writeback(sio->bvecs[p].bv_page);
> + if (sis->flags & SWP_FS_OPS) {
> + if (swap_dev_pos(folio->swap) !=
> + swap_dev_pos(prev_folio->swap) + prev_folio_size)
> + return false;
> + } else {
> + if (swap_folio_sector(folio) !=
> + swap_folio_sector(prev_folio) +
> + (prev_folio_size >> SECTOR_SHIFT))
> + return false;
> + if (rw == WRITE && !folio_blkg_can_merge(folio, prev_folio))
> + return false;
> + }
>
> - mempool_free(sio, sio_pool);
> + return true;
> }
>
>
> -static void swap_writepage_bdev_sync(struct folio *folio,
> - struct swap_info_struct *sis)
> -{
> - struct bio_vec bv;
> - struct bio bio;
> -
> - bio_init(&bio, sis->bdev, &bv, 1, REQ_OP_WRITE | REQ_SWAP);
> - bio.bi_iter.bi_sector = swap_folio_sector(folio);
> - bio_add_folio_nofail(&bio, folio, folio_size(folio), 0);
bio_add_folio_nofail also increased bi_vcnt, now it's open coded and
bi_vcnt stays zero, it's not used right now, could there be any user
of it in the future?
> -
> - bio_associate_blkg_from_page(&bio, folio);
> - count_swpout_vm_event(folio);
> -
> - folio_start_writeback(folio);
> - folio_unlock(folio);
> -
> - submit_bio_wait(&bio);
> - __end_swap_bio_write(&bio);
> -}
> -
> -static void swap_writepage_bdev_async(struct folio *folio,
> - struct swap_info_struct *sis)
> +void __swap_writepage(struct swap_io_ctx *ctx, struct folio *folio)
> {
... The git diff is not very human readable but fine, here
we have __swap_writepage not doing IO anymore but add the folio
and deferred the IO ...
> + swap_add_folio(ctx, folio, WRITE);
So this removes swap_writepage_bdev_sync (which was calling submit_bio_wait),
we are replying on a later swap_write_submit, and just add the folio to ctx.
In the previous patch, swap_write_submit is called after the whole batch of
folio is iterated, so there is no more IO completion in pageout().
Before the series, pageout will detect for writeback finished and clean
pages under PAGE_SUCCESS, if the IO of a page is done and writeback flag
cleaned, the page get released immediately. But now, almost all pages
will be put back to the LRU waiting for rotation. This also make the
reclaim have a stronger bias towards reclaim files, and anon reclaim
become less effective.
I do observe a real performance regression with classical LRU and ZRAM,
ordinary swap seems also very slightly effected but could be noise,
test is done on my laptop by building the kernel using make -j12 and
tinyconfig in a 2G VM with mild pressure:
Before this series (With NVME, in a virtual machine, NVME drive exposed
to the VM by virtio):
152.02user 42.34system 0:32.07elapsed
3303408inputs+0outputs (83567major+15910796minor)pagefaults
After this series:
155.72user 46.19system 0:33.88elapsed
3617720inputs+0outputs (94045major+16094079minor)pagefaults
Before this series (with 8G ZRAM only):
175.39user 125.89system 0:40.54elapsed
26581840inputs+0outputs (1186391major+22698964minor)pagefaults
After this sereis:
165.59user 216.02system 1:04.06elapsed <- the problemic one.
53531464inputs+0outputs (2550441major+31974007minor)pagefaults
Because all anon folios will be derfer freed on next iteration of the LRU,
which caused a much higher memory pressure.
And, very interestingly, with MGLRU, things are looking better in
every way (which is kind of expected, see below):
Before this series (With NVME, in a virtual machine, NVME drive exposed
to the VM by virtio):
155.96user 42.15system 0:30.57elapsed
2057792inputs+0outputs (75116major+15860836minor)pagefaults
After this series:
150.42user 44.18system 0:31.21elapsed
2545616inputs+0outputs (69325major+16141325minor)pagefaults
Before this series (with 8G ZRAM only):
175.57user 120.27system 0:36.32elapsed
24655320inputs+0outputs (1568694major+22468687minor)pagefaults
After:
180.59user 119.43system 0:36.18elapsed
23893384inputs+0outputs (1546158major+22302635minor)pagefaults
I only testes each case for 5 times and take the median result, it
could be a bit noisy but good enough for now, the results are mostly stable.
So in summary when using classical LRU with ZRAM, there is a major regression
because pageout can no longer free any page directly (40s -> 1min), all page
have to do a round trip to the LRU again before gets freed. Other cases are
mostly fine, but maybe pageout might catch a few clean folios before even
for ordinary devices and free them directly? So we are seeming a slightly
higher major fault due to deferred free / IO? (Could be noise though).
Things are looking good with MGLRU, because MGLRU have a secondary
retry for clean folio direct reclaim even if pageout() didn't do that
(see the "retry folios that may have missed folio_rotate_reclaimable"
comment, that comment isn't that correct either though).
I remember Barry mentioned that someone once tried to implement a similar
secondary retry for classical LRU too, but got rejected.
I think this better be fixed form the LRU side to always try batch free
the clean folios after one iteration, which is also part of unifying
MGLRU / LRU, compression (either ZRAM / ZSWAP) may also benefit from
batching though, and free the folios in batch it could be even better.
That could take more time though, so shall we have a way to just submit
the IO early for now?
next prev parent reply other threads:[~2026-07-15 16:03 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-13 9:33 better block swap batching and a different take on swap_ops v5 Christoph Hellwig
2026-07-13 9:33 ` [PATCH 1/7] shmem: provide a shmem_write_folio wrapper Christoph Hellwig
2026-07-14 17:27 ` Kairui Song
2026-07-13 9:33 ` [PATCH 2/7] mm/swap: introduce struct swap_io_ctx Christoph Hellwig
2026-07-14 1:26 ` Baoquan He
2026-07-14 5:06 ` Christoph Hellwig
2026-07-13 9:33 ` [PATCH 3/7] mm/swap: also use struct swap_iocb for block I/O Christoph Hellwig
2026-07-15 16:03 ` Kairui Song [this message]
2026-07-13 9:33 ` [PATCH 4/7] mm/swap: remove count_swpout_vm_event Christoph Hellwig
2026-07-13 9:33 ` [PATCH 5/7] mm/swap: use swap_ops to register swap device's methods Christoph Hellwig
2026-07-13 9:33 ` [PATCH 6/7] mm/swap: remove SWP_FS_OPS Christoph Hellwig
2026-07-13 9:33 ` [PATCH 7/7] mm/vmstat: add NRSWP{IN,OUT} counters Christoph Hellwig
2026-07-13 20:13 ` better block swap batching and a different take on swap_ops v5 Andrew Morton
2026-07-14 5:12 ` Christoph Hellwig
-- strict thread matches above, loose matches on Subject: below --
2026-07-10 6:39 better block swap batching and a different take on swap_ops v4 Christoph Hellwig
2026-07-10 6:39 ` [PATCH 3/7] mm/swap: also use struct swap_iocb for block I/O Christoph Hellwig
2026-07-06 13:21 better block swap batching and a different take on swap_ops v3 Christoph Hellwig
2026-07-06 13:21 ` [PATCH 3/7] mm/swap: also use struct swap_iocb for block I/O Christoph Hellwig
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=alZ0fgRSy0RpHxcT@KASONG-MC4 \
--to=ryncsn@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=baoquan.he@linux.dev \
--cc=chrisl@kernel.org \
--cc=hch@lst.de \
--cc=kasong@tencent.com \
--cc=linux-mm@kvack.org \
--cc=nphamcs@gmail.com \
--cc=shikemeng@huaweicloud.com \
--cc=usama.arif@linux.dev \
--cc=youngjun.park@lge.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.