Skip to content

[Android] Let the raster thread use every core of a chip without little cores - #195071

Merged
auto-submit[bot] merged 6 commits into
flutter:masterfrom
Lopution:raster-affinity-capacity
Oct 30, 2026
Merged

auto-submit[bot] merged 6 commits into
flutter:masterfrom
Lopution:raster-affinity-capacity

Conversation

@Lopution

@Lopution Lopution commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

CPUSpeedTracker counts the CPUs with the lowest cpuinfo_max_freq as efficiency cores, and the raster thread is set to the other cores (kNotEfficiency, renamed kNotLittle in this PR). On a chip without little cores, those are full-size cores. On the Snapdragon 8 Elite and 8 Elite Gen 5 (two prime and six performance Oryon cores) the raster thread only gets the two prime cores. They are mostly idle, so raster frames start at the cluster's lowest frequency. See #195070 for measurements.

This change also reads /sys/devices/system/cpu/cpuN/cpu_capacity, the kernel's rating of each CPU, scaled so that the fastest is 1024. When every CPU has a capacity and none is below half of the fastest, the chip has no little cores, and kNotLittle is every core.

  • Why half: little cores rate well below it. Upstream SM8650 device trees put the Cortex-A520 at about 37% of the X4 at their top frequencies. The SM8850's performance cores are at 762 of 1024.
  • Equal capacities, or a CPU without one, change nothing. A kernel without capacity data reports 1024 for every CPU.
  • Other affinities do not change. kEfficiency is still the slowest group, so background threads stay off the prime cores.
  • kNotEfficiency is renamed kNotLittle, since on these chips it includes the efficiency cores. Each CpuAffinity value's doc says which cores it covers, and that performance and efficiency there are speed groups, not the industry's core types.
  • Chips with little cores (Snapdragon 8 Gen 1–3, Tensor, Exynos 2400) should keep the current set. I have only read the capacities of the SM8850.
  • Other chips without little cores also get every core. Examples: Dimensity 9300/9400 (the slowest group is four Cortex-A720), Snapdragon 8s Gen 4, and Snapdragon 8 Elite Gen 6. On the 8 Elite Gen 6 the six performance cores report two top frequencies (4.03 and 3.74 GHz), so today three of them count as efficiency cores. I have not measured these chips.
  • On the Dimensity 9600 Pro (2 × C2-Ultra at 4.55 GHz, 3 × C2-Pro at 4.35 GHz, 3 × C2-Pro at 3.1 GHz), the 3.1 GHz cores should rate near half, so it may go either way. Either way the raster thread keeps at least the five cores it gets today.

This keeps the current design and only widens it where the capacities are clear. A chip whose performance cores rate below half of its prime cores would still leave the raster thread on the prime cores. The more general fix would be to stop pinning the raster thread, as Android does not pin a native app's RenderThread. That needs measurements on the devices that flutter-team-archive/engine#45673 and #153690 were about, so this change leaves it out.

Tests: three new CpuAffinity tests in fml_unittests. NoLittleCores fails without this change. I ran the CpuAffinity tests against the changed files in a standalone build with clang++ and g++, not in a full engine build. On an SM8850 phone, setting the raster threads to every core from the app (the same result as this change) gave the numbers in #195070. I have not tested a custom engine build on the device.

Fixes #195070

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

…le cores

CPUSpeedTracker counts the CPUs with the lowest top frequency as
efficiency cores, and the raster thread is set to the others. On the
Snapdragon 8 Elite and 8 Elite Gen 5 (two prime and six performance
cores) that leaves the raster thread the two prime cores, which idle at
their lowest frequency.

Also read each CPU's cpu_capacity. When every CPU has one and none is
below half of the fastest, the chip has no little cores, and
kNotEfficiency is every core.

Fixes flutter#195070
@github-actions github-actions Bot added the engine flutter/engine related. See also e: labels. label Oct 22, 2026
@google-cla

