[Docs] (P12): the ~5 ms per round trip is the DEVICE's deep-idle exit, not the link and not the client - which is what the handoff measured and could not explain

- Three one-variable experiments on the same device, same workload (the 43k-record frame): a 40x
  larger client spin moves the per-wait mean by ~10% (so it is not the client park/wake); the
  same frames over adb forward - a link whose raw RTT is several times lower - by ~25-30% (so it
  is not the link, which is what the handoff saw in 3.6 and could not account for); and pinning
  the device (governor performance on all eight cores, cpuoff_l / clusteroff_l / mcusysoff /
  s2idle disabled) by 26% per wait and 25% of the whole frame: 221.5 s -> 166.5 s for 43,218
  records and 100.5 MB staged, against 43,232 and 101.4 MB in the baseline.
- The mechanism: the server's apply thread parks between records, the SoC drops into a deep idle
  state, and every record pays the exit latency - milliseconds, once per record, which is the
  entire shape of the frame. It also matches the server profile the handoff took (65% socket
  syscalls, ~15% doorbell spin: waiting and waking, not working).
- Stated where it matters: the handoff's 249 s was taken on an UNPINNED device, so its absolute
  numbers carry this idle tax - the project has a pinning discipline for exactly this reason.
- The lever list is now ordered: batch the waits (K <= 8 by the reply-slot pool, client-only),
  keep the server hot between records (its own spin budget, which the APK does not wire yet), and
  measure split numbers on a pinned device. The device was restored to stock afterwards.
This commit is contained in:
BZLZHH
2026-09-25 21:28:13 +08:00
parent 6e7c0ce710
commit 7c72dfde2b
2 changed files with 30 additions and 1 deletions
+7 -1
View File
@@ -15,7 +15,13 @@
按形状实测(`RemoteClientControls.ReplyWaitsByOpForAnAtlasShapedLoad`,主机 inproc,按 op 计数):**图集拼接**(一张 level 上 64 次 `glTexSubImage2D`)**64 → 0** 次回包等待;
**每 sprite 一张纹理**(64 张纹理 + 256 次 `glTexParameteri`)**384 → 320**,剩下的全是 per-resource / per-call 的三条 row(create 76 / respecify 130 / params 130)——
它们的拒绝是「服务端没有这个 handle」的唯一客户端可见信号,删掉等待就是删掉信号,裁定见 `CONTRACT-P5E.md` §2.5。
**真机端到端墙钟仍待复测**,见 [`notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md`](notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md)。
真机(TrebleDroid GSI,MC 26.2 经 TCP 入世界)那一帧的构成为 `ResourceRespecify` 20,633 + `SetTextureParams` 18,056 + `ResourceCreate` 4,536 条**各等一次**,
而 `ResourceSubData` 12,671 条**零等待**(B 的实机证据);这些记录都是真实状态变化,客户端没有可去重的(同一值的 `glTexParameteri` 本来就不产生记录)。
- **单次往返那 ~5 ms 是设备侧的深度空闲退出,不是链路也不是客户端**(P12,本轮实测):同一帧、同一批记录,
客户端自旋 40× 只快 ~10%;换 `adb forward`(USB)快 ~25–30%;而把设备 8 核钉 `performance` 并关掉 `cpuoff_l`/`clusteroff_l`/`mcusysoff`/`s2idle` 后,
单次等待 **5,040 → 3,715 µs**、那一帧墙钟 **221.5 s → 166.5 s(−25%)**——记录数与字节数都不变。
这解释了 handoff §3.6「换快 3.4 倍的链路墙钟不变」:钱花在设备每次唤醒上;handoff §3.2 那 249 s 是在**未定频**的机器上取的,含这份空闲税。
见 [`notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md`](notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md) §2.6–2.10。
- **画面正确**:Vulkan 后端真机画面检查 36/36 通过,CTS 五块相对基线没有超过 0.5 个百分点的退步、没有新崩溃(P7);server 自有窗口上屏两后端 SSIM 1.0(P12)。
## 按阶段
@@ -228,6 +228,29 @@ handoff §3.6 留过一个没解释的现象:把链路从 Wi-Fi 换到 USB(
而在「客户端 park → 服务端唤醒/读取/apply/回铃 → 客户端唤醒」这条本地路径上——这与两侧 profile 的形状一致(服务端 65% 在 socket syscall、约 15% 在门铃自旋)。
**结论:想动这一帧,只有两条路**——(a) 同样多的记录、更少的往返(把等待批起来,回复槽池 8 个 ⇒ K ≤ 8 可证明安全);
(b) 降低单次往返的本地成本(服务端 apply/wake 路径、`MOBILEGL_IPC_SPIN_US` 之类)。**不是**换链路,也不是去重记录(§2.7 已证否)。
### 2.9 那 ~5 ms 的真身:**设备侧的深度空闲退出**(本轮把 handoff §3.6 的谜题解开了)
§2.8 排除了链路,本节把剩下的候选一个个排除,最后落在一个可复现、可量化的原因上。同一台设备、同一份工作量(那一帧 43k 条记录),只改一个变量:
| 变体 | 单次等待均值 `rtt_mean` | 那一帧的墙钟 | 说明 |
|---|---|---|---|
| 基线(LAN + 客户端 spin 50 µs + 设备出厂省电) | 5.0–7.9 ms | **221.5 s**(43,232 waits / 101.4 MB staged) | |
| 客户端 `MOBILEGL_IPC_SPIN_US=2000`(自旋 40×) | 4.8–6.9 ms | — | 只快 ~10% ⇒ **不是客户端 park/wake** |
| `adb forward`(USB,裸 RTT 低数倍) | ~25–30% 更快 | — | ⇒ **不是链路**(§2.8) |
| **设备 CPU 钉住**:8 核 governor→`performance`,关掉 `cpuoff_l`/`clusteroff_l`/`mcusysoff`/`s2idle` | **3,715 µs**(帧内均值从 5,040 降下来) | **166.5 s**(43,218 waits / 100.5 MB staged) | ⇒ **就是它**:同一条数、同样的字节,**快了 25%** |
**机理**:服务端 apply 线程在两条记录之间 park,SoC 随即掉进 `cpuoff_l`/`mcusysoff` 这类深度空闲;下一条记录到达时要付**退出延迟**(毫秒级)。
每一条记录一次唤醒 ⇒ 每次往返 ~5 ms,与链路无关(§2.8),也与客户端怎么等无关(自旋试验)。
**这同时解释了 handoff 的两处观察**:§3.6「换快 3.4 倍的链路墙钟不变」(因为钱花在设备的唤醒上),以及 §3.3 服务端 profile 里那 65% 的 socket syscall / 15% 门铃自旋(等待与唤醒的形状)。
**也要说清楚**:handoff §3.2 那 249 s 是在**没有**做设备定频/关空闲的机器上取的(项目自己的纪律 `guide/pin-verification-2026-09-07.md` 就是为这件事写的),所以它的绝对值里含这份空闲税。
### 2.10 于是「2–3 分钟停滞」的修法清单(按预期收益排序)
1. **把等待批起来**(K ≤ 8,回复槽池可证明):同样多的记录、少得多的唤醒。与第 2 条互补,且**纯客户端**,不需要设备权限。
2. **让服务端在记录之间保持"热"**:它自己的自旋预算(设备日志里的 `spin 50 us`)——但 APK 今天没有把 `MOBILEGL_IPC_*` 接进去(P12 阶段表里那条 "FCL env 与 plugin APK 开关表接线" 未做),所以真机上只能靠 root 钉频率来证明这块蛋糕有多大(本节已证明:25%)。
3. **测量纪律**:任何 split 真机数字都应该在定频+关深度空闲的设备上取,否则把设备省电策略算进了"协议代价"。
## 4. 证据位置
| 位置 | 内容 |