掘金热榜文章《Flutter Android 性能修复:8 个大核的手机,Raster 居然只跑在 2 个核上?》记录了一个常见观测。 这个现象值得排查,但不能仅凭标题得出“Flutter 只启用了两个核”的结论:同一 Raster 线程在两个 CPU 之间迁移、多个渲染 worker 恰好活跃在两个核心上,或者采样期间 Raster 已经完成工作,都可能造成类似现象。
核心判断是:多核手机不等于 Raster 会自动利用多核。优化前应先区分 Raster 是“没活干”“想运行却拿不到 CPU”,还是“确实在执行大量难以并行的工作”。光栅化负责把绘制指令转换成像素数据,合成则负责叠加并处理各图层数据,两者是不同的渲染阶段。 “Flutter Raster 线程 CPU 利用率低”本身不是故障结论,真正需要关注的是帧耗时、掉帧以及线程在 Trace 中的状态。
先用系统 Trace 确认 Raster 的运行状态
先建立可比较的基线:固定设备、测试场景、操作路径和目标刷新率。诊断阶段使用 Profile 构建,最终结论必须在 Release 构建中复测,否则调试开销、断言和不同代码路径都可能干扰结果。
DevTools 与系统 Trace 的职责并不相同。运行 flutter run --profile 后,可以从启动输出中的 URL 打开 DevTools;Performance Overlay 与 FrameTiming 适合拆解 build、raster、vsync 等阶段。 线程调度、CPU 亲和性、运行核心、频率变化和唤醒延迟,则应交给 Perfetto 或 Android Studio System Trace。
定位前还要确认统计对象究竟是哪一个 Raster TID。单线程先后出现在两个核心上通常只是调度迁移,不等于两个核心在并行处理同一帧;多个线程同时处于活跃状态,才可能说明存在并行执行。可以先用以下命令交叉确认:
flutter --version
adb shell ps -T -p <pid> | grep -Ei 'raster|flutter'
adb shell cat /proc/<pid>/status | grep -E 'Name|State|Cpus_allowed_list'
adb shell cat /proc/<pid>/task/<tid>/status | grep -E 'Name|State|Cpus_allowed_list'
adb shell taskset -pc <tid>
不同 Android 版本和厂商镜像对 shell 权限、taskset 支持程度可能不同,命令不可用时应以 Perfetto 中的线程调度事件和 /proc 信息为准。
读取 Trace 时,需要把“低利用率”拆成几种完全不同的情况:
| Trace 或帧指标信号 | 更可能的含义 | 优先处理方向 |
|---|---|---|
| Raster 长时间处于 Sleeping 或等待状态,帧率正常 | Raster 已提前完成工作,正在等待下一帧或同步条件 | 通常无需修复 |
| Raster 处于 Runnable,但长时间没有获得 CPU | 亲和性、cpuset、优先级竞争或系统调度限制 | 检查调度与 CPU 限制 |
| Raster 正在运行,且 Raster 耗时持续偏高 | 绘制任务过重,或当前任务无法充分并行 | 降低负载或核对 Engine 并发能力 |
| UI、GPU 提交等阶段耗时更长 | 瓶颈位于 Raster 上游 | 修复 UI 或 GPU 阶段 |
“8 个大核”这一说法需要分别核实:总核心数、在线核心数、性能核心数、线程被允许运行的核心数,以及采样窗口内真正活跃的核心数。这五项的含义不同。只有当 Cpus_allowed_list 或系统 cpuset 确实把 Raster 限制在两个核心时,才能确认存在亲和性限制;如果允许范围很宽,“只看到两个核心”本身不足以证明问题。
再区分亲和性限制、串行工作和上游等待
先比较进程级和线程级的 Cpus_allowed_list。如果整个进程都被限制在少数核心,问题通常来自系统 cpuset、调试脚本或启动配置;如果进程范围正常而某个 Raster TID 被单独收窄,则应继续检查应用、插件或 Engine 内部是否修改过亲和性。taskset -pc 显示的是线程当前允许使用的核心范围,不代表线程此刻实际运行在多少个核心上。
亲和性正常时,还要看调度条件。Perfetto 中的唤醒到运行延迟、线程切换、CPU idle、频率变化和抢占记录,能够区分 CPU 繁忙、线程优先级竞争、后台任务干扰、功耗模式以及温控降频。Raster 优先级高并不意味着应该继续提高它;先确认线程为什么没有及时运行,再决定是否调整应用负载。
串行化边界取决于当前 Flutter Engine、渲染后端和具体帧结构。资料对 Flutter、Android 渲染路线的概括是以直接光栅化为主,光栅化和合成同步执行;Chromium 常见的异步分块光栅化则允许光栅与合成异步进行。 这可以帮助理解传统实现边界,但不能代替对目标 Flutter 版本和实际渲染后端的核对。手机有八个核心,不代表同一帧会自动被拆给八个 worker。
上游等待也容易被误判为 Raster 性能问题。应通过 Trace slice 和线程栈确认 Raster 是否在等待 UI 线程产出图层树、上一帧提交或 GPU 同步条件。图片解码如果发生在其他线程或资源加载阶段,就不应未经证据直接计入 Raster 耗时。
首屏卡顿和持续掉帧也应分开处理。首屏优先检查图片解码、资源缓存和首次绘制;持续掉帧则检查复杂图层、过度绘制、离屏缓冲、线程竞争及温控。长时间测试后逐渐掉帧时,还应记录功耗模式、CPU 频率与温度变化。Profile 正常而 Release 异常,或者不同设备结果差异明显时,需要同时记录 Flutter SDK、Engine revision、渲染后端和启动参数,不能把一台设备的结论直接套到另一台设备。
按证据修复,并用帧指标验收
如果确认是亲和性或 cpuset 限制,修复方式取决于限制来源。由调试脚本或人为设置产生时,应恢复合理范围;由应用或 Engine 自身设置时,应优先升级版本或修正对应实现;由系统或 OEM 策略造成时,应用侧不宜长期硬绑核心,而应降低渲染负载并保留系统调度空间。
不建议把“手动把 Raster 绑定到大核”作为通用修复方案。手动绑定可以作为受控实验,但可能让 Raster 与其他性能核任务直接竞争。只有在证据证明存在非预期限制,并且 A/B 数据证明绑定后帧耗时稳定改善时,才值得保留。
调度竞争和温控问题应从持续负载入手。设备已经降频时,增加线程或开启并发不能绕过系统限制;更有效的方向通常是减少无效绘制、降低同时发生的资源峰值,并排除后台任务干扰。
Engine 和渲染后端差异需要按版本核对。Flutter 官方文档提供了升级稳定版、切换发布通道以及获取历史 SDK 的路径。 如果目标版本确实提供并发光栅化能力,应按照该版本文档进行单独 A/B,保留设备、场景和构建模式不变。旧文章中的具体开关名和默认值不能直接照抄,发布前应再次核实。
Raster 工作量过大时,应减少不必要的可见图层、过度绘制、复杂裁剪和阴影,以及没有明确收益的离屏缓冲。资料指出,为图层分配独立像素缓冲区会引入额外缓冲和间接光栅化成本,因此应把 RepaintBoundary、Relayout Boundary 视为边界控制工具,而不是“自动并行光栅”的开关。
首屏资源压力可以通过预取缓解,例如:
await precacheImage(
const AssetImage('assets/banner.png'),
context,
);
precacheImage 可以提前准备即将使用的图片资源,降低首次使用时的解码与加载压力。 但它不会让 Raster 自动使用更多核心,也不能替代对首屏绘制成本的测量。如果 Trace 显示 UI 或 GPU 提交才是主要瓶颈,应优先修复对应阶段。
验收时应在相同设备和场景下比较 Raster 耗时、总帧耗时、掉帧情况、线程状态分布、亲和性范围、CPU 频率和温度变化,并且每次只修改一个变量。对于热榜中的“8 核设备、Raster 看起来只跑在 2 个核”案例,完整的前后对照至少应记录设备实际核心拓扑、Flutter SDK 与 Engine 版本、渲染后端、Trace 中的 Raster TID 与线程状态、唯一修复动作,以及 Raster 耗时和掉帧指标。原标题没有提供的具体版本、命令和数值,不应自行补写。
CPU 占用更高不是验收标准。负载优化后 Raster 利用率进一步下降,同时帧耗时和掉帧改善,往往是正常且理想的结果。
可对相同场景录制系统 Trace,确认 Raster 的 TID、线程状态和 Cpus_allowed_list;每次只改变一个限制因素,再复测同一组帧指标。
[email protected]
。
我们承诺在核实信息后,将第一时间删除相关内容或断开链接。
























暂无评论内容