[PATCH v6 0/3] mm/zswap: shrink zswap_entry via a pool id
From: Jianyue Wu
Date: Sun Sep 06 2026 - 03:47:34 EST
Every stored page has a struct zswap_entry, so its size is pure per-page
overhead. On 64-bit it is currently 56 bytes, of which 8 bytes are a
pointer to the owning zswap_pool.
Only a handful of pools are ever live: a new pool is created only when
the compressor is (re)set, and pools are reused across compressor
switches. A list cannot look a pool up by id. An allocating xarray
can, which lets each zswap_entry store a u8 instead of a pointer.
This series:
1. Releases retired pools with queue_rcu_work() instead of a worker
calling synchronize_rcu(), so the release worker no longer blocks
on an RCU grace period.
2. Replaces the zswap_pools list with an allocating xarray
(XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_BH) and a separate RCU-protected
current-pool pointer, giving each pool a stable small id. Ids
start at 1. The reserved id 0 is never allocated, so looking it
up resolves to NULL. The table grows as needed up to 255 live
pools (u8 pool_idx), not a fixed slot array.
3. Stores that u8 pool id in each zswap_entry instead of the pool
pointer. The u8 fits in padding after the bool referenced field.
On 64-bit that shrinks the entry from 56 to 48 bytes, which fits 73 to
85 objects in a 4K slab (~2MiB of metadata saved per 1GiB of data held
in zswap).
The 255-id cap counts every pool still in the xarray. Switching
compressor kills the old pool, but that pool stays in the table until
its last entry drops the pool's ref, so a draining pool still occupies
an id. The id is reused only after xa_erase. Switching back to a
compressor whose pool is still in the table resurrects it instead of
allocating a new id. If every id is occupied, creating a pool for
another compressor fails and the switch is rejected.
Pool table locking: xa_for_each() walks and the current-pool pointer
use an explicit rcu_read_lock(), because xa_for_each()'s own RCU does
not span the loop body. xa_load() takes RCU around the lookup itself,
so zswap_entry_pool() needs no extra rcu_read_lock(). The returned
pool stays valid because a live entry pins it via percpu_ref, so the
id cannot be reused under it. xa_lock is taken only in xa_alloc_bh()
and xa_erase_bh().
Benchmark (x86_64, compressor=lzo, MADV_PAGEOUT store + fault-in load):
- e2e store+load median latency: no measurable regression vs baseline
at matched stored_delta
Each store, free, and decompress looks up the pool with xa_load()
instead of following a pointer. With only a handful of live pools the
xarray walk is short.
Testing
=======
- Boot with DEBUG_ATOMIC_SLEEP + lockdep/PROVE_RCU + KASAN:
zswap store/load, shrinker writeback, and compressor switch
(retire, then switch back to resurrect) pass
This series is based on akpm/mm-unstable as of 2026-09-05
(d118502628f8).
To: Johannes Weiner <hannes@xxxxxxxxxxx>
To: Yosry Ahmed <yosry@xxxxxxxxxx>
To: Nhat Pham <nphamcs@xxxxxxxxx>
To: Chengming Zhou <chengming.zhou@xxxxxxxxx>
To: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
Cc: Chris Li <chrisl@xxxxxxxxxx>
Cc: linux-mm@xxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx
Signed-off-by: Jianyue Wu <wujianyue000@xxxxxxxxx>
Changes since v5:
- Use xa_erase_bh() and move queue_rcu_work() out of xa_lock() in
__zswap_pool_empty().
- Drop the redundant rcu_read_lock() around zswap_entry_pool(). xa_load()
already takes RCU and a live entry pins its pool via percpu_ref.
- Replace xa_lock() protected pool walks with RCU. xa_lock() is now only for
xa_alloc_bh() and xa_erase_bh().
- Drop zswap_pool_current(), it only read the current pool under xa_lock(),
which this path no longer takes.
- Add a comment in zswap_total_pages() on why the outer rcu_read_lock()
remains (xa_for_each()'s RCU does not span the loop body).
- Rebase onto akpm/mm-unstable d118502628f8.
Link: https://lore.kernel.org/all/cover.1788528216.git.wujianyue000@xxxxxxxxx/
Link: https://lore.kernel.org/all/20260830114731.8322-1-wujianyue000@xxxxxxxxx/
Link: https://lore.kernel.org/all/20260815-shrink_zswap_entry_0815_v2-v3-3-0171bd86a667@xxxxxxxxx/
Link: https://lore.kernel.org/all/20260731-shrink_zswap_entry_v2-0-0-v2-0-e72083aa8734@xxxxxxxxx/
Link: https://lore.kernel.org/all/20260726-shrink_zswap_entry_v1-0-0-v1-1-30957e4d0cb6@xxxxxxxxx/
---
Jianyue Wu (3):
mm/zswap: release retired pools via queue_rcu_work() instead of synchronize_rcu()
mm/zswap: replace the zswap_pools list with an allocating xarray
mm/zswap: reference the pool by id to shrink struct zswap_entry
mm/zswap.c | 171 ++++++++++++++++++++++++++++++++++++-------------------------
1 file changed, 101 insertions(+), 70 deletions(-)
---
base-commit: d118502628f8b673be9023db8bdf878f64a7ed45
change-id: 20260906-shrink_zswap_entry_v6-ed3d14c9001f
Best regards,
--
Jianyue Wu <wujianyue000@xxxxxxxxx>