From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtpout.efficios.com (smtpout.efficios.com [158.69.130.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5AFDD417343 for ; Tue, 7 Jul 2026 13:16:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=158.69.130.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783430181; cv=none; b=W3qmy66yjOS9KPFyPhpSrzO43ayntM8XAuA5oJi1uEu9Y+MrNHQjiXg0B/iLNGX8qIRtzpC5rmnfY3S9CPVGdlpnLl4s3EEYo+msm2/qLHi8sot8vVvGUxFkuh9TjI2ZTULvnVz0DXclR2WHOrbzG1euhnZnn/yj7DvprUPiED8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783430181; c=relaxed/simple; bh=/MJfL9R3fs/CERe81BfLJwZBy7td/ssC2o3J7vlneww=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WueWft8sE19GGPGkfFQgenBueG+xJuXq+grxkDi6CaTC7Lvjt7RkLDxh4mb//ivWXeoqIoY3P3Rc46l7ITvxA0X40wzeYRtWGpEH+sppBm4bDjy5TN14vbj+e9QvoA0GdYeW5R+EpTmbny6EX5AfKYiHCFaUEGcrJViIavfHK0o= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=efficios.com; spf=pass smtp.mailfrom=efficios.com; dkim=pass (2048-bit key) header.d=efficios.com header.i=@efficios.com header.b=D0uBITgw; arc=none smtp.client-ip=158.69.130.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=efficios.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=efficios.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=efficios.com header.i=@efficios.com header.b="D0uBITgw" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=efficios.com; s=smtpout1; t=1783430170; bh=Ui614MPQIEbcScloqlMfHq2wxZkPsVPFRzGkJ2jSbz0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=D0uBITgwcO9CSWWU1w2ZupEZODYctvIt/R1NFBMGl5Y1u7XR1isV5/QJhvBk3Oc3i Xbvkv06eEy/kIgz2wlNCFE1hOYjy9iZpfzQD89bOCXusIhKbEL08dv7nZ56mOWxKWg 4o1VA6Xc+RgjY1R8pyjVG3QrgK/mwgfzGdht6YjGCyWIGY1ku7LV9hJT6dmviI+in+ CXkUvTLS54qktcfdlPbETnAJDb4GW5Z4sI200P6Gsv3IcJI8nsUtHdDs8FP0JneSrG raOUjPiw5kB01b200yXQiZaYXzM5qLx2We4FkmS/+ZE95pD0AlkRrwptYobYObNOY3 4Q6BkH2tqrEYw== Received: from compudjdev.. (mtl.efficios.com [216.120.195.104]) by smtpout.efficios.com (Postfix) with ESMTPSA id 4gvhZ22zV3zTlq; Tue, 07 Jul 2026 09:16:10 -0400 (EDT) From: Mathieu Desnoyers To: Cc: linux-kernel@vger.kernel.org, Mathieu Desnoyers , "Paul E. McKenney" , Steven Rostedt , Masami Hiramatsu , Dennis Zhou , Tejun Heo , Christoph Lameter , Martin Liu , David Rientjes , christian.koenig@amd.com, Shakeel Butt , SeongJae Park , Michal Hocko , Johannes Weiner , Sweet Tea Dorminy , Lorenzo Stoakes , "Liam R . Howlett" , Mike Rapoport , Suren Baghdasaryan , Vlastimil Babka , Christian Brauner , Wei Yang , David Hildenbrand , Miaohe Lin , Al Viro , Yu Zhao , Roman Gushchin , Mateusz Guzik , Matthew Wilcox , Baolin Wang , Aboorva Devarajan , David Carlier , Josh Law , Andrew Morton Subject: [PATCH v20 3/6] mm: improve RSS counter approximation accuracy for proc interfaces Date: Tue, 7 Jul 2026 09:15:33 -0400 Message-ID: <20260707131544.75906-4-mathieu.desnoyers@efficios.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260707131544.75906-1-mathieu.desnoyers@efficios.com> References: <20260707131544.75906-1-mathieu.desnoyers@efficios.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Use hierarchical per-cpu counters for RSS tracking to improve the accuracy of per-mm RSS sum approximation on large many-core systems [1]. This improves the accuracy of the RSS values returned by proc interfaces. Here is a (possibly incomplete) list of the prior approaches that were used or proposed, along with their downside: 1) Per-thread rss tracking: large error on many-thread processes. 2) Per-CPU counters: up to 12% slower for short-lived processes and 9% increased system time in make test workloads [1]. Moreover, the inaccuracy increases with O(n^2) with the number of CPUs. 3) Per-NUMA-node counters: requires atomics on fast-path (overhead), error is high with systems that have lots of NUMA nodes (32 times the number of NUMA nodes). 4) Use a percise per-cpu counter sum for each counter value query: Requires iteration on each possible CPUs for each sum, which adds overhead on large many-core systems running many processes. The approach proposed here is to replace the per-cpu counters by the hierarchical per-cpu counters, which bounds the inaccuracy based on the system topology with O(N*logN). * Testing results: Test hardware: 2 sockets AMD EPYC 9654 96-Core Processor (384 logical CPUs total) Methodology: Comparing the current upstream implementation with the hierarchical counters is done by keeping both implementations wired up in parallel, and running a single-process, single-threaded program which hops randomly across CPUs in the system, calling mmap(2) and munmap(2) on random CPUs, keeping track of an array of allocated mappings, randomly choosing entries to either map or unmap. get_mm_counter() is instrumented to compare the upstream counter approximation to the precise value, and print the delta when going over a given threshold. The delta of the hierarchical counter approximation to the precise value is also printed for comparison. After a few minutes running this test, the upstream implementation counter approximation reaches a 1GB delta from the precise value, compared to 80MB delta with the hierarchical counter. The hierarchical counter provides a guaranteed maximum approximation inaccuracy of 192MB on that hardware topology. * Fast path implementation comparison The new inline percpu_counter_tree_add() uses a this_cpu_add_return() for the fast path (under a certain allocation size threshold). Above that, it calls a slow path which "trickles up" the carry to upper level counters with atomic_add_return. In comparison, the upstream counters implementation calls percpu_counter_add_batch which uses this_cpu_try_cmpxchg() on the fast path, and does a raw_spin_lock_irqsave above a certain threshold. The hierarchical implementation is therefore expected to have less contention on mid-sized allocations than the upstream counters because the atomic counters tracking those bits are only shared across nearby CPUs. In comparison, the upstream counters immediately use a global spinlock when reaching the threshold. * Benchmarks Using will-it-scale page_fault1 benchmarks to compare the upstream counters to the hierarchical counters. This is done with hyperthreading disabled. The speedup is within the standard deviation of the upstream runs, so the overhead is not significant. upstream hierarchical speedup page_fault1_processes -s 100 -t 1 614783 615558 +0.1% page_fault1_threads -s 100 -t 1 612788 612447 -0.1% page_fault1_processes -s 100 -t 96 37994977 37932035 -0.2% page_fault1_threads -s 100 -t 96 2484130 2504860 +0.8% page_fault1_processes -s 100 -t 192 71262917 71118830 -0.2% page_fault1_threads -s 100 -t 192 2446437 2469296 +0.1% * Memory Use The most important parts in terms of memory use are the per-cpu counters and the tree items which propagate the carry. In the proposed implementation, the per-cpu counters are allocated within per-cpu data structures, so they end up using: nr_possible_cpus * sizeof(unsigned long) This is in addition to the tree items. The size of those items is defined by the per_nr_cpu_order_config table "nr_items" field. Each item is aligned on cacheline size (typically 64 bytes) to minimize false sharing. Here is the footprint for a few nr_cpu_ids on a 64-bit arch: nr_cpu_ids percpu counters (bytes) nr_items items size (bytes) total (bytes) 2 16 1 64 80 4 32 3 192 224 8 64 7 448 512 64 512 21 1344 1856 128 1024 21 1344 2368 256 2048 37 2368 4416 512 4096 73 4672 8768 Compared to this, the upstream percpu counters use a 32-bit integer per-cpu (4 bytes), and accumulate within a 64-bit global value. So there is an extra memory footprint added by the current hpcc implementation, but if it's an issue we have various options to consider to reduce its footprint. Link: https://lore.kernel.org/lkml/20250331223516.7810-2-sweettea-kernel@dorminy.me/ # [1] Link: https://lkml.kernel.org/r/20260227153730.1556542-4-mathieu.desnoyers@efficios.com Signed-off-by: Mathieu Desnoyers Cc: "Paul E. McKenney" Cc: Steven Rostedt Cc: Masami Hiramatsu Cc: Mathieu Desnoyers Cc: Dennis Zhou Cc: Tejun Heo Cc: Christoph Lameter Cc: Martin Liu Cc: David Rientjes Cc: christian.koenig@amd.com Cc: Shakeel Butt Cc: SeongJae Park Cc: Michal Hocko Cc: Johannes Weiner Cc: Sweet Tea Dorminy Cc: Lorenzo Stoakes Cc: "Liam R . Howlett" Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Christian Brauner Cc: Wei Yang Cc: David Hildenbrand Cc: Miaohe Lin Cc: Al Viro Cc: Yu Zhao Cc: Roman Gushchin Cc: Mateusz Guzik Cc: Matthew Wilcox Cc: Baolin Wang Cc: Aboorva Devarajan Cc: David Carlier Cc: Josh Law Cc: Andrew Morton --- include/linux/mm.h | 19 ++++++++++---- include/linux/mm_types.h | 50 +++++++++++++++++++++++++++---------- include/trace/events/kmem.h | 2 +- kernel/fork.c | 22 +++++++++------- 4 files changed, 65 insertions(+), 28 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 485df9c2dbdd..45b49f15bb5e 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3338,38 +3338,47 @@ static inline bool get_user_page_fast_only(unsigned long addr, { return get_user_pages_fast_only(addr, 1, gup_flags, pagep) == 1; } + +static inline struct percpu_counter_tree_level_item *get_rss_stat_items(struct mm_struct *mm) +{ + unsigned long ptr = (unsigned long)mm; + + ptr += offsetof(struct mm_struct, flexible_array); + return (struct percpu_counter_tree_level_item *)ptr; +} + /* * per-process(per-mm_struct) statistics. */ static inline unsigned long get_mm_counter(struct mm_struct *mm, int member) { - return percpu_counter_read_positive(&mm->rss_stat[member]); + return percpu_counter_tree_approximate_sum_positive(&mm->rss_stat[member]); } static inline unsigned long get_mm_counter_sum(struct mm_struct *mm, int member) { - return percpu_counter_sum_positive(&mm->rss_stat[member]); + return percpu_counter_tree_precise_sum_positive(&mm->rss_stat[member]); } void mm_trace_rss_stat(struct mm_struct *mm, int member); static inline void add_mm_counter(struct mm_struct *mm, int member, long value) { - percpu_counter_add(&mm->rss_stat[member], value); + percpu_counter_tree_add(&mm->rss_stat[member], value); mm_trace_rss_stat(mm, member); } static inline void inc_mm_counter(struct mm_struct *mm, int member) { - percpu_counter_inc(&mm->rss_stat[member]); + percpu_counter_tree_add(&mm->rss_stat[member], 1); mm_trace_rss_stat(mm, member); } static inline void dec_mm_counter(struct mm_struct *mm, int member) { - percpu_counter_dec(&mm->rss_stat[member]); + percpu_counter_tree_add(&mm->rss_stat[member], -1); mm_trace_rss_stat(mm, member); } diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 00eb86f38183..414618ffe329 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include @@ -1155,6 +1155,19 @@ typedef struct { DECLARE_BITMAP(__mm_flags, NUM_MM_FLAG_BITS); } __private mm_flags_t; +/* + * The alignment of the mm_struct flexible array is based on the largest + * alignment of its content: + * __alignof__(struct percpu_counter_tree_level_item) provides a + * cacheline aligned alignment on SMP systems, else alignment on + * unsigned long on UP systems. + */ +#ifdef CONFIG_SMP +# define __mm_struct_flexible_array_aligned __aligned(__alignof__(struct percpu_counter_tree_level_item)) +#else +# define __mm_struct_flexible_array_aligned __aligned(__alignof__(unsigned long)) +#endif + struct kioctx_table; struct iommu_mm_data; struct mm_struct { @@ -1297,7 +1310,7 @@ struct mm_struct { unsigned long saved_e_flags; #endif - struct percpu_counter rss_stat[NR_MM_COUNTERS]; + struct percpu_counter_tree rss_stat[NR_MM_COUNTERS]; struct linux_binfmt *binfmt; @@ -1407,10 +1420,13 @@ struct mm_struct { } __randomize_layout; /* - * The mm_cpumask needs to be at the end of mm_struct, because it - * is dynamically sized based on nr_cpu_ids. + * The rss hierarchical counter items, mm_cpumask, and mm_cid + * masks need to be at the end of mm_struct, because they are + * dynamically sized based on nr_cpu_ids. + * The content of the flexible array needs to be placed in + * decreasing alignment requirement order. */ - char flexible_array[] __aligned(__alignof__(unsigned long)); + char flexible_array[] __mm_struct_flexible_array_aligned; }; /* Copy value to the first system word of mm flags, non-atomically. */ @@ -1449,22 +1465,28 @@ extern struct mm_struct init_mm; #define MM_STRUCT_FLEXIBLE_ARRAY_INIT \ { \ - [0 ... sizeof(cpumask_t) + MM_CID_STATIC_SIZE - 1] = 0 \ + [0 ... (PERCPU_COUNTER_TREE_ITEMS_STATIC_SIZE * NR_MM_COUNTERS) + sizeof(cpumask_t) + MM_CID_STATIC_SIZE - 1] = 0 \ } -/* Pointer magic because the dynamic array size confuses some compilers. */ -static inline void mm_init_cpumask(struct mm_struct *mm) +static inline size_t get_rss_stat_items_size(void) { - unsigned long cpu_bitmap = (unsigned long)mm; - - cpu_bitmap += offsetof(struct mm_struct, flexible_array); - cpumask_clear((struct cpumask *)cpu_bitmap); + return percpu_counter_tree_items_size() * NR_MM_COUNTERS; } /* Future-safe accessor for struct mm_struct's cpu_vm_mask. */ static inline cpumask_t *mm_cpumask(struct mm_struct *mm) { - return (struct cpumask *)&mm->flexible_array; + unsigned long ptr = (unsigned long)mm; + + ptr += offsetof(struct mm_struct, flexible_array); + /* Skip RSS stats counters. */ + ptr += get_rss_stat_items_size(); + return (struct cpumask *)ptr; +} + +static inline void mm_init_cpumask(struct mm_struct *mm) +{ + cpumask_clear((struct cpumask *)mm_cpumask(mm)); } #ifdef CONFIG_LRU_GEN @@ -1556,6 +1578,8 @@ static inline cpumask_t *mm_cpus_allowed(struct mm_struct *mm) unsigned long bitmap = (unsigned long)mm; bitmap += offsetof(struct mm_struct, flexible_array); + /* Skip RSS stats counters. */ + bitmap += get_rss_stat_items_size(); /* Skip cpu_bitmap */ bitmap += cpumask_size(); return (struct cpumask *)bitmap; diff --git a/include/trace/events/kmem.h b/include/trace/events/kmem.h index cd7920c81f85..290ccb9fd25d 100644 --- a/include/trace/events/kmem.h +++ b/include/trace/events/kmem.h @@ -448,7 +448,7 @@ TRACE_EVENT(rss_stat, */ __entry->curr = current->mm == mm && !(current->flags & PF_KTHREAD); __entry->member = member; - __entry->size = (percpu_counter_sum_positive(&mm->rss_stat[member]) + __entry->size = (percpu_counter_tree_approximate_sum_positive(&mm->rss_stat[member]) << PAGE_SHIFT); ), diff --git a/kernel/fork.c b/kernel/fork.c index f0e2e131a9a5..d66f5ccd5414 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -137,6 +137,11 @@ */ #define MAX_THREADS FUTEX_TID_MASK +/* + * Batch size of rss stat approximation + */ +#define RSS_STAT_BATCH_SIZE 32 + /* * Protected counters by write_lock_irq(&tasklist_lock) */ @@ -632,14 +637,12 @@ static void check_mm(struct mm_struct *mm) "Please make sure 'struct resident_page_types[]' is updated as well"); for (i = 0; i < NR_MM_COUNTERS; i++) { - long x = percpu_counter_sum(&mm->rss_stat[i]); - - if (unlikely(x)) { + if (unlikely(percpu_counter_tree_precise_compare_value(&mm->rss_stat[i], 0) != 0)) pr_alert("BUG: Bad rss-counter state mm:%p type:%s val:%ld Comm:%s Pid:%d\n", - mm, resident_page_types[i], x, + mm, resident_page_types[i], + percpu_counter_tree_precise_sum(&mm->rss_stat[i]), current->comm, task_pid_nr(current)); - } } if (mm_pgtables_bytes(mm)) @@ -737,7 +740,7 @@ void __mmdrop(struct mm_struct *mm) check_mm(mm); mm_pasid_drop(mm); mm_destroy_cid(mm); - percpu_counter_destroy_many(mm->rss_stat, NR_MM_COUNTERS); + percpu_counter_tree_destroy_many(mm->rss_stat, NR_MM_COUNTERS); free_mm(mm); } @@ -1138,8 +1141,9 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p) if (mm_alloc_sched(mm)) goto fail_sched; - if (percpu_counter_init_many(mm->rss_stat, 0, GFP_KERNEL_ACCOUNT, - NR_MM_COUNTERS)) + if (percpu_counter_tree_init_many(mm->rss_stat, get_rss_stat_items(mm), + NR_MM_COUNTERS, RSS_STAT_BATCH_SIZE, + GFP_KERNEL_ACCOUNT)) goto fail_pcpu; lru_gen_init_mm(mm); @@ -3103,7 +3107,7 @@ void __init mm_cache_init(void) * dynamically sized based on the maximum CPU number this system * can have, taking hotplug into account (nr_cpu_ids). */ - mm_size = sizeof(struct mm_struct) + cpumask_size() + mm_cid_size(); + mm_size = sizeof(struct mm_struct) + cpumask_size() + mm_cid_size() + get_rss_stat_items_size(); mm_cachep = kmem_cache_create_usercopy("mm_struct", mm_size, ARCH_MIN_MMSTRUCT_ALIGN, -- 2.43.0