Skip to content

Replicate generic hardware events on all CPU PMUs - #2123

Merged
dmah42 merged 1 commit into
google:mainfrom
pcc:cpu-pmu
Feb 11, 2026
Merged

dmah42 merged 1 commit into
google:mainfrom
pcc:cpu-pmu

Conversation

@pcc

@pcc pcc commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

On systems with more than one PMU for the CPUs (e.g. Apple M series SOCs), generic hardware events are only created for an arbitrary PMU. Usually this is the big cluster's PMU, which can cause inaccuracies when the process is scheduled onto a little core. To fix this, teach PerfCounters to register generic hardware events on all CPU PMUs.

CPU PMUs are identified using the same method as perf.

@pcc

pcc commented Feb 6, 2026

Copy link
Copy Markdown
Contributor Author

cc @captain5050

Comment thread src/perf_counters.cc Resolved
Comment thread src/perf_counters.cc Resolved
Comment thread src/perf_counters.cc Resolved
@LebedevRI

Copy link
Copy Markdown
Collaborator

Does this need any kind of guarding or is this always the right thing to do?

@pcc

pcc commented Feb 7, 2026

Copy link
Copy Markdown
Contributor Author

Does this need any kind of guarding or is this always the right thing to do?

This is always correct as far as I'm aware. I've tested that it does the right thing on an M2 Ultra Mac Studio, a Pixel 10 and a regular x86 PC (where it just finds the single PMU which returns the same results as not specifying a PMU).

LebedevRI
LebedevRI previously approved these changes Feb 7, 2026

@LebedevRI LebedevRI left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't test, but seems fine.

@LebedevRI
LebedevRI requested a review from dmah42 February 7, 2026 01:44
@dmah42

dmah42 commented Feb 9, 2026

Copy link
Copy Markdown
Member

can we add google test unit tests for this please? i don't know how tricky it is to test it but i'm a little concerned having no tests at all.

@pcc

pcc commented Feb 9, 2026

Copy link
Copy Markdown
Contributor Author

can we add google test unit tests for this please? i don't know how tricky it is to test it but i'm a little concerned having no tests at all.

