Linux cgroups development
 help / color / mirror / Atom feed
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

  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