google-cla Bot commented Oct 22, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces CPU capacity detection to identify devices without little cores, reading the cpu_capacity file on Android and updating CPUSpeedTracker to treat all cores as non-efficiency cores when no little cores are present. Feedback suggests simplifying the HasNoLittleCores helper function by using std::min and std::max with std::numeric_limits to avoid verbose optional state management.

Comment on lines +40 to +57
bool HasNoLittleCores(const std::vector<CpuIndexAndSpeed>& cpus) {
std::optional<int64_t> min_capacity = std::nullopt;
std::optional<int64_t> max_capacity = std::nullopt;
for (const auto& cpu : cpus) {
if (!cpu.capacity.has_value()) {
return false;
}
const int64_t capacity = cpu.capacity.value();
if (!min_capacity.has_value() || capacity < min_capacity.value()) {
min_capacity = capacity;
}
if (!max_capacity.has_value() || capacity > max_capacity.value()) {
max_capacity = capacity;
}
}
return min_capacity.has_value() && min_capacity != max_capacity &&
min_capacity.value() * 2 >= max_capacity.value();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The logic in HasNoLittleCores can be simplified and made more idiomatic by checking if the input vector is empty first, and then using standard std::min and std::max with std::numeric_limits to track the minimum and maximum capacities. This avoids the verbosity of managing multiple std::optional states and calling .value() repeatedly.

bool HasNoLittleCores(const std::vector<CpuIndexAndSpeed>& cpus) {
  if (cpus.empty()) {
    return false;
  }
  int64_t min_capacity = std::numeric_limits<int64_t>::max();
  int64_t max_capacity = std::numeric_limits<int64_t>::min();
  for (const auto& cpu : cpus) {
    if (!cpu.capacity.has_value()) {
      return false;
    }
    const int64_t capacity = *cpu.capacity;
    min_capacity = std::min(min_capacity, capacity);
    max_capacity = std::max(max_capacity, capacity);
  }
  return min_capacity != max_capacity && min_capacity * 2 >= max_capacity;
}

@gaaclarke gaaclarke left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Okay, let's just clean up the tests. I think the premise for this change is fine. I think it's unfortunate and maybe there is another way we can do this better. This is acceptable for now.

Comment on lines +63 to +64
ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotEfficiency)[0], 1u);
ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotEfficiency)[1], 2u);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any assertion that won't lead to a crash should be EXPECT_EQ

@gaaclarke gaaclarke added the CICD Run CI/CD label Oct 27, 2026
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Oct 28, 2026
@Lopution

Lopution commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I switched the assertions that don't need to abort to EXPECT_*, and kept ASSERT_EQ only on the size checks that guard the indexing right after them. Pushed in 48b667c.

You mentioned there may be a better way. I hit this as an app developer, so I'd like to know which direction the team has in mind so I can follow along or help where useful. Is it something like ADPF hint sessions (#114835; I saw #192582 for API 35+), or leaning more on the scheduler and not pinning threads at all?

If this is meant as a stopgap, that's fine — we're carrying an app-side workaround for now.

gaaclarke
gaaclarke previously approved these changes Oct 28, 2026

@gaaclarke gaaclarke left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this is a fine change for now until we come up with a more comprehensive solution to the general problem.

@gaaclarke
gaaclarke requested a review from b-luk October 28, 2026 16:26
@gaaclarke

Copy link
Copy Markdown
Member

@b-luk can you please be a second reviewer for this, you were looking at a similar class of problems the other day. I don't think this is ideal but should do some good without causing any harm with the current approach.

@b-luk b-luk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think both the existing cpu affinity code and the changes from this PR make a lot of assumptions about the categorization and behavior of CPU cores which are somewhat mismatched with categorizations and behavior in the real world. So overall I'm not a fan of this entire existing approach. I think we should be handling CPU core selection and scheduling in a different way than trying to do our own manual categorization of cores and affinity selection. But coming up with an entirely different approach is outside the scope of this PR.

This PR improves your demonstrated use case, so I'm okay with it despite my caveats above.

