From: Joshua Hahn <joshua.hahnjy@gmail.com>
To: Joshua Hahn <joshua.hahnjy@gmail.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>,
Michal Hocko <mhocko@kernel.org>,
Roman Gushchin <roman.gushchin@linux.dev>,
Shakeel Butt <shakeel.butt@linux.dev>,
Muchun Song <muchun.song@linux.dev>,
Andrew Morton <akpm@linux-foundation.org>,
David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R . Howlett" <liam.howlett@oracle.com>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
cgroups@vger.kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, kernel-team@meta.com
Subject: Re: [PATCH 1/7 v3] mm/page_counter: introduce per-page_counter stock
Date: Mon, 25 May 2026 12:45:04 -0700 [thread overview]
Message-ID: <20260525194506.3414995-1-joshua.hahnjy@gmail.com> (raw)
In-Reply-To: <20260525190455.2843786-2-joshua.hahnjy@gmail.com>
On Mon, 25 May 2026 12:04:48 -0700 Joshua Hahn <joshua.hahnjy@gmail.com> wrote:
> In order to avoid expensive hierarchy walks on every memcg charge and
> limit check, memcontrol uses per-cpu stocks (memcg_stock_pcp) to cache
> pre-charged pages and introduce a fast path to try_charge_memcg.
>
> However, there are a few quirks with the current implementation that
> could be improved upon.
>
> First, each memcg_stock_pcp can only cache the charges of 7 memcgs
> (defined as NR_MEMCG_STOCK), which means that once a CPU starts handling
> the charging of more than 7 memcgs, it randomly selects a victim memcg
> to evict and drain from the cpu, which can cause unnecessarily increased
> latencies and thrashing as memcgs continually evict each others' stock.
>
> Second, stock is tightly coupled with memcg, which means that all page
> counters in a memcg share the same resource. This may simplify some of
> the charging logic, but it prevents new page counters from being added
> and using a separate stock.
>
> We can address these concerns by pushing the concept of stock down to
> the page_counter level, which addresses the random eviction problem by
> getting rid of the 7 slot limit, and makes enabling separate stock
> caches for other page_counters simpler.
>
> Introduce a generic per-cpu stock directly in struct page_counter.
> Stock can optionally be enabled per-page_counter, limiting the overhead
> increase for page_counters who do not benefit greatly from caching
> charges.
>
> This patch introduces the page_counter_stock struct and its
> enable/disable/free functions, but does not use these yet.
>
> Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
> Signed-off-by: Joshua Hahn <joshua.hahnjy@gmail.com>
> ---
> include/linux/page_counter.h | 13 ++++++++
> mm/page_counter.c | 64 ++++++++++++++++++++++++++++++++++++
> 2 files changed, 77 insertions(+)
>
> diff --git a/include/linux/page_counter.h b/include/linux/page_counter.h
> index d649b6bbbc871..c7e3ab3356d20 100644
> --- a/include/linux/page_counter.h
> +++ b/include/linux/page_counter.h
> @@ -5,8 +5,15 @@
> #include <linux/atomic.h>
> #include <linux/cache.h>
> #include <linux/limits.h>
> +#include <linux/local_lock.h>
> +#include <linux/percpu.h>
> #include <asm/page.h>
>
> +struct page_counter_stock {
> + local_trylock_t lock;
> + unsigned long nr_pages;
> +};
> +
> struct page_counter {
> /*
> * Make sure 'usage' does not share cacheline with any other field in
> @@ -41,6 +48,8 @@ struct page_counter {
> unsigned long high;
> unsigned long max;
> struct page_counter *parent;
> + struct page_counter_stock __percpu *stock;
> + unsigned int batch;
> } ____cacheline_internodealigned_in_smp;
>
> #if BITS_PER_LONG == 32
> @@ -99,6 +108,10 @@ static inline void page_counter_reset_watermark(struct page_counter *counter)
> counter->watermark = usage;
> }
>
> +int page_counter_enable_stock(struct page_counter *counter, unsigned int batch);
> +void page_counter_disable_stock(struct page_counter *counter);
> +void page_counter_free_stock(struct page_counter *counter);
> +
> #if IS_ENABLED(CONFIG_MEMCG) || IS_ENABLED(CONFIG_CGROUP_DMEM)
> void page_counter_calculate_protection(struct page_counter *root,
> struct page_counter *counter,
> diff --git a/mm/page_counter.c b/mm/page_counter.c
> index 661e0f2a5127a..a1a871a9d5c49 100644
> --- a/mm/page_counter.c
> +++ b/mm/page_counter.c
> @@ -8,6 +8,7 @@
> #include <linux/page_counter.h>
> #include <linux/atomic.h>
> #include <linux/kernel.h>
> +#include <linux/percpu.h>
> #include <linux/string.h>
> #include <linux/sched.h>
> #include <linux/bug.h>
> @@ -289,6 +290,69 @@ int page_counter_memparse(const char *buf, const char *max,
> return 0;
> }
>
> +int page_counter_enable_stock(struct page_counter *counter, unsigned int batch)
> +{
> + struct page_counter_stock __percpu *stock;
> + int cpu;
> +
> + stock = alloc_percpu(struct page_counter_stock);
> + if (!stock)
> + return -ENOMEM;
> +
> + for_each_possible_cpu(cpu) {
> + struct page_counter_stock *s = per_cpu_ptr(stock, cpu);
> +
> + local_trylock_init(&s->lock);
> + }
> + counter->stock = stock;
> + counter->batch = batch;
> +
> + return 0;
> +}
> +
> +static void page_counter_drain_stock_nolock(struct page_counter *counter)
> +{
> + unsigned long stock_to_drain = 0;
> + int cpu;
> +
> + for_each_possible_cpu(cpu) {
> + struct page_counter_stock *stock;
> +
> + stock = per_cpu_ptr(counter->stock, cpu);
> + stock_to_drain += stock->nr_pages;
> + stock->nr_pages = 0;
> + }
> +
> + if (stock_to_drain)
> + page_counter_uncharge(counter, stock_to_drain);
> +}
> +
> +void page_counter_disable_stock(struct page_counter *counter)
> +{
> + if (!counter->stock)
> + return;
> +
> + /* This prevents future charges from trying to deposit pages */
> + WRITE_ONCE(counter->batch, 0);
> +
> + /*
> + * Charges can still be in-flight at this time. Instead of locking here,
> + * do the majority of the drains here without locking to free up pages
> + * now. Any remaining stock will be drained in page_counter_free_stock.
> + */
> + page_counter_drain_stock_nolock(counter);
Sashiko raised a concern here.
Allowing racy charges is OK, but the problem is that writing stock->nr_pages = 0
with no lock here can race with the reading of that value, and lead to
double-uncharging when racing with concurrent charges.
I think that this can be fixed by not draining in disable_stock and
reordering the callsite:
Before:
drain_all_stock(memcg);
page_counter_disable_stock(&memcg->memory);
After:
page_counter_disable_stock(&memcg->memory);
drain_all_stock(memcg);
This way, the WRITE_ONCE(counter->batch, 0); should prevent any
future charges from trying to land, before we drain stock.
Despite not allowing any more racy charges, we still need to keep the drain
in free_stock since drain_all_stock() uses a mutex_trylock and can fail;
if that happens we need to still drain the stock at a later time, when we
can guarantee that there will be no more contention.
Thanks, Sashiko! I'll keep my eyes posted on the rest of the series as it
goes through the review pipeline.
Joshua
> +}
> +
> +void page_counter_free_stock(struct page_counter *counter)
> +{
> + if (!counter->stock)
> + return;
> +
> + page_counter_drain_stock_nolock(counter);
> + free_percpu(counter->stock);
> + counter->stock = NULL;
> +}
> +
next prev parent reply other threads:[~2026-05-25 19:45 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-25 19:04 [PATCH 0/7 v3] mm/memcontrol, page_counter: move stock from mem_cgroup to page_counter Joshua Hahn
2026-05-25 19:04 ` [PATCH 1/7 v3] mm/page_counter: introduce per-page_counter stock Joshua Hahn
2026-05-25 19:45 ` Joshua Hahn [this message]
2026-05-25 19:04 ` [PATCH 2/7 v3] mm/page_counter: use page_counter_stock in page_counter_try_charge Joshua Hahn
2026-05-25 19:04 ` [PATCH 3/7 v3] mm/page_counter: introduce stock drain APIs Joshua Hahn
2026-05-25 19:04 ` [PATCH 4/7 v3] mm/memcontrol: convert memcg to use page_counter_stock Joshua Hahn
2026-05-25 19:04 ` [PATCH 5/7 v3] mm/memcontrol: optimize memsw stock for cgroup v1 Joshua Hahn
2026-05-25 19:04 ` [PATCH 6/7 v3] mm/memcontrol: optimize stock usage for cgroup v2 Joshua Hahn
2026-05-25 19:04 ` [PATCH 7/7 v3] mm/memcontrol: remove unused memcg_stock code Joshua Hahn
2026-05-26 15:16 ` [PATCH 0/7 v3] mm/memcontrol, page_counter: move stock from mem_cgroup to page_counter Joshua Hahn
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=20260525194506.3414995-1-joshua.hahnjy@gmail.com \
--to=joshua.hahnjy@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=cgroups@vger.kernel.org \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=kernel-team@meta.com \
--cc=liam.howlett@oracle.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@kernel.org \
--cc=muchun.song@linux.dev \
--cc=roman.gushchin@linux.dev \
--cc=rppt@kernel.org \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=vbabka@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®