Flutter Android 性能修复:8 个大核的手机,Raster 居然只跑在 2 个核上?
发布时间:2026/11/4 12:10:13 锦皓数字建站

最近 Flutter 刚刚合并了 PR #195071这个 Bug 挺搞笑的因为 Android Engine 一套沿用上古的 CPU 亲和性策略在 Snapdragon 8 Elite 这一类没有传统小核的芯片上Flutter 会把六个性能核当成效率核最后只允许 Raster 线程在两个超大核上运行。然后开发者在 Snapdragon 8 Elite Gen 5SM8850真机上 通过应用侧修改线程亲和性来测试放开可用核心之后Raster 帧耗时中位数下降了 16%21%Raster 线程累计 CPU 执行时间减少 16%18%这个一看就很反常明明两个超大核的最高频率更高为什么放到普通性能核上反而跑得更快这套策略最早可以追溯到 2023 年的 Engine #45673当时 Flutter 希望让 UI、Raster 之类的关键线程避开慢核所以通过/sys/devices/system/cpu/cpuN/cpufreq/cpuinfo_max_freq读取每个核心的最高频率最高频率的一组叫kPerformance最低频率的一组叫kEfficiency。但是这样做之后又遇到一个问题一些 Android 手机只有一个频率最高的核心例如 #153690 的 Pixel 8UI、Raster 和部分 Dart Worker 可能挤到同一个核上所以后来的 Engine #54616 把 UI、Raster 的策略放宽成kNotEfficiency也就是除了最低频率那组其他核心都能用这个做法在有传统大小核的手机上还算合理但是 Snapdragon 8 Elite / 8 Elite Gen 5 改成了两个 Prime 超大核加六个 Oryon 性能核已经没有传统 Little Core 了比如拿 PR 实测的 SM8850 来说CPU 05 的最高频率是 3.63 GHzCPU 67 是 4.61 GHz然后 Flutter 一比较还是把前六个识别成了kEfficiency所以kNotEfficiency最后只剩下 CPU 67所以实际上问题出在把「最高频率最低」直接当成「小核」也就是问题处理上 PR 不需要重写调度框架只需要额外读取/sys/devices/system/cpu/cpuN/cpu_capacity就可以了这个值提供了 CPU 在最高频率下的相对计算能力。这里新增的HasNoLittleCores()要求每个核心都有 Capacity 数据而且不能全部相同然后检查return min_capacity ! max_capacity min_capacity * 2 max_capacity;也就是最弱核心至少达到最强核心的 50%SM8850 的六个性能核是 762两个超大核是 1024比例为 74.4%所以 Flutter 判断这颗芯片没有明显的小核分组把原来的kNotEfficiency改成kNotLittle然后让 UI、Raster 的可用核心扩大到 CPU 07。传统大小核上像 Cortex-A520 相对 X4 的 Capacity 37%就不会触发这种放宽。当然这里还有两个保守处理只要某个核心的 Capacity 读不到或者所有核心都报告相同值Flutter 就继续沿用旧的频率分组逻辑。而且kEfficiency的分组逻辑没有变化在 SM8850 上它还是指那六个最高频率较低的 Oryon 性能核后台线程的原有策略也没有被整体重做。那到这里其实还有另外一个问题两个超大核明明更快Raster 为什么反而慢了所以 PR 用 Perfetto 找到的执行过程因为 Raster 属于一帧来时忙一阵、处理完又等待下一帧的负载两个超大核虽然有 4.61 GHz 的上限但在这台 SM8850 上Raster 被限制到 CPU 67 之后线程每次执行往往从 883 MHz 开始97%100% 的 CPU 执行时间都低于 1 GHz。也就是你看到的最高频率很漂亮但一帧的工作可能已经结束了频率还没来得及爬上去。这就类似于你骑车起步挂高档但是只给你 3s 时间速度还没起来就停了。所以如果 App 启动时用sched_setaffinity把 Raster 的可用范围改成 CPU 07 后Perfetto 看到线程更多地跑在六个普通性能核上实际平均执行频率为 1.251.4 GHz反而比限制在超大核时高。是不是很有趣就和之前我们聊的在 iOS 上因为优化的太好系统觉得你不需要 Power 核心直接给你挪到小核上一样抽象这次的也是比如放开后 PR 里打开并滚动详情页可以看到Raster 帧耗时中位数从 1.48 ms 降到 1.24 ms超过 12 ms 的帧从 7 个减少到 2 个在 120 Hz、每帧 8.3 ms 的预算下这种变化主要体现在页面切换、冷启动和少数比较重的帧上其他两个场景的中位数也分别从 1.25 降到 1.00 ms、1.74 降到 1.38 ms实际上这种按 CPU 频率划分核心、再手动指定亲和性的办法对真实芯片拓扑和调度行为做了太多假设其实本身就不是很健壮但是为了解决问题暂时选择这个方向也是一种妥协。理论上 Android 官方的 Performance Hint 文档本来就建议 App 不要自己绑定 CPU 核心因为哪一组核更合适会随着 SoC 架构、温度、功耗限制和系统负载变化一般来说更合理的做法是把目标帧耗时、实际工作耗时交给 ADPF让 Android 去决定频率和核心。其实Flutter 也有相关的 #192582 Draft PR尝试在 Android 15 用 ADPF Hint Session。整体来看这个问题其实挺有意思的Flutter 原本通过限制线程可用 CPU 来追求稳定性能结果新硬件把这个假设打破了变成了负优化只能说这就是“写死”的魅力。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。