[PATCH 0/4] perf: Manage single-CPU PMU affinity in perf core
Haris Okanovic
harisokn at amazon.com
Tue Oct 13 11:55:59 PDT 2026
About 25 PMU drivers, nearly all under drivers/perf, drive their PMU from
a single CPU, and each implements the same logic independently: pick a CPU
at probe with cpumask_local_spread(), cpumask_any(), cpumask_any_and() or
similar, register a CPUHP state to handle hotplug migration, bind every
event to that CPU in event_init(), and publish it via a 'cpumask'
attribute.
Besides the duplicated code, that has two costs. On systems with many
PMUs, the arbitrary choice tends to land all of them on the same CPU,
concentrating overhead there. The chosen CPU also cannot be changed
afterwards, except via hotplug, which is awkward on systems that confine
background kernel work to housekeeping CPUs or otherwise isolate PMUs
from latency sensitive work.
These patches move the choice into perf core as a new "device" pmu::scope.
A driver describes which CPUs are eligible with an optional
filter_allowed_cpus() callback; core intersects that with the online CPUs,
picks one with cpumask_any_distribute() so instances spread out, migrates
events on hotplug, and reports the result through an optional migrate_cpu()
so the driver can follow with interrupt affinity.
'cpumask' is made writable for device scope, so users can also choose the
owning CPU deliberately, e.g. to move a PMU onto a housekeeping CPU. The
attribute is read-only or absent in other scopes. Reusing it was the
minimal change, and I'm happy to add a separate attribute instead if you
would rather 'cpumask' stayed read-only everywhere.
Only arm-cmn is converted in this series, as an example and because it is
what I can test today. I have prototype conversions for the other 24
drivers locally and will post them if the core approach looks right, but I
have no access to most of that hardware and will need help testing.
The series also carries two synchronization changes for events left behind
by migration: the __perf_pmu_remove() fix in patch 1 and the sequence
counter mechanism in patch 2. Neither problem is new. Under hotplug,
leftover events are torn down with the dying CPU, which limits the damage.
Migrating between two online CPUs leaves nothing to clean them up, so they
matter more once users can request migration. I'm open to suggestions on
different or better ways to close these races.
Tested arm-cmn on two platforms with CONFIG_PROVE_LOCKING=y:
AWS m9g.metal-48xl CMN S3, one mesh, 192 CPUs
AWS m8g.metal-48xl CMN-700, two meshes, 192 CPUs
- Events multiplex on the configured CPU, and the two meshes are placed
on distinct CPUs at registration.
- Offlining the owning CPU migrates every open event and updates the
attribute; nothing is left behind on the dying CPU.
- Writing 'cpumask' migrates open events, and offlining the vacated CPU
afterwards strands nothing. Bad input is rejected with EINVAL, ERANGE
or ENODEV without moving the PMU.
- Hammering perf_event_open() from 64 threads while flipping 'cpumask'
between two CPUs does not strand events.
- No lockdep reports on either platform.
This supersedes the arm-cmn-only approach, following Robin Murphy's
suggestion that CPU affinity be generalized into perf core:
https://lore.kernel.org/linux-arm-kernel/2d70be7b-12c0-46e0-b308-744a3159388a@arm.com/
Haris Okanovic (4):
perf/core: Fix perf_pmu_migrate_context() leaving events behind
perf/core: Add PERF_PMU_SCOPE_DEVICE
perf/core: Allow userspace to select a device PMU's CPU
perf/arm-cmn: Convert to PERF_PMU_SCOPE_DEVICE
drivers/perf/arm-cmn.c | 104 ++++------------
include/linux/perf_event.h | 26 ++++
kernel/events/core.c | 247 +++++++++++++++++++++++++++++++++----
3 files changed, 274 insertions(+), 103 deletions(-)
--
Haris Okanovic
AWS Graviton
More information about the linux-arm-kernel
mailing list