From: Tim Chen <tim.c.chen@linux.intel.com>
To: Pasha Tatashin <pasha.tatashin@soleen.com>,
kees@kernel.org, rppt@kernel.org, juri.lelli@redhat.com,
nik.borisov@suse.com, tglx@kernel.org, vschneid@redhat.com,
kprateek.nayak@amd.com, vincent.guittot@linaro.org,
bp@alien8.de, thorsten.blum@linux.dev, cgroups@vger.kernel.org,
tony.luck@intel.com, david@kernel.org, mingo@redhat.com,
akpm@linux-foundation.org, linux-mm@kvack.org,
seanjc@google.com, ljs@kernel.org, zhangguopeng@kylinos.cn,
dietmar.eggemann@arm.com, mhocko@suse.com,
vinicius.gomes@intel.com, tj@kernel.org, vbabka@kernel.org,
linux-kernel@vger.kernel.org, peterz@infradead.org,
surenb@google.com, hpa@zytor.com, x86@kernel.org,
dmatlack@google.com, kexec@lists.infradead.org,
pratyush@kernel.org, dave.hansen@linux.intel.com,
rostedt@goodmis.org, gmonaco@redhat.com, jgross@suse.com,
mgorman@suse.de, graf@amazon.com, linux-edac@vger.kernel.org,
liam@infradead.org, ridong.chen@linux.dev, longman@redhat.com,
bsegall@google.com
Subject: Re: [PATCH v1 7/8] sched/topology: Avoid redundant percpu allocations and bitmap walks in build_sched_domains()
Date: Thu, 15 Oct 2026 16:17:48 -0700 [thread overview]
Message-ID: <1ec1aab8eabdad173ddef7483a940bfa3f6c3238.camel@linux.intel.com> (raw)
In-Reply-To: <20261015153331.1872641-8-pasha.tatashin@soleen.com>
On Thu, 2026-10-15 at 11:33 -0400, Pasha Tatashin wrote:
> During CPU hotplug, build_sched_domains() is invoked on every CPU
> activation and deactivation. Profiling build_sched_domains() on a
> 480-CPU Intel Xeon 6985P-C and a 512-CPU AMD EPYC 9B45 shows several
> sources of avoidable O(N^2) and O(N) work per step:
>
> 1. build_sched_groups() and build_overlap_sched_groups() iterate over
> every bit of sched_domain_span(sd) via for_each_cpu_wrap(i, span, cpu)
> and check cpumask_test_cpu(i, covered) one CPU at a time, even after
> entire child groups (e.g., 120-CPU NUMA nodes) have been added to
> covered. Instead, track the remaining uncovered CPUs
> (cpumask_copy(uncovered, span)), clear each built group's span with
> cpumask_andnot(), and advance directly to the next uncovered CPU.
>
> 2. __visit_domain_allocation_hell() and __free_domain_allocs() allocate
> and free 3 * nr_levels + 2 temporary per-CPU pointer arrays via
> alloc_percpu() / free_percpu() on every domain rebuild. Reuse the
> per-CPU pointer arrays across domain rebuilds under
> sched_domains_mutex, clearing entries in __free_domain_allocs() and
> only freeing the topology-level arrays when sched_domain_topology is
> replaced in sched_init_numa() / sched_reset_numa().
sched_domains_mutex does not protect the free:
[snip]
> #ifdef CONFIG_NUMA
> +static void __sdt_free_percpu(void)
> +{
> + struct sched_domain_topology_level *tl;
> +
> + for_each_sd_topology(tl) {
> + struct sd_data *sdd = &tl->data;
> +
sched_init_numa() and sched_reset_numa() are called from
sched_update_numa(), which runs from sched_cpu_activate() and
sched_cpu_deactivate() without sched_domains_mutex. The free is safe
because every domain builder holds at least cpus_read_lock(), and
sched_update_numa() runs under the hotplug write lock.
Please fix the changelog and add lockdep_assert_cpus_held()
in __sdt_free_percpu(), so a future caller cannot break this without a warning.
> + free_percpu(sdd->sd);
> + sdd->sd = NULL;
> + free_percpu(sdd->sg);
> + sdd->sg = NULL;
> + free_percpu(sdd->sgc);
> + sdd->sgc = NULL;
> + }
> +}
> +
> static int cpu_numa_flags(void)
> {
> return SD_NUMA;
> @@ -2449,6 +2483,7 @@ void sched_init_numa(int offline_node)
> /*
> * Copy the default topology bits..
> */
> + __sdt_free_percpu();
> for (i = 0; sched_domain_topology[i].mask; i++)
> tl[i] = sched_domain_topology[i];
>
> @@ -2507,6 +2542,7 @@ static void sched_reset_numa(void)
> kfree(masks);
> }
> if (sched_domain_topology_saved) {
> + __sdt_free_percpu();
> kfree(sched_domain_topology);
> sched_domain_topology = sched_domain_topology_saved;
> sched_domain_topology_saved = NULL;
> @@ -2708,17 +2744,23 @@ static int __sdt_alloc(const struct cpumask *cpu_map)
> for_each_sd_topology(tl) {
> struct sd_data *sdd = &tl->data;
>
> - sdd->sd = alloc_percpu(struct sched_domain *);
> - if (!sdd->sd)
> - return -ENOMEM;
> + if (!sdd->sd) {
> + sdd->sd = alloc_percpu(struct sched_domain *);
> + if (!sdd->sd)
> + return -ENOMEM;
> + }
>
> - sdd->sg = alloc_percpu(struct sched_group *);
> - if (!sdd->sg)
> - return -ENOMEM;
> + if (!sdd->sg) {
> + sdd->sg = alloc_percpu(struct sched_group *);
> + if (!sdd->sg)
> + return -ENOMEM;
> + }
>
> - sdd->sgc = alloc_percpu(struct sched_group_capacity *);
> - if (!sdd->sgc)
> - return -ENOMEM;
> + if (!sdd->sgc) {
> + sdd->sgc = alloc_percpu(struct sched_group_capacity *);
> + if (!sdd->sgc)
> + return -ENOMEM;
> + }
>
Keeping the arrays also means every slot must be NULL when a build
starts. Patch 5 depends on that: get_group(),
init_overlap_sched_group() and init_sched_domain_shared() treat a NULL
slot as "not allocated yet", and the overlap fast path treats
"sgc->ref > 0" as "set up in this build". An old pointer would pass
these checks and be used without a warning.
The rule holds today, but nothing says so. It also depends on builds
never filling a slot outside cpu_map, because the clearing loops only
visit cpu_map. Please write this down and check it, for example in
__sdt_alloc():
> for_each_cpu(j, cpu_map) {
> struct sched_domain *sd;
Maybe add
/*
* The slot arrays are kept between builds. Every slot for a CPU
* in cpu_map must be NULL here; lazy allocation treats NULL as
* "not allocated in this build". claim_allocations() and
* __sdt_free()/__sds_free() clear the slots, but only for
* cpu_map, so a build must not fill slots outside cpu_map.
*/
for_each_cpu(j, cpu_map) {
struct sched_domain *sd;
WARN_ON_ONCE(*per_cpu_ptr(sdd->sd, j) ||
*per_cpu_ptr(sdd->sg, j) ||
*per_cpu_ptr(sdd->sgc, j));
> @@ -2748,30 +2790,36 @@ static void __sdt_free(const struct cpumask *cpu_map)
>
> if (sdd->sd) {
> sd = *per_cpu_ptr(sdd->sd, j);
> - if (sd && (sd->flags & SD_NUMA))
> - free_sched_groups(sd->groups, 0);
> - kfree(*per_cpu_ptr(sdd->sd, j));
> + if (sd) {
> + if (sd->flags & SD_NUMA)
> + free_sched_groups(sd->groups, 0);
> + kfree(sd);
> + *per_cpu_ptr(sdd->sd, j) = NULL;
> + }
> }
>
> - if (sdd->sg)
> + if (sdd->sg) {
> kfree(*per_cpu_ptr(sdd->sg, j));
> - if (sdd->sgc)
> + *per_cpu_ptr(sdd->sg, j) = NULL;
> + }
> + if (sdd->sgc) {
> kfree(*per_cpu_ptr(sdd->sgc, j));
> + *per_cpu_ptr(sdd->sgc, j) = NULL;
> + }
> }
> - free_percpu(sdd->sd);
> - sdd->sd = NULL;
> - free_percpu(sdd->sg);
> - sdd->sg = NULL;
> - free_percpu(sdd->sgc);
> - sdd->sgc = NULL;
> }
> }
>
> +static struct sched_domain_shared * __percpu *sched_domains_sds;
> +
> static int __sds_alloc(struct s_data *d, const struct cpumask *cpu_map)
> {
and maybe similar check
for_each_cpu(j, cpu_map)
WARN_ON_ONCE(*per_cpu_ptr(d->sds, j));
> - d->sds = alloc_percpu(struct sched_domain_shared *);
> - if (!d->sds)
> - return -ENOMEM;
> + if (!sched_domains_sds) {
> + sched_domains_sds = alloc_percpu(struct sched_domain_shared *);
> + if (!sched_domains_sds)
> + return -ENOMEM;
> + }
> + d->sds = sched_domains_sds;
>
> return 0;
> }
Thanks.
Tim
next prev parent reply other threads:[~2026-10-15 23:17 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-15 15:33 [PATCH v1 0/8] x86, sched, mm: Speed up CPU hotplug on large systems Pasha Tatashin
2026-10-15 15:33 ` [PATCH v1 1/8] x86/realmode: Remove unnecessary WBINVD from AP startup trampoline Pasha Tatashin
2026-10-15 15:41 ` sashiko-bot
2026-10-15 15:33 ` [PATCH v1 2/8] x86/smp: Remove unnecessary WBINVD from mwait_play_dead() Pasha Tatashin
2026-10-15 15:44 ` sashiko-bot
2026-10-15 16:26 ` Pasha Tatashin
2026-10-15 15:33 ` [PATCH v1 3/8] x86/mce/intel: Bound cmci_rediscover() to a single topology peer Pasha Tatashin
2026-10-15 15:51 ` sashiko-bot
2026-10-15 15:33 ` [PATCH v1 4/8] x86/cacheinfo, mtrr: Skip stop_machine rendezvous in cache_ap_online() when MTRRs match Pasha Tatashin
2026-10-15 15:45 ` sashiko-bot
2026-10-15 15:33 ` [PATCH v1 5/8] sched/topology: Avoid redundant O(N^3) group capacity walks and unused allocations in build_sched_domains() Pasha Tatashin
2026-10-15 15:43 ` sashiko-bot
2026-10-15 15:33 ` [PATCH v1 6/8] sched/deadline, topology: Avoid O(N^2) dl_server bandwidth walks during root_domain rebuilds Pasha Tatashin
2026-10-15 15:57 ` sashiko-bot
2026-10-15 20:43 ` Pasha Tatashin
2026-10-15 15:33 ` [PATCH v1 7/8] sched/topology: Avoid redundant percpu allocations and bitmap walks in build_sched_domains() Pasha Tatashin
2026-10-15 15:51 ` sashiko-bot
2026-10-15 23:17 ` Tim Chen [this message]
2026-10-15 15:33 ` [PATCH v1 8/8] mm/vmstat: Avoid redundant per-CPU stat_threshold writes in refresh_zone_stat_thresholds() Pasha Tatashin
2026-10-15 15:49 ` sashiko-bot
2026-10-15 21:33 ` [PATCH v1 0/8] x86, sched, mm: Speed up CPU hotplug on large systems Luck, Tony
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=1ec1aab8eabdad173ddef7483a940bfa3f6c3238.camel@linux.intel.com \
--to=tim.c.chen@linux.intel.com \
--cc=akpm@linux-foundation.org \
--cc=bp@alien8.de \
--cc=bsegall@google.com \
--cc=cgroups@vger.kernel.org \
--cc=dave.hansen@linux.intel.com \
--cc=david@kernel.org \
--cc=dietmar.eggemann@arm.com \
--cc=dmatlack@google.com \
--cc=gmonaco@redhat.com \
--cc=graf@amazon.com \
--cc=hpa@zytor.com \
--cc=jgross@suse.com \
--cc=juri.lelli@redhat.com \
--cc=kees@kernel.org \
--cc=kexec@lists.infradead.org \
--cc=kprateek.nayak@amd.com \
--cc=liam@infradead.org \
--cc=linux-edac@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=longman@redhat.com \
--cc=mgorman@suse.de \
--cc=mhocko@suse.com \
--cc=mingo@redhat.com \
--cc=nik.borisov@suse.com \
--cc=pasha.tatashin@soleen.com \
--cc=peterz@infradead.org \
--cc=pratyush@kernel.org \
--cc=ridong.chen@linux.dev \
--cc=rostedt@goodmis.org \
--cc=rppt@kernel.org \
--cc=seanjc@google.com \
--cc=surenb@google.com \
--cc=tglx@kernel.org \
--cc=thorsten.blum@linux.dev \
--cc=tj@kernel.org \
--cc=tony.luck@intel.com \
--cc=vbabka@kernel.org \
--cc=vincent.guittot@linaro.org \
--cc=vinicius.gomes@intel.com \
--cc=vschneid@redhat.com \
--cc=x86@kernel.org \
--cc=zhangguopeng@kylinos.cn \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.