From: "Kanchana P. Sridhar" <kanchanapsridhar2026@gmail.com>
To: hannes@cmpxchg.org, yosry@kernel.org, nphamcs@gmail.com,
chengming.zhou@linux.dev, akpm@linux-foundation.org,
linux-mm@kvack.org, herbert@gondor.apana.org.au,
davem@davemloft.net, linux-crypto@vger.kernel.org,
kanchanapsridhar2026@gmail.com, linux-kernel@vger.kernel.org
Cc: rientjes@google.com, willy@infradead.org, senozhatsky@chromium.org
Subject: [PATCH v3 7/8] mm: zswap: Refactor and optimize zswap_store_pages() batch operations.
Date: Thu, 5 Nov 2026 23:24:11 -0800 [thread overview]
Message-ID: <20261106072412.7909-8-kanchanapsridhar2026@gmail.com> (raw)
In-Reply-To: <20261106072412.7909-1-kanchanapsridhar2026@gmail.com>
The various stages of zswap_store_pages() are broken down into routines
that work on a batch of data when a large folio is being processed:
zswap_init_entries()
zswap_tree_stores()
The struct zswap_store_context is used to pass constraints and data
among these batching components. The resulting code is highly simplified
and maintainable, where each of the batching sub-routines of
zswap_store() can be optimized modularly.
For instance, the zswap_tree_stores() now does the zswap atomics
counter increments, getting pool references, getting objcg references
and objcg zswap charging at the batch level. A new
obj_cgroup_charge_zswap_many() is added to enable batch charging of
compressed bytes for multiple pages/incompressible pages in the batch,
in order to accurately update the MEMCG_ZSWAP* counters.
The next commit will implement batching of zswap_compress().
Subsequent patch series will be submitted, that implement ideas building
upon this batching framework:
1) Replacing zsmalloc with SLUB allocator, comprising of kmem_caches
for a finite # of compressed size bins. This enables bulk allocation
of compressed buffers in a batch that belong to the same size
bin. It gets rid of significant zsmalloc latency overhead (locks,
compressed data spanning zspages, etc.), without compromising memory
efficiency.
2) vmscan reclaim batching of hybrid any-order folios, that will submit
a batch of folios to zswap_store()'s batching framework to optimize
storing of batches of pages from different folios, in addition to
within large folios.
Signed-off-by: Kanchana P. Sridhar <kanchanapsridhar2026@gmail.com>
---
include/linux/memcontrol.h | 7 ++
mm/memcontrol.c | 36 ++++++++
mm/zswap.c | 182 ++++++++++++++++++++++++-------------
3 files changed, 164 insertions(+), 61 deletions(-)
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index 69e1bcc67c58..7532dd9f2211 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -1946,6 +1946,8 @@ static inline void mem_cgroup_calculate_protection_path(struct mem_cgroup *root,
#if defined(CONFIG_MEMCG) && defined(CONFIG_ZSWAP)
bool obj_cgroup_may_zswap(struct obj_cgroup *objcg);
void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size);
+void obj_cgroup_charge_zswap_many(struct obj_cgroup *objcg, size_t size,
+ u8 nr_pages, u8 nr_incomp_pages);
void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, size_t size);
bool mem_cgroup_zswap_writeback_enabled(const struct mem_cgroup *memcg);
#else
@@ -1957,6 +1959,11 @@ static inline void obj_cgroup_charge_zswap(struct obj_cgroup *objcg,
size_t size)
{
}
+static inline void obj_cgroup_charge_zswap_many(struct obj_cgroup *objcg,
+ size_t size, u8 nr_pages,
+ u8 nr_incomp_pages)
+{
+}
static inline void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg,
size_t size)
{
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 84f4a8fd4d16..eddf6dbe6342 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -6345,6 +6345,42 @@ void obj_cgroup_charge_zswap(struct obj_cgroup *objcg, size_t size)
rcu_read_unlock();
}
+/**
+ * obj_cgroup_charge_zswap_many - bulk charge compression backend memory
+ * from compressing a batch of pages.
+ * @objcg: the object cgroup
+ * @size: total compressed length of a batch of compressed objects
+ * @nr_pages: # of pages that were compressed to store in zswap
+ * @nr_incomp_pages: # of incompressible pages to store in zswap
+ *
+ * This forces the charge after obj_cgroup_may_zswap() allowed
+ * compression and storage in zswap for this cgroup to go ahead.
+ */
+void obj_cgroup_charge_zswap_many(struct obj_cgroup *objcg, size_t size,
+ u8 nr_pages, u8 nr_incomp_pages)
+{
+ struct mem_cgroup *memcg;
+
+ if (!cgroup_subsys_on_dfl(memory_cgrp_subsys))
+ return;
+
+ if (obj_cgroup_is_root(objcg))
+ return;
+
+ VM_WARN_ON_ONCE(!(current->flags & PF_MEMALLOC));
+
+ /* PF_MEMALLOC context, charging must succeed */
+ if (obj_cgroup_charge(objcg, GFP_KERNEL, size))
+ VM_WARN_ON_ONCE(1);
+
+ rcu_read_lock();
+ memcg = obj_cgroup_memcg(objcg);
+ mod_memcg_state(memcg, MEMCG_ZSWAP_B, size);
+ mod_memcg_state(memcg, MEMCG_ZSWAPPED, nr_pages);
+ mod_memcg_state(memcg, MEMCG_ZSWAP_INCOMP, nr_incomp_pages);
+ rcu_read_unlock();
+}
+
/**
* obj_cgroup_uncharge_zswap - uncharge compression backend memory
* @objcg: the object cgroup
diff --git a/mm/zswap.c b/mm/zswap.c
index 00cc4586b284..c2d17ccb53a1 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -490,12 +490,12 @@ static int __must_check zswap_pool_tryget(struct zswap_pool *pool)
}
/* The caller must already have a reference. */
-static void zswap_pool_get(struct zswap_pool *pool)
+static inline void zswap_pool_get_many(struct zswap_pool *pool, u8 nr)
{
- percpu_ref_get(&pool->ref);
+ percpu_ref_get_many(&pool->ref, nr);
}
-static void zswap_pool_put(struct zswap_pool *pool)
+static inline void zswap_pool_put(struct zswap_pool *pool)
{
percpu_ref_put(&pool->ref);
}
@@ -1570,56 +1570,61 @@ static void shrink_worker(struct work_struct *w)
* main API
**********************************/
+/* Functions called by zswap_store(), that operate on batches of pages. */
+
/*
- * Store multiple pages in @zc->folio, starting from the page at index @start up
- * to the page at index @end-1.
+ * zswap_init_entries() co-locates most of the zswap entry fields' setup under
+ * the assumption that all phases of store processing will succeed. An important
+ * marker to simplify catch-all cleanup in zswap_store_pages() is done by
+ * initializing each entry's @handle to ERR_PTR(-EINVAL).
*/
-static bool zswap_store_pages(struct zswap_store_context *zc,
- long start,
- long end,
- struct zswap_pool *pool,
- struct obj_cgroup *objcg)
+static inline void zswap_init_entries(struct zswap_store_context *zc,
+ struct zswap_pool *pool,
+ struct obj_cgroup *objcg)
{
- u8 i, store_fail_idx = 0;
- bool ret;
-
- zc->nr_pages = end - start;
- zc->folio_start = start;
-
- VM_WARN_ON_ONCE(zc->nr_pages <= 0 || zc->nr_pages > ZSWAP_MAX_BATCH_SIZE);
-
- ret = zswap_entries_cache_alloc_batch(zc, GFP_KERNEL);
- if (unlikely(!ret))
- return false;
+ struct zswap_entry *entry;
+ u8 i;
/*
* We co-locate entry initialization as much as possible here to
* minimize potential cache misses.
*/
for (i = 0; i < zc->nr_pages; ++i) {
- zc->entries[i]->handle = (unsigned long)ERR_PTR(-EINVAL);
+ entry = zc->entries[i];
+ entry->handle = (unsigned long)ERR_PTR(-EINVAL);
/*
- * Set pool_idx before the xa_store() below publishes the entry, or a
- * concurrent reader could resolve a stale pool_idx left by slab reuse
- * to an unrelated live pool.
+ * Set pool_idx before the xa_store() in zswap_tree_stores()
+ * publishes the entry, or a concurrent reader could resolve a
+ * stale pool_idx left by slab reuse to an unrelated live pool.
*/
- zc->entries[i]->pool_idx = pool->idx;
- zc->entries[i]->swpentry = folio_swap_entry(zc->folio,
+ entry->pool_idx = pool->idx;
+ entry->swpentry = folio_swap_entry(zc->folio,
zc->folio_start + i);
- zc->entries[i]->objcg = objcg;
- zc->entries[i]->referenced = true;
- zc->entries[i]->nid = zc->nid;
- INIT_LIST_HEAD(&zc->entries[i]->lru);
+ entry->objcg = objcg;
+ entry->referenced = true;
+ entry->nid = zc->nid;
+ INIT_LIST_HEAD(&entry->lru);
}
+}
- for (i = 0; i < zc->nr_pages; ++i) {
- if (!zswap_compress(zc->folio, zc->folio_start + i,
- zc->entries[i], pool, zc->wb_enabled))
- goto store_pages_failed;
- }
+/*
+ * zswap_tree_stores() stores the batch entries in xarray and adds them to the
+ * zswap LRU list. If an error occurs during xa_store(), localized state
+ * unwinding is done to erase prior batch entries from the xarray and delete
+ * them from the zswap LRU list. Remaining batch cleanup occurs in
+ * zswap_store_pages(). As batch entries are processed, we maintain aggregate
+ * statistics to do the accounting in one bulk operation.
+ */
+static bool zswap_tree_stores(struct zswap_store_context *zc,
+ struct zswap_pool *pool,
+ struct obj_cgroup *objcg)
+{
+ u8 i, store_fail_idx = 0, nr_incomp = 0;
+ size_t total_batch_comp_length = 0;
+ struct zswap_entry *old, *entry;
for (i = 0; i < zc->nr_pages; ++i) {
- struct zswap_entry *old, *entry = zc->entries[i];
+ entry = zc->entries[i];
old = xa_store(swap_zswap_tree(entry->swpentry),
swp_offset(entry->swpentry),
@@ -1631,13 +1636,11 @@ static bool zswap_store_pages(struct zswap_store_context *zc,
zswap_reject_alloc_fail++;
/*
* Entries up to this point have been stored in the
- * xarray. zswap_store() will erase them from the xarray
- * and call zswap_entry_free(). Local cleanup in
- * 'store_pages_failed' only needs to happen for
- * entries from [@i to @nr_pages).
+ * xarray. Local cleanup in 'tree_store_failed' needs to
+ * erase entries up until now.
*/
store_fail_idx = i;
- goto store_pages_failed;
+ goto tree_store_failed;
}
/*
@@ -1648,22 +1651,10 @@ static bool zswap_store_pages(struct zswap_store_context *zc,
if (unlikely(old))
zswap_entry_free(old);
- /*
- * The entry is successfully compressed and stored in the tree,
- * and further failures will be cleaned up in zswap_store().
- * Grab refs to the pool and objcg, charge zswap memory, and
- * increment zswap_stored_pages. The opposite actions will be
- * performed by zswap_entry_free() when the entry is removed
- * from the tree.
- */
- zswap_pool_get(pool);
- if (objcg) {
- obj_cgroup_get(objcg);
- obj_cgroup_charge_zswap(objcg, entry->length);
- }
- atomic_long_inc(&zswap_stored_pages);
+ total_batch_comp_length += entry->length;
+
if (unlikely(entry->length == PAGE_SIZE))
- atomic_long_inc(&zswap_stored_incompressible_pages);
+ ++nr_incomp;
/*
* We finish by adding the entry to the LRU while it's already
@@ -1679,17 +1670,86 @@ static bool zswap_store_pages(struct zswap_store_context *zc,
zswap_lru_add(entry);
}
+ /*
+ * Each batch entry is successfully compressed and stored in the tree,
+ * and further failures will be cleaned up in zswap_store().
+ * Next, we do bulk accounting for the batch: grab refs to the pool and
+ * objcg, charge zswap memory, increment zswap_stored_pages and
+ * zswap_stored_incompressible_pages. The opposite actions will be
+ * performed by zswap_entry_free() when the entry is removed from the
+ * tree.
+ */
+ zswap_pool_get_many(pool, zc->nr_pages);
+ if (objcg) {
+ obj_cgroup_get_many(objcg, zc->nr_pages);
+ obj_cgroup_charge_zswap_many(objcg, total_batch_comp_length,
+ zc->nr_pages, nr_incomp);
+ }
+ atomic_long_add(zc->nr_pages, &zswap_stored_pages);
+ atomic_long_add(nr_incomp, &zswap_stored_incompressible_pages);
+
+ return true;
+
+tree_store_failed:
+ for (i = 0; i < store_fail_idx; ++i) {
+ xa_erase(swap_zswap_tree(zc->entries[i]->swpentry),
+ swp_offset(zc->entries[i]->swpentry));
+ zswap_lru_del(zc->entries[i]);
+ }
+
+ return false;
+}
+
+/*
+ * Store multiple pages in @zc->folio, starting from the page at index @start up
+ * to the page at index @end-1, i.e. @zc->nr_pages. Each phase of
+ * zswap_store_pages() does batch processing of @zc->nr_pages in the folio.
+ * State unwinding is kept to a minimum as a result of the batch processing: a
+ * batch proceeds to the next phase of store processing only if it passes this
+ * stage. The batch cleanup is consolidated under the @store_pages_failed
+ * label. Errors in storing a batch result in folio level cleanup occurring in
+ * zswap_store().
+ */
+static bool zswap_store_pages(struct zswap_store_context *zc,
+ long start,
+ long end,
+ struct zswap_pool *pool,
+ struct obj_cgroup *objcg)
+{
+ bool ret;
+ u8 i;
+
+ zc->nr_pages = end - start;
+ zc->folio_start = start;
+
+ VM_WARN_ON_ONCE(zc->nr_pages <= 0 || zc->nr_pages > ZSWAP_MAX_BATCH_SIZE);
+
+ ret = zswap_entries_cache_alloc_batch(zc, GFP_KERNEL);
+ if (unlikely(!ret))
+ return false;
+
+ zswap_init_entries(zc, pool, objcg);
+
+ for (i = 0; i < zc->nr_pages; ++i) {
+ if (!zswap_compress(zc->folio, zc->folio_start + i,
+ zc->entries[i], pool, zc->wb_enabled))
+ goto store_pages_failed;
+ }
+
+ ret = zswap_tree_stores(zc, pool, objcg);
+
+ if (unlikely(!ret))
+ goto store_pages_failed;
+
return true;
store_pages_failed:
- for (i = store_fail_idx; i < zc->nr_pages; ++i) {
+ for (i = 0; i < zc->nr_pages; ++i) {
if (!IS_ERR_VALUE(zc->entries[i]->handle))
zs_free(pool->zs_pool, zc->entries[i]->handle);
}
- zswap_entries_cache_free_batch(&zc->entries[store_fail_idx],
- zc->nr_pages - store_fail_idx);
-
+ zswap_entries_cache_free_batch(zc->entries, zc->nr_pages);
return false;
}
--
2.39.5
next prev parent reply other threads:[~2026-11-06 7:24 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 7:24 [PATCH v3 0/8] Batched zswap_store() framework with compression batching Kanchana P. Sridhar
2026-11-06 7:24 ` [PATCH v3 1/8] crypto: acomp - Define a unit_size in struct acomp_req to enable batching. mm: zswap: Set the unit size for zswap to PAGE_SIZE Kanchana P. Sridhar
2026-11-06 7:24 ` [PATCH v3 2/8] crypto: acomp - Add bit to indicate segmentation support Kanchana P. Sridhar
2026-11-06 7:24 ` [PATCH v3 3/8] crypto: acomp - Add trivial segmentation wrapper Kanchana P. Sridhar
2026-11-06 7:24 ` [PATCH v3 4/8] crypto: acomp - Add API to get a compression algorithm's batch-size Kanchana P. Sridhar
2026-11-06 7:24 ` [PATCH v3 5/8] mm: zswap: Store the folio's node id in the zswap_entry Kanchana P. Sridhar
2026-11-06 7:24 ` [PATCH v3 6/8] mm: zswap: Store large folios in batches Kanchana P. Sridhar
2026-11-06 7:24 ` Kanchana P. Sridhar [this message]
2026-11-06 7:24 ` [PATCH v3 8/8] mm: zswap: Batched zswap_compress() for storing " Kanchana P. Sridhar
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=20261106072412.7909-8-kanchanapsridhar2026@gmail.com \
--to=kanchanapsridhar2026@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=chengming.zhou@linux.dev \
--cc=davem@davemloft.net \
--cc=hannes@cmpxchg.org \
--cc=herbert@gondor.apana.org.au \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=nphamcs@gmail.com \
--cc=rientjes@google.com \
--cc=senozhatsky@chromium.org \
--cc=willy@infradead.org \
--cc=yosry@kernel.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®