From: Michal Hocko <mhocko@suse.com>
To: Joanne Koong <joannelkoong@gmail.com>
Cc: Andrew Morton <akpm@linux-foundation.org>,
Shakeel Butt <shakeel.butt@linux.dev>,
Yosry Ahmed <yosry@kernel.org>,
Johannes Weiner <hannes@cmpxchg.org>,
Roman Gushchin <roman.gushchin@linux.dev>,
Muchun Song <muchun.song@linux.dev>,
cgroups@vger.kernel.org, linux-mm@kvack.org
Subject: Re: [PATCH v3 1/2] memcg: reduce cacheline contention between lruvec stat updates and LRU lists
Date: Mon, 26 Oct 2026 10:29:39 +0100 [thread overview]
Message-ID: <at8dg75u4qmQKZ9M@tiehlicka> (raw)
In-Reply-To: <20261023195417.1391216-2-joannelkoong@gmail.com>
On Fri 23-10-26 12:54:16, Joanne Koong wrote:
> Every memcg lruvec (per-node) stat update reads the memcg and
> lruvec_stats_percpu pointers in the first 64 bytes of struct
> mem_cgroup_per_node. These updates are extremely frequent (eg these
> updates happen several times for every page that's faulted in, freed,
> dirtied, or written back).
>
> The memcg_pn_read_mostly cacheline group keeps those read-mostly fields
> on a different cacheline from the lruvec, but Intel CPUs do spatial
> prefetching [1] and fetch cachelines in 128-byte aligned pairs.
> Depending on where struct mem_cgroup_per_node lands in its slab, that
> cacheline can be paired with the cacheline holding the LRU list heads.
>
> When this happens, every stat update also pulls in a copy of the LRU
> list heads cacheline, which is written to constantly by other CPUs under
> the lru_lock. The stat update has to fetch it from another CPU, which is
> unnecessary overhead since the stat update doesn't use the LRU list
> heads at all. But even worse, the lru_lock holder then has to invalidate
> those copies and wait for the acknowledgements to come back before it
> can do its next write, which makes the lru_lock critical section longer
> for everyone waiting on the lock.
>
> This was found when analyzing the output of perf c2c for a benchmark
> where many threads fault in pages in one memcg. It showed the first 64
> bytes of mem_cgroup_per_node as the most contended cacheline (over a
> third of all HITMs and 5x more than the lru_lock cacheline) even though
> there are no writes to it. A quick experiment adding extra padding so
> that the cacheline no longer shared a pairing with the LRU list heads
> showed that this removed most of the contention, which confirmed the
> cause.
>
> Fix this by moving the memcg_pn_cold group, which is only touched when
> the memcg is reparented or freed, right after memcg_pn_read_mostly, so
> that the read-mostly cacheline is paired with the cold cacheline
> instead of the LRU list heads.
>
> Results from a multi-threaded fault/free benchmark in a single memcg,
> run on an Intel Skylake machine (80 threads, 2 NUMA nodes) with threads
> pinned to CPUs showed average pages faulted in (and freed) per second
> across all threads, over 7 freshly created memcgs per run:
>
> before after
> shmem, 20 threads 4.74M 5.59M (+18%)
> shmem, 80 threads 7.94M 8.66M (+9%)
> anon, 20 threads 3.04M 3.08M (+1%)
>
> [1] https://www.intel.com/content/dam/doc/manual/64-ia-32-architectures-optimization-manual.pdf
> (bottom of page 62/800)
>
> Link: https://lore.kernel.org/linux-fsdevel/CAJnrk1ZezmmzrYdf8KP6re_a76izMS=qGPeC8ZZXiteHyZZC-Q@mail.gmail.com/
> Suggested-by: Shakeel Butt <shakeel.butt@linux.dev>
> Acked-by: Shakeel Butt <shakeel.butt@linux.dev>
> Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Thanks!
> ---
> include/linux/memcontrol.h | 29 ++++++++++++++++++-----------
> 1 file changed, 18 insertions(+), 11 deletions(-)
>
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index 69e1bcc67c58..6139195b7fa4 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -95,6 +95,24 @@ struct mem_cgroup_per_node {
>
> __cacheline_group_end_aligned(memcg_pn_read_mostly);
>
> + /*
> + * Touched only when the memcg is reparented or freed. Placed right
> + * after memcg_pn_read_mostly because CPUs with spatial prefetching
> + * (eg most Intel CPUs) fetch cachelines in 128-byte aligned pairs.
> + * This keeps the read-mostly line, which every lruvec stat update
> + * reads, from being paired with the LRU list heads, which are written
> + * constantly under lru_lock.
> + */
> + __cacheline_group_begin_aligned(memcg_pn_cold);
> + /*
> + * orig_objcg preserves a pointer (and a reference) to the original
> + * objcg until the end of life of memcg.
> + */
> + struct obj_cgroup *orig_objcg;
> + /* list of inherited objcgs, protected by objcg_lock */
> + struct list_head objcg_list;
> + __cacheline_group_end_aligned(memcg_pn_cold);
> +
> /*
> * Keep lruvec on its own lines. Sharing them with lru_zone_size[]
> * regressed, see commit f59adcf59332 ("mm: memcg: add cacheline
> @@ -114,17 +132,6 @@ struct mem_cgroup_per_node {
> atomic_t slab_unreclaimable;
> #endif
> __cacheline_group_end_aligned(memcg_pn_write_hot);
> -
> - /* Touched only when the memcg is reparented or freed. */
> - __cacheline_group_begin_aligned(memcg_pn_cold);
> - /*
> - * orig_objcg preserves a pointer (and a reference) to the original
> - * objcg until the end of life of memcg.
> - */
> - struct obj_cgroup *orig_objcg;
> - /* list of inherited objcgs, protected by objcg_lock */
> - struct list_head objcg_list;
> - __cacheline_group_end_aligned(memcg_pn_cold);
> };
>
> struct mem_cgroup_threshold {
> --
> 2.52.0
--
Michal Hocko
SUSE Labs
next prev parent reply other threads:[~2026-10-26 9:29 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 19:54 [PATCH v3 0/2] memcg: reduce cacheline contention from lruvec stat Joanne Koong
2026-10-23 19:54 ` [PATCH v3 1/2] memcg: reduce cacheline contention between lruvec stat updates and LRU lists Joanne Koong
2026-10-26 9:29 ` Michal Hocko [this message]
2026-10-23 19:54 ` [PATCH v3 2/2] memcg: don't go through the lruvec for folio and kmem lruvec stat updates Joanne Koong
2026-10-26 9:38 ` Michal Hocko
2026-10-23 22:54 ` [PATCH v3 0/2] memcg: reduce cacheline contention from lruvec stat Andrew Morton
2026-10-27 22:57 ` Joanne Koong
2026-10-28 2:24 ` Andrew Morton
2026-10-28 18:33 ` Joanne Koong
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=at8dg75u4qmQKZ9M@tiehlicka \
--to=mhocko@suse.com \
--cc=akpm@linux-foundation.org \
--cc=cgroups@vger.kernel.org \
--cc=hannes@cmpxchg.org \
--cc=joannelkoong@gmail.com \
--cc=linux-mm@kvack.org \
--cc=muchun.song@linux.dev \
--cc=roman.gushchin@linux.dev \
--cc=shakeel.butt@linux.dev \
--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