We can test it by verifying that counters are non-zero when the process is pinned to each CPU in the system. We can adapt the test PerfCountersTest.Read1Counter for this purpose. Actually, on my Mac Studio that test fails at head (after patching BCR to fix the build failures with --define pfm=1: bazelbuild/bazel-central-registry#7502 bazelbuild/bazel-central-registry#7503) and it also fails after this change (because it didn't expect more than one counter with the same name). I'll take care of that as well.

@pcc

pcc commented Feb 10, 2026

Copy link
Copy Markdown
Contributor Author

can we add google test unit tests for this please? i don't know how tricky it is to test it but i'm a little concerned having no tests at all.

We can test it by verifying that counters are non-zero when the process is pinned to each CPU in the system. We can adapt the test PerfCountersTest.Read1Counter for this purpose. Actually, on my Mac Studio that test fails at head (after patching BCR to fix the build failures with --define pfm=1: bazelbuild/bazel-central-registry#7502 bazelbuild/bazel-central-registry#7503) and it also fails after this change (because it didn't expect more than one counter with the same name). I'll take care of that as well.

Done

dmah42
dmah42 previously approved these changes Feb 10, 2026
@dmah42

dmah42 commented Feb 10, 2026

Copy link
Copy Markdown
Member

looks like we need to update this from head and do some clang-format fun.

On systems with more than one PMU for the CPUs (e.g. Apple M series SOCs),
generic hardware events are only created for an arbitrary PMU. Usually
this is the big cluster's PMU, which can cause inaccuracies when the
process is scheduled onto a little core. To fix this, teach PerfCounters
to register generic hardware events on all CPU PMUs.

CPU PMUs are identified using the same method as perf.
@pcc

pcc commented Feb 10, 2026

Copy link
Copy Markdown
Contributor Author

looks like we need to update this from head and do some clang-format fun.

Done

@dmah42
dmah42 merged commit 84732c8 into google:main Feb 11, 2026
82 of 84 checks passed
@dmah42

dmah42 commented Feb 11, 2026

Copy link
Copy Markdown
Member

thank you so much :)

charlesmunger pushed a commit to charlesmunger/benchmark that referenced this pull request Nov 5, 2026
Instead of falling back to a plain hardware event inside the open loop
when no extended hardware event type works (which needed the
try_extended_hw_type flag, an index-based loop that appends to the PMU
list, and restoring group_id), decide up front which CPU PMUs to replicate
generic hardware events on: probe each CPU PMU once per process by opening
(and closing) a CYCLES event with its extended type, and only replicate on
the PMUs that accept it. If none do, open plain hardware events, as before
"Replicate generic hardware events on all CPU PMUs" (google#2123).

This leaves PerfCounters::Create() unchanged from before the fix, and also
falls back to plain events if no CPU PMUs are found at all.

The --v=1 logging of failed and successful perf_event_open calls is
replaced by logging the probe results, e.g.:

  perf_counters: found CPU PMU armv8_pmuv3 type 8
  perf_counters: PMU type 8 does not support extended hardware event types: Invalid argument

AI disclosure: this change was written with the help of an AI coding tool.
charlesmunger pushed a commit to charlesmunger/benchmark that referenced this pull request Nov 5, 2026
Instead of falling back to a plain hardware event inside the open loop
(which needed the try_extended_hw_type flag, an index-based loop that
appends to the PMU list, and restoring group_id), move the code that
opens a counter (with retries and the fallback to a new group) into a
helper that returns 0 if the counter could be opened, or else the errno
of the failed perf_event_open call. Use it to first try opening each
generic hardware event on all CPU PMUs with extended types and, if none
of these copies can be opened, to open it as a plain hardware event, as
before "Replicate generic hardware events on all CPU PMUs" (google#2123). Each
event is only opened once, and no state is carried from one counter to
the next.

Since the helper only updates group_id when a counter is opened, a
counter that cannot be opened no longer forces the following counters
into a new group.

The --v=1 logging of every perf_event_open call is replaced by logging
the copies that cannot be opened, e.g.:

  perf_counters: found CPU PMU armv8_pmuv3 type 8
  perf_counters: cannot open CYCLES on PMU type 8: Invalid argument

AI disclosure: this change was written with the help of an AI coding tool.
LebedevRI added a commit that referenced this pull request Nov 10, 2026
…ted (#2367)

* Fall back to plain hardware perf events when extended types are rejected

Since "Replicate generic hardware events on all CPU PMUs" (#2123), generic
hardware events are opened on every CPU PMU with the PMU type in the upper
bits of attr.config (PERF_PMU_TYPE_SHIFT). That needs the PMU driver to
advertise PERF_PMU_CAP_EXTENDED_HW_TYPE, which e.g. arm_pmu in Linux 6.1
does not, so the kernel rejects such events (with EINVAL on an arm64
emulator, ENOENT on a Pixel 9). There was no fallback, so every hardware
counter was dropped with "Failed to get a file descriptor for performance
counter" and Android benchmarks reported no PMU data.

If a hardware event cannot be opened on any CPU PMU with an extended type,
open it as a plain hardware event instead, and stop trying extended types
for the remaining counters. Failures on extended types are logged at --v=1
rather than as warnings, since the fallback may succeed.

On heterogeneous systems without extended type support, the kernel opens
the plain event on a single CPU PMU of its choosing, so it only counts
while the benchmark runs on that PMU's CPUs (as before #2123).

Also log CPU PMU discovery and perf_event_open results at --v=1:

  perf_counters: found CPU PMU armv8_pmuv3 type 8
  perf_counters: could not open CYCLES (pmu 8, type 0, config 0x800000000): Invalid argument
  perf_counters: extended hardware event types appear to be unsupported, falling back to plain hardware events
  perf_counters: opened CYCLES (pmu 0) as fd 3

AI disclosure: this change was written with the help of an AI coding tool.

* perf_counters: fall back to plain hardware events without a flag

Instead of falling back to a plain hardware event inside the open loop
(which needed the try_extended_hw_type flag, an index-based loop that
appends to the PMU list, and restoring group_id), move the code that
opens a counter (with retries and the fallback to a new group) into a
helper that returns 0 if the counter could be opened, or else the errno
of the failed perf_event_open call. Use it to first try opening each
generic hardware event on all CPU PMUs with extended types and, if none
of these copies can be opened, to open it as a plain hardware event, as
before "Replicate generic hardware events on all CPU PMUs" (#2123). Each
event is only opened once, and no state is carried from one counter to
the next.

Since the helper only updates group_id when a counter is opened, a
counter that cannot be opened no longer forces the following counters
into a new group.

The --v=1 logging of every perf_event_open call is replaced by logging
the copies that cannot be opened, e.g.:

  perf_counters: found CPU PMU armv8_pmuv3 type 8
  perf_counters: cannot open CYCLES on PMU type 8: Invalid argument

AI disclosure: this change was written with the help of an AI coding tool.

* perf_counters: address review comments on the plain event fallback

* Keep GetPMUTypesForEvent() as before, only dropping the default {0}
  (non-replicated events now take the plain-event path in the caller).
* Document that the plain event is only opened if the event could not
  be opened on any CPU PMU (or is not replicated): also opening it on
  some PMUs would count twice on the PMU that the kernel opens the plain
  event on, since the counts of all PMUs are summed.

No functional change.

AI disclosure: this change was written with the help of an AI coding tool.

* perf_counters: restore upstream formatting of attr {}

The previous change accidentally reformatted `struct perf_event_attr
attr {};` to `attr{}`, as newer clang-format versions do. The
clang-format-lint check uses clang-format 18, which formats it as
`attr {}`.

AI disclosure: this change was written with the help of an AI coding tool.

---------

Co-authored-by: clm <clm@google.com>
Co-authored-by: Roman Lebedev <lebedev.ri@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants