* [RFC PATCH v4 1/2] mm/zsmalloc: defer object free to per-cpu batches and a drain worker
2026-10-16 12:09 [RFC PATCH v4 0/2] mm/zsmalloc: defer object free to per-cpu batches Hongfeng Chen
@ 2026-10-16 12:09 ` Hongfeng Chen
2026-10-16 16:09 ` [RFC PATCH v4 0/2] mm/zsmalloc: defer object free to per-cpu batches Nhat Pham
2026-10-17 3:15 ` [RFC PATCH v4 2/2] zram: keep the hot statistics per cpu Hongfeng Chen
2 siblings, 0 replies; 5+ messages in thread
From: Hongfeng Chen @ 2026-10-16 12:09 UTC (permalink / raw)
To: Andrew Morton, Barry Song, Chengming Zhou, Jens Axboe,
Johannes Weiner, Minchan Kim, Nhat Pham, Sergey Senozhatsky,
Yosry Ahmed, Wenchao Hao, Wenchao Hao
Cc: linux-mm, linux-kernel, linux-block, Hongfeng Chen
Unmapping a VMA full of swap entries makes the unmapping task free every
compressed object itself, one zs_free() call per page. On Android this
slows down the exit of a task killed to reclaim memory.
Instead of freeing the object right away, zs_free() appends its handle to
a per-cpu batch of 64 handles. A full batch goes to an unbound drain worker
through a lockless list, and the worker frees the objects. This batches the
release and moves it off the unmapping path, and the change stays within
zsmalloc.
Freeing empty zspages is left to the worker too: it does that directly when
it can take the page locks, and reuses kick_deferred_free() to free them
asynchronously when it cannot.
Failing to allocate the per-cpu state is not fatal: pool->defer stays NULL
and zs_free() keeps its old path.
Signed-off-by: Hongfeng Chen <hongfengchen@xiaomi.com>
---
mm/zsmalloc.c | 182 ++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 154 insertions(+), 28 deletions(-)
diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c
index 7ef80e0da626..eacd771bd542 100644
--- a/mm/zsmalloc.c
+++ b/mm/zsmalloc.c
@@ -43,6 +43,7 @@
#include <linux/zsmalloc.h>
#include <linux/fs.h>
#include <linux/workqueue.h>
+#include <linux/llist.h>
#include "zpdesc.h"
#define ZSPAGE_MAGIC 0x58
@@ -267,6 +268,21 @@ struct link_free {
static struct kmem_cache *handle_cachep;
static struct kmem_cache *zspage_cachep;
+#define ZS_DEFER_BATCH 64
+
+struct zs_defer_batch {
+ struct llist_node node;
+ unsigned int count;
+ unsigned long handles[ZS_DEFER_BATCH];
+};
+
+struct zs_defer_percpu {
+ struct zs_defer_batch *active;
+ struct llist_head pending;
+ struct work_struct drain_work;
+ struct zs_pool *pool;
+};
+
struct zs_pool {
const char *name;
@@ -282,12 +298,12 @@ struct zs_pool {
#ifdef CONFIG_ZSMALLOC_STAT
struct dentry *stat_dentry;
#endif
-#ifdef CONFIG_COMPACTION
struct work_struct free_work;
-#endif
/* protect zspage migration/compaction */
rwlock_t lock;
atomic_t compaction_in_progress;
+
+ struct zs_defer_percpu __percpu *defer;
};
static inline void zpdesc_inc_zone_page_state(struct zpdesc *zpdesc)
@@ -428,13 +444,11 @@ static bool ZsHugePage(struct zspage *zspage)
return zspage->huge;
}
-#ifdef CONFIG_COMPACTION
static void kick_deferred_free(struct zs_pool *pool);
static void init_deferred_free(struct zs_pool *pool);
+#ifdef CONFIG_COMPACTION
static void SetZsPageMovable(struct zs_pool *pool, struct zspage *zspage);
#else
-static void kick_deferred_free(struct zs_pool *pool) {}
-static void init_deferred_free(struct zs_pool *pool) {}
static void SetZsPageMovable(struct zs_pool *pool, struct zspage *zspage) {}
#endif
@@ -684,7 +698,6 @@ static inline void zs_pool_stat_destroy(struct zs_pool *pool)
}
#endif
-
/*
* For each size class, zspages are divided into different groups
* depending on their usage ratio. This function returns fullness
@@ -1293,7 +1306,6 @@ static unsigned long obj_malloc(struct zs_pool *pool,
return obj;
}
-
/**
* zs_malloc - Allocate block of given size from pool.
* @pool: pool to allocate from
@@ -1371,7 +1383,6 @@ static void obj_free(int class_size, unsigned long obj)
unsigned int f_objidx;
void *vaddr;
-
obj_to_location(obj, &f_zpdesc, &f_objidx);
f_offset = offset_in_page(class_size * f_objidx);
zspage = get_zspage(f_zpdesc);
@@ -1447,7 +1458,7 @@ static inline void obj_class_get_and_lock(struct zs_pool *pool, unsigned long ha
}
#endif
-void zs_free(struct zs_pool *pool, unsigned long handle)
+static void __zs_free_one(struct zs_pool *pool, unsigned long handle)
{
struct zspage *zspage;
unsigned long obj;
@@ -1455,9 +1466,6 @@ void zs_free(struct zs_pool *pool, unsigned long handle)
int fullness;
struct zspage *zspage_to_free = NULL;
- if (IS_ERR_OR_NULL((void *)handle))
- return;
-
obj_class_get_and_lock(pool, handle, &obj, &zspage, &class);
class_stat_sub(class, ZS_OBJS_INUSE, 1);
@@ -1483,6 +1491,78 @@ void zs_free(struct zs_pool *pool, unsigned long handle)
}
cache_free_handle(handle);
}
+
+static void zs_defer_drain(struct zs_defer_percpu *defer)
+{
+ struct zs_pool *pool = defer->pool;
+ struct zs_defer_batch *batch, *n;
+ struct llist_node *first;
+
+ first = llist_del_all(&defer->pending);
+ llist_for_each_entry_safe(batch, n, first, node) {
+ unsigned int i;
+
+ for (i = 0; i < batch->count; i++)
+ __zs_free_one(pool, batch->handles[i]);
+ kfree(batch);
+ cond_resched();
+ }
+}
+
+static void zs_defer_drain_work(struct work_struct *work)
+{
+ zs_defer_drain(container_of(work, struct zs_defer_percpu, drain_work));
+}
+
+static bool zs_defer_push(struct zs_pool *pool, unsigned long handle)
+{
+ struct zs_defer_batch *new_batch = NULL, *batch;
+ struct zs_defer_percpu *defer;
+ bool queue = false;
+
+ if (!pool->defer)
+ return false;
+
+ defer = get_cpu_ptr(pool->defer);
+ if (!defer->active) {
+ put_cpu_ptr(pool->defer);
+ new_batch = kmalloc_obj(*new_batch, GFP_NOWAIT | __GFP_NOWARN);
+ if (!new_batch)
+ return false;
+ new_batch->count = 0;
+
+ defer = get_cpu_ptr(pool->defer);
+ if (!defer->active) {
+ defer->active = new_batch;
+ new_batch = NULL;
+ }
+ }
+
+ batch = defer->active;
+ batch->handles[batch->count++] = handle;
+ if (batch->count == ZS_DEFER_BATCH) {
+ llist_add(&batch->node, &defer->pending);
+ defer->active = NULL;
+ queue = true;
+ }
+ put_cpu_ptr(pool->defer);
+
+ if (unlikely(new_batch))
+ kfree(new_batch);
+
+ if (queue)
+ queue_work(system_dfl_wq, &defer->drain_work);
+ return true;
+}
+
+void zs_free(struct zs_pool *pool, unsigned long handle)
+{
+ if (IS_ERR_OR_NULL((void *)handle))
+ return;
+
+ if (!zs_defer_push(pool, handle))
+ __zs_free_one(pool, handle);
+}
EXPORT_SYMBOL_GPL(zs_free);
static void zs_object_copy(struct size_class *class, unsigned long dst,
@@ -1671,7 +1751,6 @@ static int putback_zspage(struct size_class *class, struct zspage *zspage)
return fullness;
}
-#ifdef CONFIG_COMPACTION
/*
* To prevent zspage destroy during migration, zspage freeing should
* hold locks of all pages in the zspage.
@@ -1714,6 +1793,8 @@ static void lock_zspage(struct zspage *zspage)
zspage_read_unlock(zspage);
}
+#ifdef CONFIG_COMPACTION
+
static void replace_sub_page(struct size_class *class, struct zspage *zspage,
struct zpdesc *newzpdesc, struct zpdesc *oldzpdesc)
{
@@ -1866,6 +1947,18 @@ const struct movable_operations zsmalloc_mops = {
.putback_page = zs_page_putback,
};
+static void SetZsPageMovable(struct zs_pool *pool, struct zspage *zspage)
+{
+ struct zpdesc *zpdesc = get_first_zpdesc(zspage);
+
+ do {
+ WARN_ON(!zpdesc_trylock(zpdesc));
+ __zpdesc_set_movable(zpdesc);
+ zpdesc_unlock(zpdesc);
+ } while ((zpdesc = get_next_zpdesc(zpdesc)) != NULL);
+}
+#endif /* CONFIG_COMPACTION */
+
/*
* Caller should hold page_lock of all pages in the zspage
* In here, we cannot use zspage meta data.
@@ -1904,7 +1997,7 @@ static void async_free_zspage(struct work_struct *work)
static void kick_deferred_free(struct zs_pool *pool)
{
- schedule_work(&pool->free_work);
+ queue_work(system_dfl_wq, &pool->free_work);
}
static void zs_flush_migration(struct zs_pool *pool)
@@ -1917,20 +2010,6 @@ static void init_deferred_free(struct zs_pool *pool)
INIT_WORK(&pool->free_work, async_free_zspage);
}
-static void SetZsPageMovable(struct zs_pool *pool, struct zspage *zspage)
-{
- struct zpdesc *zpdesc = get_first_zpdesc(zspage);
-
- do {
- WARN_ON(!zpdesc_trylock(zpdesc));
- __zpdesc_set_movable(zpdesc);
- zpdesc_unlock(zpdesc);
- } while ((zpdesc = get_next_zpdesc(zpdesc)) != NULL);
-}
-#else
-static inline void zs_flush_migration(struct zs_pool *pool) { }
-#endif
-
/*
*
* Based on the number of unused allocated objects calculate
@@ -2127,6 +2206,48 @@ static int calculate_zspage_chain_size(int class_size)
return chain_size;
}
+static void zs_defer_destroy(struct zs_pool *pool)
+{
+ struct zs_defer_percpu __percpu *defer_pcp = pool->defer;
+ int cpu;
+
+ if (!defer_pcp)
+ return;
+
+ pool->defer = NULL;
+ for_each_possible_cpu(cpu) {
+ struct zs_defer_percpu *defer = per_cpu_ptr(defer_pcp, cpu);
+
+ cancel_work_sync(&defer->drain_work);
+ if (defer->active) {
+ llist_add(&defer->active->node, &defer->pending);
+ defer->active = NULL;
+ }
+ zs_defer_drain(defer);
+ }
+ free_percpu(defer_pcp);
+}
+
+static int zs_defer_init(struct zs_pool *pool)
+{
+ int cpu;
+
+ pool->defer = alloc_percpu(struct zs_defer_percpu);
+ if (!pool->defer)
+ return -ENOMEM;
+
+ for_each_possible_cpu(cpu) {
+ struct zs_defer_percpu *defer = per_cpu_ptr(pool->defer, cpu);
+
+ defer->active = NULL;
+ init_llist_head(&defer->pending);
+ defer->pool = pool;
+ INIT_WORK(&defer->drain_work, zs_defer_drain_work);
+ }
+
+ return 0;
+}
+
/**
* zs_create_pool - Creates an allocation pool to work from.
* @name: pool name to be created
@@ -2151,6 +2272,10 @@ struct zs_pool *zs_create_pool(const char *name)
rwlock_init(&pool->lock);
atomic_set(&pool->compaction_in_progress, 0);
+ if (zs_defer_init(pool))
+ pr_info("deferred free unavailable for <%s>, freeing synchronously\n",
+ name);
+
pool->name = kstrdup(name, GFP_KERNEL);
if (!pool->name)
goto err;
@@ -2253,6 +2378,7 @@ void zs_destroy_pool(struct zs_pool *pool)
int i;
zs_unregister_shrinker(pool);
+ zs_defer_destroy(pool);
zs_flush_migration(pool);
zs_pool_stat_destroy(pool);
--
2.54.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [RFC PATCH v4 2/2] zram: keep the hot statistics per cpu
2026-10-16 12:09 [RFC PATCH v4 0/2] mm/zsmalloc: defer object free to per-cpu batches Hongfeng Chen
2026-10-16 12:09 ` [RFC PATCH v4 1/2] mm/zsmalloc: defer object free to per-cpu batches and a drain worker Hongfeng Chen
2026-10-16 16:09 ` [RFC PATCH v4 0/2] mm/zsmalloc: defer object free to per-cpu batches Nhat Pham
@ 2026-10-17 3:15 ` Hongfeng Chen
2 siblings, 0 replies; 5+ messages in thread
From: Hongfeng Chen @ 2026-10-17 3:15 UTC (permalink / raw)
To: Andrew Morton, Barry Song, Chengming Zhou, Jens Axboe,
Johannes Weiner, Minchan Kim, Nhat Pham, Sergey Senozhatsky,
Yosry Ahmed, Wenchao Hao, Wenchao Hao
Cc: linux-mm, linux-kernel, linux-block, Hongfeng Chen, chenhongfeng0212
From: Hongfeng Chen <hongfengchen@xiaomi.com>
pages_stored, compr_data_size, same_pages, huge_pages and notify_free are
updated on every page that zram stores or releases. When several processes
munmap() swapped out memory at the same time, all cpus update these
counters, and the cacheline that holds them bounces between cpus.
Move the five counters into a per cpu structure and update them through
zram_pcpu_add(), which disables preemption and adds to the value of the
current cpu. All writers run in process context, so disabling preemption is
enough. The store is WRITE_ONCE() and the remote reads are READ_ONCE(),
since a reader on another cpu can see the value at any time. A sysfs read
sums the per cpu values without a snapshot across cpus, so a sum can be
briefly negative and is clamped at zero.
slot_free() and the write, recompression and writeback paths update all of
their counters while preemption is disabled once. Disabling preemption and
finding the per cpu data has a cost, and this way it is paid once per slot
instead of once per counter.
u64_stats_sync keeps 64-bit reads from tearing on 32-bit systems and is
empty on 64-bit systems.
Signed-off-by: Hongfeng Chen <hongfengchen@xiaomi.com>
---
drivers/block/zram/zram_drv.c | 171 ++++++++++++++++++++++++++++------
drivers/block/zram/zram_drv.h | 16 +++-
2 files changed, 155 insertions(+), 32 deletions(-)
diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c
index 024402438bd1..affe758a9543 100644
--- a/drivers/block/zram/zram_drv.c
+++ b/drivers/block/zram/zram_drv.c
@@ -488,6 +488,56 @@ static ssize_t idle_store(struct device *dev, struct device_attribute *attr,
return len;
}
+struct zram_stats_sum {
+ s64 pages_stored;
+ s64 compr_data_size;
+ s64 same_pages;
+ s64 huge_pages;
+ u64 notify_free;
+};
+
+#define zram_pcpu_add(zram_dev, field, val) \
+do { \
+ struct zram *__zram = (zram_dev); \
+ struct zram_pcpu_stats *__stats; \
+ \
+ __stats = get_cpu_ptr(__zram->pcpu_stats); \
+ u64_stats_update_begin(&__stats->syncp); \
+ WRITE_ONCE(__stats->field, __stats->field + (val)); \
+ u64_stats_update_end(&__stats->syncp); \
+ put_cpu_ptr(__zram->pcpu_stats); \
+} while (0)
+
+static void zram_pcpu_sum(struct zram *zram, struct zram_stats_sum *sum)
+{
+ int cpu;
+
+ memset(sum, 0, sizeof(*sum));
+
+ for_each_possible_cpu(cpu) {
+ struct zram_pcpu_stats *stats;
+ unsigned int start;
+ s64 pages, bytes, same, huge;
+ u64 notify;
+
+ stats = per_cpu_ptr(zram->pcpu_stats, cpu);
+ do {
+ start = u64_stats_fetch_begin(&stats->syncp);
+ pages = READ_ONCE(stats->pages_stored);
+ bytes = READ_ONCE(stats->compr_data_size);
+ same = READ_ONCE(stats->same_pages);
+ huge = READ_ONCE(stats->huge_pages);
+ notify = READ_ONCE(stats->notify_free);
+ } while (u64_stats_fetch_retry(&stats->syncp, start));
+
+ sum->pages_stored += pages;
+ sum->compr_data_size += bytes;
+ sum->same_pages += same;
+ sum->huge_pages += huge;
+ sum->notify_free += notify;
+ }
+}
+
#ifdef CONFIG_ZRAM_WRITEBACK
#define INVALID_BDEV_BLOCK (~0UL)
@@ -919,6 +969,8 @@ static void zram_account_writeback_submit(struct zram *zram)
static int zram_writeback_complete(struct zram *zram, struct zram_wb_req *req)
{
unsigned long index = req->pps->index;
+ struct zram_pcpu_stats *stats;
+ s64 huge, size;
int err;
err = blk_status_to_errno(req->bio.bi_status);
@@ -947,9 +999,14 @@ static int zram_writeback_complete(struct zram *zram, struct zram_wb_req *req)
}
clear_slot_flag(zram, index, ZRAM_IDLE);
- if (test_slot_flag(zram, index, ZRAM_HUGE))
- atomic64_dec(&zram->stats.huge_pages);
- atomic64_sub(get_slot_size(zram, index), &zram->stats.compr_data_size);
+ huge = test_slot_flag(zram, index, ZRAM_HUGE) ? -1 : 0;
+ size = get_slot_size(zram, index);
+ stats = get_cpu_ptr(zram->pcpu_stats);
+ u64_stats_update_begin(&stats->syncp);
+ WRITE_ONCE(stats->huge_pages, stats->huge_pages + huge);
+ WRITE_ONCE(stats->compr_data_size, stats->compr_data_size - size);
+ u64_stats_update_end(&stats->syncp);
+ put_cpu_ptr(zram->pcpu_stats);
zs_free(zram->mem_pool, get_slot_handle(zram, index));
set_slot_handle(zram, index, req->blk_idx);
set_slot_flag(zram, index, ZRAM_WB);
@@ -1919,14 +1976,16 @@ static ssize_t io_stat_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct zram *zram = dev_to_zram(dev);
+ struct zram_stats_sum sum;
ssize_t ret;
guard(rwsem_read)(&zram->dev_lock);
+ zram_pcpu_sum(zram, &sum);
ret = sysfs_emit(buf,
"%8llu %8llu 0 %8llu\n",
(u64)atomic64_read(&zram->stats.failed_reads),
(u64)atomic64_read(&zram->stats.failed_writes),
- (u64)atomic64_read(&zram->stats.notify_free));
+ sum.notify_free);
return ret;
}
@@ -1936,6 +1995,7 @@ static ssize_t mm_stat_show(struct device *dev, struct device_attribute *attr,
{
struct zram *zram = dev_to_zram(dev);
struct zs_pool_stats pool_stats;
+ struct zram_stats_sum sum;
u64 orig_size, mem_used = 0;
long max_used;
ssize_t ret;
@@ -1948,19 +2008,20 @@ static ssize_t mm_stat_show(struct device *dev, struct device_attribute *attr,
zs_pool_stats(zram->mem_pool, &pool_stats);
}
- orig_size = atomic64_read(&zram->stats.pages_stored);
+ zram_pcpu_sum(zram, &sum);
+ orig_size = max_t(s64, sum.pages_stored, 0);
max_used = atomic_long_read(&zram->stats.max_used_pages);
ret = sysfs_emit(buf,
"%8llu %8llu %8llu %8lu %8ld %8llu %8lu %8llu %8llu\n",
orig_size << PAGE_SHIFT,
- (u64)atomic64_read(&zram->stats.compr_data_size),
+ (u64)max_t(s64, sum.compr_data_size, 0),
mem_used << PAGE_SHIFT,
zram->limit_pages << PAGE_SHIFT,
max_used << PAGE_SHIFT,
- (u64)atomic64_read(&zram->stats.same_pages),
+ (u64)max_t(s64, sum.same_pages, 0),
atomic_long_read(&pool_stats.pages_compacted),
- (u64)atomic64_read(&zram->stats.huge_pages),
+ (u64)max_t(s64, sum.huge_pages, 0),
(u64)atomic64_read(&zram->stats.huge_pages_since));
return ret;
@@ -2027,6 +2088,8 @@ static bool zram_meta_alloc(struct zram *zram, u64 disksize)
static void slot_free(struct zram *zram, unsigned long index)
{
+ s64 pages = 0, bytes = 0, same = 0, huge = 0;
+ struct zram_pcpu_stats *stats;
unsigned long handle;
#ifdef CONFIG_ZRAM_TRACK_ENTRY_ACTIME
@@ -2044,7 +2107,7 @@ static void slot_free(struct zram *zram, unsigned long index)
* ZRAM_HUGE flag for deferred decompression path.
*/
if (!test_slot_flag(zram, index, ZRAM_WB))
- atomic64_dec(&zram->stats.huge_pages);
+ huge = -1;
clear_slot_flag(zram, index, ZRAM_HUGE);
}
@@ -2060,22 +2123,32 @@ static void slot_free(struct zram *zram, unsigned long index)
*/
if (test_slot_flag(zram, index, ZRAM_SAME)) {
clear_slot_flag(zram, index, ZRAM_SAME);
- atomic64_dec(&zram->stats.same_pages);
+ same = -1;
goto out;
}
handle = get_slot_handle(zram, index);
if (!handle)
- return;
+ goto stats;
zs_free(zram->mem_pool, handle);
- atomic64_sub(get_slot_size(zram, index),
- &zram->stats.compr_data_size);
+ bytes = -(s64)get_slot_size(zram, index);
out:
- atomic64_dec(&zram->stats.pages_stored);
+ pages = -1;
set_slot_handle(zram, index, 0);
set_slot_size(zram, index, 0);
+stats:
+ if (!(pages | bytes | same | huge))
+ return;
+ stats = get_cpu_ptr(zram->pcpu_stats);
+ u64_stats_update_begin(&stats->syncp);
+ WRITE_ONCE(stats->pages_stored, stats->pages_stored + pages);
+ WRITE_ONCE(stats->compr_data_size, stats->compr_data_size + bytes);
+ WRITE_ONCE(stats->same_pages, stats->same_pages + same);
+ WRITE_ONCE(stats->huge_pages, stats->huge_pages + huge);
+ u64_stats_update_end(&stats->syncp);
+ put_cpu_ptr(zram->pcpu_stats);
}
static int read_same_filled_page(struct zram *zram, struct page *page,
@@ -2237,14 +2310,20 @@ static int zram_bvec_read(struct zram *zram, struct bio_vec *bvec,
static int write_same_filled_page(struct zram *zram, unsigned long fill,
unsigned long index)
{
+ struct zram_pcpu_stats *stats;
+
slot_lock(zram, index);
slot_free(zram, index);
set_slot_flag(zram, index, ZRAM_SAME);
set_slot_handle(zram, index, fill);
slot_unlock(zram, index);
- atomic64_inc(&zram->stats.same_pages);
- atomic64_inc(&zram->stats.pages_stored);
+ stats = get_cpu_ptr(zram->pcpu_stats);
+ u64_stats_update_begin(&stats->syncp);
+ WRITE_ONCE(stats->same_pages, stats->same_pages + 1);
+ WRITE_ONCE(stats->pages_stored, stats->pages_stored + 1);
+ u64_stats_update_end(&stats->syncp);
+ put_cpu_ptr(zram->pcpu_stats);
return 0;
}
@@ -2252,6 +2331,7 @@ static int write_same_filled_page(struct zram *zram, unsigned long fill,
static int write_incompressible_page(struct zram *zram, struct page *page,
unsigned long index)
{
+ struct zram_pcpu_stats *stats;
unsigned long handle;
void *src;
@@ -2282,10 +2362,14 @@ static int write_incompressible_page(struct zram *zram, struct page *page,
set_slot_size(zram, index, PAGE_SIZE);
slot_unlock(zram, index);
- atomic64_add(PAGE_SIZE, &zram->stats.compr_data_size);
- atomic64_inc(&zram->stats.huge_pages);
+ stats = get_cpu_ptr(zram->pcpu_stats);
+ u64_stats_update_begin(&stats->syncp);
+ WRITE_ONCE(stats->compr_data_size, stats->compr_data_size + PAGE_SIZE);
+ WRITE_ONCE(stats->huge_pages, stats->huge_pages + 1);
+ WRITE_ONCE(stats->pages_stored, stats->pages_stored + 1);
+ u64_stats_update_end(&stats->syncp);
+ put_cpu_ptr(zram->pcpu_stats);
atomic64_inc(&zram->stats.huge_pages_since);
- atomic64_inc(&zram->stats.pages_stored);
return 0;
}
@@ -2293,6 +2377,7 @@ static int write_incompressible_page(struct zram *zram, struct page *page,
static int zram_write_page(struct zram *zram, struct page *page,
unsigned long index)
{
+ struct zram_pcpu_stats *stats;
int ret = 0;
unsigned long handle;
unsigned int comp_len;
@@ -2348,8 +2433,12 @@ static int zram_write_page(struct zram *zram, struct page *page,
slot_unlock(zram, index);
/* Update stats */
- atomic64_inc(&zram->stats.pages_stored);
- atomic64_add(comp_len, &zram->stats.compr_data_size);
+ stats = get_cpu_ptr(zram->pcpu_stats);
+ u64_stats_update_begin(&stats->syncp);
+ WRITE_ONCE(stats->pages_stored, stats->pages_stored + 1);
+ WRITE_ONCE(stats->compr_data_size, stats->compr_data_size + comp_len);
+ u64_stats_update_end(&stats->syncp);
+ put_cpu_ptr(zram->pcpu_stats);
return ret;
}
@@ -2449,6 +2538,7 @@ static int recompress_slot(struct zram *zram, unsigned long index,
u32 threshold, u32 prio)
{
struct zcomp_strm *zstrm = NULL;
+ struct zram_pcpu_stats *stats;
unsigned long handle_old;
unsigned long handle_new;
unsigned int comp_len_old;
@@ -2545,8 +2635,12 @@ static int recompress_slot(struct zram *zram, unsigned long index,
set_slot_size(zram, index, comp_len_new);
set_slot_comp_priority(zram, index, prio);
- atomic64_add(comp_len_new, &zram->stats.compr_data_size);
- atomic64_inc(&zram->stats.pages_stored);
+ stats = get_cpu_ptr(zram->pcpu_stats);
+ u64_stats_update_begin(&stats->syncp);
+ WRITE_ONCE(stats->compr_data_size, stats->compr_data_size + comp_len_new);
+ WRITE_ONCE(stats->pages_stored, stats->pages_stored + 1);
+ u64_stats_update_end(&stats->syncp);
+ put_cpu_ptr(zram->pcpu_stats);
return 0;
}
@@ -2724,7 +2818,7 @@ static void zram_bio_discard(struct zram *zram, struct bio *bio)
slot_lock(zram, index);
slot_free(zram, index);
slot_unlock(zram, index);
- atomic64_inc(&zram->stats.notify_free);
+ zram_pcpu_add(zram, notify_free, 1);
index++;
n -= PAGE_SIZE;
}
@@ -2825,7 +2919,7 @@ static void zram_slot_free_notify(struct block_device *bdev,
zram = bdev->bd_disk->private_data;
- atomic64_inc(&zram->stats.notify_free);
+ zram_pcpu_add(zram, notify_free, 1);
if (!slot_trylock(zram, index)) {
atomic64_inc(&zram->stats.miss_free);
return;
@@ -2866,6 +2960,8 @@ static void zram_destroy_comps(struct zram *zram)
static void zram_reset_device(struct zram *zram)
{
+ int cpu;
+
guard(rwsem_write)(&zram->dev_lock);
zram->limit_pages = 0;
@@ -2877,6 +2973,16 @@ static void zram_reset_device(struct zram *zram)
zram_meta_free(zram, zram->disksize);
zram->disksize = 0;
zram_destroy_comps(zram);
+ for_each_possible_cpu(cpu) {
+ struct zram_pcpu_stats *stats;
+
+ stats = per_cpu_ptr(zram->pcpu_stats, cpu);
+ stats->pages_stored = 0;
+ stats->compr_data_size = 0;
+ stats->same_pages = 0;
+ stats->huge_pages = 0;
+ stats->notify_free = 0;
+ }
memset(&zram->stats, 0, sizeof(zram->stats));
reset_bdev(zram);
}
@@ -3083,15 +3189,23 @@ static int zram_add(void)
BLK_FEAT_SYNCHRONOUS,
};
struct zram *zram;
- int ret, device_id;
+ int cpu, ret, device_id;
zram = kzalloc_obj(struct zram);
if (!zram)
return -ENOMEM;
+ zram->pcpu_stats = alloc_percpu(struct zram_pcpu_stats);
+ if (!zram->pcpu_stats) {
+ ret = -ENOMEM;
+ goto out_free_dev;
+ }
+ for_each_possible_cpu(cpu)
+ u64_stats_init(&per_cpu_ptr(zram->pcpu_stats, cpu)->syncp);
+
ret = idr_alloc(&zram_index_idr, zram, 0, 0, GFP_KERNEL);
if (ret < 0)
- goto out_free_dev;
+ goto out_free_pcpu;
device_id = ret;
init_rwsem(&zram->dev_lock);
@@ -3133,6 +3247,8 @@ static int zram_add(void)
put_disk(zram->disk);
out_free_idr:
idr_remove(&zram_index_idr, device_id);
+out_free_pcpu:
+ free_percpu(zram->pcpu_stats);
out_free_dev:
kfree(zram);
return ret;
@@ -3182,6 +3298,7 @@ static int zram_remove(struct zram *zram)
zram_reset_device(zram);
put_disk(zram->disk);
+ free_percpu(zram->pcpu_stats);
kfree(zram);
return 0;
}
diff --git a/drivers/block/zram/zram_drv.h b/drivers/block/zram/zram_drv.h
index 7a55d751417e..482e1c28ca1c 100644
--- a/drivers/block/zram/zram_drv.h
+++ b/drivers/block/zram/zram_drv.h
@@ -17,6 +17,7 @@
#include <asm/byteorder.h>
#include <linux/rwsem.h>
+#include <linux/u64_stats_sync.h>
#include <linux/zsmalloc.h>
#include "zcomp.h"
@@ -90,15 +91,19 @@ struct zram_table_entry {
};
};
+struct zram_pcpu_stats {
+ struct u64_stats_sync syncp;
+ s64 pages_stored; /* no. of pages currently stored */
+ s64 compr_data_size; /* compressed size of pages stored */
+ s64 same_pages; /* no. of same element filled pages */
+ s64 huge_pages; /* no. of huge pages */
+ u64 notify_free; /* no. of swap slot free notifications */
+};
+
struct zram_stats {
- atomic64_t compr_data_size; /* compressed size of pages stored */
atomic64_t failed_reads; /* can happen when memory is too low */
atomic64_t failed_writes; /* can happen when memory is too low */
- atomic64_t notify_free; /* no. of swap slot free notifications */
- atomic64_t same_pages; /* no. of same element filled pages */
- atomic64_t huge_pages; /* no. of huge pages */
atomic64_t huge_pages_since; /* no. of huge pages since zram set up */
- atomic64_t pages_stored; /* no. of pages currently stored */
atomic_long_t max_used_pages; /* no. of maximum pages stored */
atomic64_t miss_free; /* no. of missed free */
#ifdef CONFIG_ZRAM_WRITEBACK
@@ -134,6 +139,7 @@ struct zram {
unsigned long limit_pages;
struct zram_stats stats;
+ struct zram_pcpu_stats __percpu *pcpu_stats;
/*
* This is the limit on amount of *uncompressed* worth of data
* we can store in a disk.
--
2.54.0
^ permalink raw reply [flat|nested] 5+ messages in thread