Comment thread engine/src/flutter/fml/cpu_affinity.h Resolved
Comment thread engine/src/flutter/fml/cpu_affinity.h Resolved
Comment thread engine/src/flutter/fml/cpu_affinity.h Resolved
… covers

On a chip without little cores, kNotEfficiency includes the efficiency
cores, so it no longer meant "every core except the efficiency cores".
Each CpuAffinity value now says which cores it covers, and the enum notes
that performance and efficiency here are speed groups, not the industry's
core types. The example moves out of CPUSpeedTracker's doc, and
kPerformance no longer claims that the UI and raster threads use it.
@Lopution
Lopution requested a review from a team as a code owner October 29, 2026 10:53
@Lopution
Lopution requested review from mboetger and removed request for a team October 29, 2026 10:53
@github-actions github-actions Bot added platform-android Android applications specifically team-android Owned by Android platform team labels Oct 29, 2026
@gaaclarke
gaaclarke requested a review from b-luk October 29, 2026 15:19
@b-luk b-luk added the CICD Run CI/CD label Oct 29, 2026
b-luk
b-luk previously approved these changes Oct 29, 2026

@b-luk b-luk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, @Lopution. The rename and the updated comments reduce some of the confusion and addresses my concerns.

@b-luk
b-luk requested a review from gaaclarke October 29, 2026 16:33
gaaclarke
gaaclarke previously approved these changes Oct 29, 2026

@gaaclarke gaaclarke left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm

@gaaclarke gaaclarke added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 29, 2026
@Lopution

Copy link
Copy Markdown
Contributor Author

Thanks. Both loops now reserve their vector before filling it, pushed in 1b794a0. With the engine's .clang-tidy, clang-tidy 21 reports the same two errors on the old file locally and none on the new one.

@gaaclarke
gaaclarke requested a review from b-luk October 30, 2026 15:47
@b-luk b-luk added CICD Run CI/CD autosubmit Merge PR when tree becomes green via auto submit App labels Oct 30, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 30, 2026
@auto-submit

auto-submit Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/195071, because This PR has not met approval requirements for merging. The PR author is not a member of flutter-hackers and needs 1 more review(s) in order to merge this PR.

  • Merge guidelines: A PR needs at least one approved review if the author is already part of flutter-hackers or two member reviews if the author is not a member of flutter-hackers before re-applying the autosubmit label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

@b-luk b-luk added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 30, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 30, 2026
@auto-submit

auto-submit Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/195071, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@gaaclarke gaaclarke added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 30, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into flutter:master with commit e65eafa Oct 30, 2026
41 of 42 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 30, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Oct 31, 2026
flutter/flutter@5e805bf...ee8b482

