Flutter Fixes CPU Affinity Bug That Cost Raster 20% on Snapdragon 8 Elite
The merged fix removes a 16–21% median frame-time penalty for Flutter apps on Snapdragon 8 Elite-class hardware, where the Raster thread was confined to two big cores that rarely reached boost frequency. It's also a concrete case that contradicts manual core pinning as a performance tool, matching Android's advice to rely on ADPF instead.
Flutter's engine has long used a CPU affinity heuristic: it reads each core's max frequency from sysfs, labels the lowest-frequency group as efficiency cores, and keeps UI/Raster threads off them. On Snapdragon 8 Elite-class chips, which have two Prime cores and six Oryon performance cores but no little cores, this heuristic mislabels the six Oryon cores as efficiency, leaving Raster to run only on CPU 6–7 (the 4.61 GHz primes). A developer test on SM8850 showed that relaxing that affinity via sched_setaffinity cut Raster's median frame time by 16–21% and cumulative CPU time by 16–18%.
Perfetto traces explain the anomaly: the Raster thread is bursty—work per frame, then idle. On the two big cores it often starts at 883 MHz and spends 97–100% of its CPU time below 1 GHz, because the frequency never ramps before the frame's work ends. Allowed on all eight cores, the thread runs mainly on the six Oryon cores at an average 1.25–1.4 GHz, which is faster in practice.
The merged fix adds a HasNoLittleCores() check that reads /sys/devices/system/cpu/cpuN/cpu_capacity and compares min to max capacity. If the weakest core is at least 50% of the strongest (SM8850: 762 vs 1024), it concludes there are no little cores and expands UI/Raster affinity to all cores. Conservative fallbacks keep the old frequency-based grouping when capacity data is missing or uniform.
A core's advertised max frequency is a poor proxy for bursty-thread performance: the two 4.61 GHz primes never exited the 883 MHz idle state before Raster's per-frame work finished, so the 'slower' six cores actually delivered the work faster on average.
The fix is a heuristic layered on a heuristic: capacity ratios identify 'no little cores' for this generation, but Flutter still avoids Android's recommended ADPF path, which remains a draft PR. The continued reliance on static affinity is a debt that will likely resurface on next-gen SoCs.
The test method itself is a useful debugging trick: changing thread affinity at the app level and measuring the delta with Perfetto cleanly separates scheduler behavior from application code, which is why the 16–21% regression was caught so precisely.
Reverted again.