Ente 移动端 ML 调度验证报告:DVFS 与线程调度如何让 Pixel 8 的 Rust 预处理慢 2.8 倍,以及如何修复
【免费下载链接】ente💚 End-to-end encrypted cloud for everything.项目地址: https://gitcode.com/GitHub_Trending/en/ente
导读
Ente 的移动端 ML 索引管线(人脸检测、CLIP 图像嵌入、人脸特征提取)在 Android(ONNX Runtime WebGPU)与 iOS(CoreML)之间存在惊人的端到端差距。2026-07-22 的基准显示 iPhone 15 Pro 在 Rust 预处理上快约 12 倍、串行解码快约 4 倍,远超两颗 SoC 之间约 1.6~2 倍的真实硅片差距。本文基于仓库内的 Android ML 调度验证报告,完整复盘其四个受控实验变体、sched_getcpu()+ DVFS 频率采样等新型埋点、假设验证结论,以及按预期价值排序的六条生产落地建议。读完本文,你将掌握:如何用频率采样与线程集中来诊断"突发型负载下 CPU 提频不足"这一 Android 移动端性能顽疾,以及 Ente 计划如何通过单线程管线、CPU/GPU 流水线、ADPF 等手段回收 2.8 倍预处理性能。
一、背景:07-22 基准报告中的异常差距
验证实验的起点是 20260722_FINAL_MOBILE_ML_BENCHMARK_REPORT.md。该报告在 Pixel 8(Android 17,ONNX Runtime WebGPU)与 iPhone 15 Pro(iOS 26.5.2,ONNX Runtime CoreML)上,用同一 14 图语料测得如下稳定态数据:
| 设备 | 端到端 14 图 | 每图 | Rust 总耗时 | Rust 每图 |
|---|---|---|---|---|
| Pixel 8 WebGPU | 12,994.3 ms | 928.2 ms | 7,265.3 ms | 518.9 ms |
| iPhone CoreML(warm) | 5,022.2 ms | 358.7 ms | 1,789.8 ms | 127.8 ms |
其中 iPhone 端到端快 2.59 倍,Rust 管线内快约 4 倍。但分阶段看,差距分布极不均匀:
| 阶段 | Pixel 8 | iPhone 15 Pro | 差距 |
|---|---|---|---|
| Decode | 2,771.0 ms | 1,169.6 ms | ~2.4x |
| Rust 预处理 | 652.8 ms | 55.1 ms | ~11.8x |
| Inference(WebGPU vs CoreML) | 3,720.0 ms | 557.9 ms | ~6.7x |
| Rust 后处理 | 12.7 ms | 2.3 ms | ~5.5x |
| Rust 其他 | 117.2 ms | 0.5 ms | — |
预处理 11.8 倍、后处理 5.5 倍的差距,远不能由 WebGPU/CoreML 之间的推理引擎差异(5.7~8.8x,属真实平台差距)或硅片差异解释。这构成了本次验证实验的直接动机:Pixel 的 CPU 侧慢,到底慢在硬件、并行度还是调度?
二、实验设计:四个变体与有效性控制
验证实验在ort_opt_ios分支(HEAD3c9ee2cb76追加基准埋点)上进行,设备为 Google Pixel 8(shiba),全部变体满足:AOT release(connectedIndependentReleaseAndroidTest,每轮验证dart.vm.product=true,变体 B 额外通过ML_PARITY_REQUIRE_RELEASE=true门禁)、14 图语料、每图 1 次不计时预热 + 3 次计时采样、按文件取中位数。每个变体前后设备热状态均为 1(轻度),跨变体一致,因此横向对比内部自洽。
四个变体的编译期覆盖与bench_config确认如下:
| 变体 | 编译期覆盖 | bench_config确认 |
|---|---|---|
| A | 无(基线 + 放置采样) | rayon 池 9 线程,无亲和性 |
| B | ENTE_ML_BENCHMARK_RAYON_THREADS=1 | 池配置为 1 线程 |
| C | ENTE_ML_BENCHMARK_CPU_AFFINITY=4-8 | 掩码0x1f0,9/9 工作线程钉在 mid+big |
| D | 两者皆用,亲和性8(仅 Cortex-X3) | 池 1 线程,全部工作压到 cpu8 |
其中 A 为基线,B 用于检验 rayon 扇出(fan-out)是否是预处理瓶颈,C 用于检验小核放置的影响,D 用于检验"线程集中 + 单大核"能否让 DVFS 持续提频。上述ENTE_ML_BENCHMARK_*覆盖开关是基准专用、编译期可选的能力,生产构建默认不开启(07-22 报告中明确:基准日志通过ENTE_ML_BENCHMARK_LOGGING编译期 opt-in,Android release 埋点通过ENTE_ML_BENCHMARK_RELEASE_TESTS=1开启,普通构建不受影响)。
新增的埋点(编译出生产构建之外)包括:在各阶段时钟之外采样sched_getcpu()与 DVFS 频率,以及在每次管线之后运行一次空broadcast的 rayon 唤醒延迟探针。
三、结果:阶段到底跑在哪、跑在多少 MHz
Pixel 8 的集群拓扑为:cpu0-3 Cortex-A510("little",最高 1.70 GHz)、cpu4-7 Cortex-A715("mid",最高 2.37 GHz)、cpu8 Cortex-X3("big",最高 2.91 GHz)。
基线 A 下各阶段的实际放置与频率(占总阶段时间的份额):
| 阶段(变体 A) | 放置位置 | 中位频率 |
|---|---|---|
| decode(长突发) | 42% mid / 58% big | mid 1,418 MHz,big 2,687 MHz |
| 预处理(短突发) | 3% little / 61% mid / 37% big | mid910 MHz,big 1,557 MHz |
| 后处理(µs 级突发) | 98% mid | 697 MHz |
这直接测量到了 07-22 分析所预测的模式:只有足够长的 decode 突发能在突发中途挣到高频率;短促的预处理/后处理突发总是"冷启动、冷结束"。ML 管线的 CPU 短突发(每次跟随约 240 ms 的 GPU 推理休眠,在约 9 个工作线程间轮转)从未在每个线程上累积出足够的利用率,让 Android 调度器提频或迁移到大核。基线预处理大部分时间跑在 mid 核上,中位仅910 MHz,只有其 2.37 GHz 上限的 38%。
变体 D(全部工作集中到一个 big 核)的效果:持续频率升至2,363 MHz,语料预处理总耗时从596.7 ms 降至 212.4 ms(2.8 倍),落入硅片应有的水平区间;尽管只用 1 个核而非 9 个核,却产生了所有变体中最快的端到端运行。
13 个共有 fixture 的语料汇总:
| 语料总计(ms) | A:基线 | B:串行 rayon | C:无 little 核 | D:串行 + 1 big 核 |
|---|---|---|---|---|
| decode | 2,772.1 | 3,907.5 | 2,898.9 | 3,194.0 |
| Rust 预处理 | 596.7 | 535.9 | 472.2 | 212.4 |
| inference(WebGPU) | 3,349.5 | 3,947.7 | 3,622.8 | 3,159.8 |
| Rust 后处理 | 13.6 | 8.5 | 11.4 | 9.6 |
| Rust 其他 | 103.0 | 119.0 | 105.4 | 62.7 |
| Rust 总计 | 6,874.5 | 8,536.2 | 7,073.9 | 6,652.6 |
在变体 D 中,同一预处理的每次调用中位数从 15.0 ms 降到5.4 ms(p90 从 30 ms 降到 10 ms,最大值从 81 ms 降到 21 ms)。
rayon 唤醒探针(每次 resize 分发的扇出屏障开销)数据显示:变体 A 中位数 2.2 ms、p90 4.9 ms,且 46% 的池工作线程在小核上唤醒;即便在 C(全部线程钉到 mid+big)中仍是 2.4 ms 中位数——说明该开销本质是空闲线程退出延迟,而非小核放置问题;单线程池(D)下则骤降至 0.10 ms。
四、假设验证:H1 被否决,H2 被确认
H1 — rayon 扇出屏障主导预处理:被否决为主因,被确认是真实次要成本
将池串行化(B)只回收了约 597 ms 语料预处理中的约 60 ms(约 10%),且呈双峰分布:小图显著受益(1343.jpg从 39.6 ms 降至 13.0 ms——纯屏障开销),而大图或人脸密集图反而变差(pano39.7 → 59.3 ms,people.jpeg74.2 → 84.8 ms),因为丢失了真实的并行计算。结论是每次分发的 2~5 ms 屏障税真实存在但有限。
从源码可以印证该扇出的存在:ente-mlcrate 的 Cargo.toml 中fast_image_resize = { workspace = true, features = ["rayon"] }显式启用了 resize 库的 rayon 并行特性,且 crate 直接依赖rayon = "1.12.0"。实际 resize 调用链位于 preprocess.rs(YOLO 输入预处理使用fast_image_resize的Resizer与双线性插值)以及 cv/resize.rs 等模块。这正是报告中"每次 resize 分发的 2~5 ms 屏障税"的代码出处。
H2 — 冷核/DVFS 放置主导:确认,且频率是决定性杠杆
排除 little 核(C)只让预处理改善 21%,因为 mid 核仍以 910 MHz 空转。把工作集中到一个核(D)后,schedutil得以维持 2,363 MHz,带来 2.8 倍改善。结论:单靠放置(affinity)不是解药,持续的利用率(sustained utilization)才是。
解码并行值得保留
串行解码(B)在语料范围内多花 +1,135 ms,几乎全部来自两个大/平铺 HEIC(pano714 → 1,426 ms,8606239 → 519 ms);普通 HEIC 本就接近串行解码。而在升频后的核(D)上,串行 JPEG/PNG/WebP 解码器反而比基线更快(astronaut.png11.6 → 5.0 ms,ui_app.webp121.5 → 79.9 ms)。
与 iPhone 的对账
在 13 个共有 fixture 上,iPhone 15 Pro 的预处理总量约 51 ms。变体 D 把 Pixel 从落后 11.7 倍拉近到约 4.2 倍;残余差距来自真实的硅片差距(单核约 2~3 倍)加上 X3 在推理休眠间隙只维持 2,363 MHz(而非 2,914 MHz)。07-22 报告中"Pixel 处处慢约 10 倍"的读法被证伪——那是在测量调度器行为,而非硬件。
五、异常与注意事项
- CR2 平台回退失败:
IMG_8905.CR2在 C、D 变体中其平台 JPEG 回退失败(在 A、B 及所有 07-22 运行中成功)。两次失败都发生在测试框架开始保留已安装应用(持久化应用数据)之后,因此首要嫌疑是持久化应用数据而非亲和性覆盖本身。任何亲和性相关改动上线前需针对性重跑。共有文件分析已剔除该 fixture,头条数字不受影响。 - WebGPU 推理波动:各变体间推理耗时 ±9% 波动(3,160~3,948 ms)且无明确排序,变体间推理差值应视为噪声。
- 热状态差异:本次全程热状态 1(07-22 为 0),但基线 A 仍复现了 07-22 的语料总量(decode 2,772 vs 2,771 ms;预处理 597 vs 653 ms),证明埋点与热状态没有扭曲基准。
- logcat 环形缓冲:A/B 的 logcat 各有一个 fixture 的事件在缓冲区扩容到 16 MB 前被驱逐,已由共有文件分析处理。
- 变体 D 的诊断性质:硬钉一个核是诊断手段而非可上线配置——它独占 X3、无视热余量、与其他负载冲突。
六、生产建议:按预期价值排序
报告为生产 Android 索引给出的建议(按预期价值排序):
- 停止把管线工作轮转在 FRB 工作线程池上;改为在单一持久专用线程上运行 ML 管线(可选第二线程专用于解码)。这是变体 D 结论的可上线形态:线程集中才能让
schedutil维持高频,第一步无需任何 pinning。 - CPU 与 GPU 流水线化:在图像 N 处于
Session::run内时,同步解码/预处理图像 N+1。这一举两得:完全隐藏预处理延迟,同时保持工作线程高利用率以维持频率——还能同时打击解码的 2~4 倍缺口,这是单阶段修复做不到的。 - 采用 ADPF(
PerformanceHintManager)为 ML 工作线程设置每图目标时长。这是 Android 官方针对"突发型负载 + DVFS"问题的既定机制,可替代任何亲和性 hack。 - 从
rust/crates/ml中移除fast_image_resize的rayonfeature(保留heic_decoder自身的 rayon 并行用于解码)。resize 扇出从未回本——B 变体在无它时预处理净更快——且每次分发还要付出 2~5 ms 屏障税。小而安全、立即可做。 - 不要上线 CPU 亲和性 pinning,除非 CR2 异常得到解决并在争用/热负载下测试;仅当 1~3 项不达预期时才重新考虑。
- 修正 07-22 报告的跨平台表述:CoreML 对 WebGPU 的推理差距(5.7~8.8 倍)是真实平台差距,但预处理/后处理/解码差距约 1.6~2 倍硅片差距 + Android 调度债务,后者可由 1~4 项回收。
报告对 1+2+4 项组合(暂不含推理侧工作)的稳定态影响预估:端到端约 10~20%(本语料上预处理 597 → 约 210 ms,"其他"103 → 约 60 ms,串行解码 fixture 快 1.5~2 倍,大 HEIC 解码不变),更大的战略价值在于CPU 成本不再随调度器"心情"波动,WebGPU 推理差距成为 Android 唯一的剩余短板。
七、结论:性能问题的层级与取证方法
本次验证的完整叙事是:Pixel 8 的 CPU 侧慢,根因不是硬件、也不是主要来自 rayon 扇出,而是 DVFS 与调度。短 CPU 突发跟随长 GPU 休眠、跨约 9 个线程轮转,导致每线程利用率不足、调度器不提频,基线预处理长期运行在 38% 的峰值频率上。将工作集中到单一大核后,2.8 倍性能被直接"解冻"。
这一结论对移动端 ML 工程有普遍方法学意义:
- 跨平台基准必须先排除调度因素,才能讨论硅片差距——否则会把调度器行为误读为硬件差距;
- 诊断突发型负载性能问题时,同时采样放置(
sched_getcpu)与频率(DVFS)是不可或缺的一对观测手段; - 修复方向应该是线程集中 + CPU/GPU 流水线 + ADPF 提示,而不是盲目堆并行度或依赖亲和性硬钉。
相关产物与深入阅读
本次验证的机器可读产物与工具位于 infra/ml/test:
- 各变体日志、热快照与结果载荷:
infra/ml/test/out/sched_verification_2026-07-23/{A,B,C,D}/device_logcat.txt与.../{A,C,D}/results.json(B 的载荷因测试框架测试后自动卸载而丢失,其基准数据完整保留在 logcat 中); - 分析脚本:
python3 infra/ml/test/tools/analyze_ml_sched_verification.py <device_logcat.txt>(输出放置、频率、探针与语料汇总); - 过程文档:
20260723_ANDROID_SCHED_VERIFICATION_RUNBOOK.md(与本文同目录)。
与本主题直接相关的仓库源码与配置:
- rust/crates/ml/Cargo.toml(
fast_image_resize的 rayon feature、rayon依赖、Android/iOS 的 ORT provider feature) - rust/crates/ml/src/preprocess.rs(YOLO 输入预处理:resize + letterbox + 归一化)
- rust/crates/ml/src/cv/resize.rs(通用图像 resize 路径)
- infra/ml/playground/optimizations/README.md(模型优化流水线:GELU 融合、PReLU 改写等,保证同一 ONNX 产物可被 CoreML 与 WebGPU 共用)
- infra/ml/playground/optimizations/benchmark_reports/20260722_FINAL_MOBILE_ML_BENCHMARK_REPORT.md(本文的基线报告)
如需复现基准,可参考 infra/ml/test/run_ml_parity_tests.sh 与 infra/ml/test/README.md 了解 14 图 ML 一致性语料(含man.jpeg、people.jpeg、singapore.jpg等 fixture,位于 infra/ml/playground/data)的构成与运行方式。
【免费下载链接】ente💚 End-to-end encrypted cloud for everything.项目地址: https://gitcode.com/GitHub_Trending/en/ente
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考