2026-10-31 [email protected] Roll Skia from 9bab765df357 to 607de4b035dc (1 revision) (flutter/flutter#195728)
2026-10-31 [email protected] Roll Skia from 284054bec98a to 9bab765df357 (2 revisions) (flutter/flutter#195726)
2026-10-31 [email protected] Roll Skia from 3a3f56f7ddd4 to 284054bec98a (1 revision) (flutter/flutter#195724)
2026-10-31 [email protected] Roll Skia from 466994d27b1e to 3a3f56f7ddd4 (1 revision) (flutter/flutter#195719)
2026-10-30 [email protected] Fix XcodeProjectInterpreter dependency cycle in flutter_tools (flutter/flutter#195694)
2026-10-30 [email protected] Document that Positioned also works inside an Overlay (flutter/flutter#190607)
2026-10-30 [email protected] Roll Skia from ce0b85b46db6 to 466994d27b1e (3 revisions) (flutter/flutter#195711)
2026-10-30 [email protected] Adds Vulkan sync validation test run (flutter/flutter#195596)
2026-10-30 [email protected] [Android] Let the raster thread use every core of a chip without little cores (flutter/flutter#195071)
2026-10-30 [email protected] Roll Skia from 121d04c01e2d to ce0b85b46db6 (6 revisions) (flutter/flutter#195699)
2026-10-30 [email protected] [web] Anchor platform view slots to top-left to fix RTL document layout (flutter/flutter#194601)
2026-10-30 [email protected] Update golden file testing docs (flutter/flutter#195449)
2026-10-30 [email protected] [tool] Migrate Cache.flutterRoot static state to instance configuration (flutter/flutter#190795)
2026-10-30 [email protected] Apply view metrics overrides below the framework (flutter/flutter#194311)
2026-10-30 [email protected] Roll Skia from c0c46ee9f938 to 121d04c01e2d (9 revisions) (flutter/flutter#195687)
2026-10-30 [email protected] Use uniforms in the Porter Duff blend shader instead of specialization constants (flutter/flutter#195458)
2026-10-30 [email protected] [flutter_tools] Inject dependencies into iOS, macOS, and Windows application packages (flutter/flutter#194047)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@gaaclarke

Copy link
Copy Markdown
Member

@gaaclarke

Copy link
Copy Markdown
Member

reason for revert: This has caused a regression in one of our benchmarks ( #195071 (comment) ). It is possible that the drop in the benchmark is desirable, for example it uses less energy and gives the same apparent output. We track energy usage but it didn't show a positive change for this PR. I'm not sure how it's measured and if it's trustworthy on android. So, our only option is run the benchmarks on the most capable cores.

In order to reland this we need to rework it so that either:

  1. it doesn't demonstrate the regression
  2. we address the problem where the energy measurement shows the tradeoff

@gaaclarke gaaclarke added the revert Autorevert PR (with "Reason for revert:" comment) label Nov 2, 2026
@flutteractionsbot

Copy link
Copy Markdown
Contributor

Successfully created revert PR: #195838

@flutteractionsbot flutteractionsbot removed the revert Autorevert PR (with "Reason for revert:" comment) label Nov 2, 2026
definev pushed a commit to definev/flutter that referenced this pull request Nov 3, 2026
…out little cores (flutter#195838)

Reverts: [[Android] Let the raster thread use every core of a chip
without little cores](flutter#195071)

Initiated by: @gaaclarke

Reason for reverting: 1) we address the problem where the energy
measurement shows the tradeoff

Original PR Author: @Lopution

Reviewed By: @gaaclarke

The original PR description is provided below:

`CPUSpeedTracker` counts the CPUs with the lowest `cpuinfo_max_freq` as
efficiency cores, and the raster thread is set to the other cores
(`kNotEfficiency`, renamed `kNotLittle` in this PR). On a chip without
little cores, those are full-size cores. On the Snapdragon 8 Elite and 8
Elite Gen 5 (two prime and six performance Oryon cores) the raster
thread only gets the two prime cores. They are mostly idle, so raster
frames start at the cluster's lowest frequency. See flutter#195070 for
measurements.

This change also reads `/sys/devices/system/cpu/cpuN/cpu_capacity`, the
kernel's rating of each CPU, scaled so that the fastest is 1024. When
every CPU has a capacity and none is below half of the fastest, the chip
has no little cores, and `kNotLittle` is every core.

- Why half: little cores rate well below it. Upstream SM8650 device
trees put the Cortex-A520 at about 37% of the X4 at their top
frequencies. The SM8850's performance cores are at 762 of 1024.
- Equal capacities, or a CPU without one, change nothing. A kernel
without capacity data reports 1024 for every CPU.
- Other affinities do not change. `kEfficiency` is still the slowest
group, so background threads stay off the prime cores.
- `kNotEfficiency` is renamed `kNotLittle`, since on these chips it
includes the efficiency cores. Each `CpuAffinity` value's doc says which
cores it covers, and that performance and efficiency there are speed
groups, not the industry's core types.
- Chips with little cores (Snapdragon 8 Gen 1–3, Tensor, Exynos 2400)
should keep the current set. I have only read the capacities of the
SM8850.
- Other chips without little cores also get every core. Examples:
Dimensity 9300/9400 (the slowest group is four Cortex-A720), Snapdragon
8s Gen 4, and Snapdragon 8 Elite Gen 6. On the 8 Elite Gen 6 the six
performance cores report two top frequencies (4.03 and 3.74 GHz), so
today three of them count as efficiency cores. I have not measured these
chips.
- On the Dimensity 9600 Pro (2 × C2-Ultra at 4.55 GHz, 3 × C2-Pro at
4.35 GHz, 3 × C2-Pro at 3.1 GHz), the 3.1 GHz cores should rate near
half, so it may go either way. Either way the raster thread keeps at
least the five cores it gets today.

This keeps the current design and only widens it where the capacities
are clear. A chip whose performance cores rate below half of its prime
cores would still leave the raster thread on the prime cores. The more
general fix would be to stop pinning the raster thread, as Android does
not pin a native app's RenderThread. That needs measurements on the
devices that flutter-team-archive/engine#45673
and flutter#153690 were about, so this change leaves it out.

Tests: three new `CpuAffinity` tests in `fml_unittests`. `NoLittleCores`
fails without this change. I ran the `CpuAffinity` tests against the
changed files in a standalone build with clang++ and g++, not in a full
engine build. On an SM8850 phone, setting the raster threads to every
core from the app (the same result as this change) gave the numbers in
flutter#195070. I have not tested a custom engine build on the device.

Fixes flutter#195070

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
gmackall pushed a commit to gmackall/flutter that referenced this pull request Nov 3, 2026
…le cores (flutter#195071)

`CPUSpeedTracker` counts the CPUs with the lowest `cpuinfo_max_freq` as
efficiency cores, and the raster thread is set to the other cores
(`kNotEfficiency`, renamed `kNotLittle` in this PR). On a chip without
little cores, those are full-size cores. On the Snapdragon 8 Elite and 8
Elite Gen 5 (two prime and six performance Oryon cores) the raster
thread only gets the two prime cores. They are mostly idle, so raster
frames start at the cluster's lowest frequency. See flutter#195070 for
measurements.

This change also reads `/sys/devices/system/cpu/cpuN/cpu_capacity`, the
kernel's rating of each CPU, scaled so that the fastest is 1024. When
every CPU has a capacity and none is below half of the fastest, the chip
has no little cores, and `kNotLittle` is every core.

- Why half: little cores rate well below it. Upstream SM8650 device
trees put the Cortex-A520 at about 37% of the X4 at their top
frequencies. The SM8850's performance cores are at 762 of 1024.
- Equal capacities, or a CPU without one, change nothing. A kernel
without capacity data reports 1024 for every CPU.
- Other affinities do not change. `kEfficiency` is still the slowest
group, so background threads stay off the prime cores.
- `kNotEfficiency` is renamed `kNotLittle`, since on these chips it
includes the efficiency cores. Each `CpuAffinity` value's doc says which
cores it covers, and that performance and efficiency there are speed
groups, not the industry's core types.
- Chips with little cores (Snapdragon 8 Gen 1–3, Tensor, Exynos 2400)
should keep the current set. I have only read the capacities of the
SM8850.
- Other chips without little cores also get every core. Examples:
Dimensity 9300/9400 (the slowest group is four Cortex-A720), Snapdragon
8s Gen 4, and Snapdragon 8 Elite Gen 6. On the 8 Elite Gen 6 the six
performance cores report two top frequencies (4.03 and 3.74 GHz), so
today three of them count as efficiency cores. I have not measured these
chips.
- On the Dimensity 9600 Pro (2 × C2-Ultra at 4.55 GHz, 3 × C2-Pro at
4.35 GHz, 3 × C2-Pro at 3.1 GHz), the 3.1 GHz cores should rate near
half, so it may go either way. Either way the raster thread keeps at
least the five cores it gets today.

This keeps the current design and only widens it where the capacities
are clear. A chip whose performance cores rate below half of its prime
cores would still leave the raster thread on the prime cores. The more
general fix would be to stop pinning the raster thread, as Android does
not pin a native app's RenderThread. That needs measurements on the
devices that flutter-team-archive/engine#45673
and flutter#153690 were about, so this change leaves it out.

Tests: three new `CpuAffinity` tests in `fml_unittests`. `NoLittleCores`
fails without this change. I ran the `CpuAffinity` tests against the
changed files in a standalone build with clang++ and g++, not in a full
engine build. On an SM8850 phone, setting the raster threads to every
core from the app (the same result as this change) gave the numbers in
flutter#195070. I have not tested a custom engine build on the device.

Fixes flutter#195070

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
gmackall pushed a commit to gmackall/flutter that referenced this pull request Nov 3, 2026
…out little cores (flutter#195838)

Reverts: [[Android] Let the raster thread use every core of a chip
without little cores](flutter#195071)

Initiated by: @gaaclarke

Reason for reverting: 1) we address the problem where the energy
measurement shows the tradeoff

Original PR Author: @Lopution

Reviewed By: @gaaclarke

The original PR description is provided below:

`CPUSpeedTracker` counts the CPUs with the lowest `cpuinfo_max_freq` as
efficiency cores, and the raster thread is set to the other cores
(`kNotEfficiency`, renamed `kNotLittle` in this PR). On a chip without
little cores, those are full-size cores. On the Snapdragon 8 Elite and 8
Elite Gen 5 (two prime and six performance Oryon cores) the raster
thread only gets the two prime cores. They are mostly idle, so raster
frames start at the cluster's lowest frequency. See flutter#195070 for
measurements.

This change also reads `/sys/devices/system/cpu/cpuN/cpu_capacity`, the
kernel's rating of each CPU, scaled so that the fastest is 1024. When
every CPU has a capacity and none is below half of the fastest, the chip
has no little cores, and `kNotLittle` is every core.

- Why half: little cores rate well below it. Upstream SM8650 device
trees put the Cortex-A520 at about 37% of the X4 at their top
frequencies. The SM8850's performance cores are at 762 of 1024.
- Equal capacities, or a CPU without one, change nothing. A kernel
without capacity data reports 1024 for every CPU.
- Other affinities do not change. `kEfficiency` is still the slowest
group, so background threads stay off the prime cores.
- `kNotEfficiency` is renamed `kNotLittle`, since on these chips it
includes the efficiency cores. Each `CpuAffinity` value's doc says which
cores it covers, and that performance and efficiency there are speed
groups, not the industry's core types.
- Chips with little cores (Snapdragon 8 Gen 1–3, Tensor, Exynos 2400)
should keep the current set. I have only read the capacities of the
SM8850.
- Other chips without little cores also get every core. Examples:
Dimensity 9300/9400 (the slowest group is four Cortex-A720), Snapdragon
8s Gen 4, and Snapdragon 8 Elite Gen 6. On the 8 Elite Gen 6 the six
performance cores report two top frequencies (4.03 and 3.74 GHz), so
today three of them count as efficiency cores. I have not measured these
chips.
- On the Dimensity 9600 Pro (2 × C2-Ultra at 4.55 GHz, 3 × C2-Pro at
4.35 GHz, 3 × C2-Pro at 3.1 GHz), the 3.1 GHz cores should rate near
half, so it may go either way. Either way the raster thread keeps at
least the five cores it gets today.

This keeps the current design and only widens it where the capacities
are clear. A chip whose performance cores rate below half of its prime
cores would still leave the raster thread on the prime cores. The more
general fix would be to stop pinning the raster thread, as Android does
not pin a native app's RenderThread. That needs measurements on the
devices that flutter-team-archive/engine#45673
and flutter#153690 were about, so this change leaves it out.

Tests: three new `CpuAffinity` tests in `fml_unittests`. `NoLittleCores`
fails without this change. I ran the `CpuAffinity` tests against the
changed files in a standalone build with clang++ and g++, not in a full
engine build. On an SM8850 phone, setting the raster threads to every
core from the app (the same result as this change) gave the numbers in
flutter#195070. I have not tested a custom engine build on the device.

Fixes flutter#195070

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-android Android applications specifically team-android Owned by Android platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] On Snapdragon 8 Elite and 8 Elite Gen 5 the raster thread only gets the two prime cores

5 participants