From f99415a493f7effeb555da8dc4cea3f04a04b14b Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Fri, 25 Sep 2026 21:03:42 +0800 Subject: [PATCH] [Docs] (P12): the atlas window's exact composition, and the negative result that kills the record-dedup idea before anyone builds it --- .../MG_Test/Wire/RemoteClientControls.inc | 25 +++++++++++ .../notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md | 43 ++++++++++++++++--- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/MobileGL/MG_Test/Wire/RemoteClientControls.inc b/MobileGL/MG_Test/Wire/RemoteClientControls.inc index 7976cd19..6160b5dc 100644 --- a/MobileGL/MG_Test/Wire/RemoteClientControls.inc +++ b/MobileGL/MG_Test/Wire/RemoteClientControls.inc @@ -970,6 +970,31 @@ TEST(RemoteClientControls, ReplyWaitsByOpForAnAtlasShapedLoad) { MG_Impl::GLImpl::TexParameteri(0x0DE1, 0x2800, 0x2601); // MAG_FILTER = LINEAR MGP_FILL(DrawArrays); // the drain point } + // A PROBE FOR REDUNDANCY, before anyone tries to remove it: does a repeated set of the + // SAME value produce another record? If it does not, then the device run's 18,056 + // set_texture_params records in one window are genuine changes and there is nothing to + // dedup - and any "fix" would be a fix for a problem that does not exist. + { + const Uint32 paramsOp = static_cast(MGPWireOp::SetTextureParams); + const Uint64 beforeSame = Transport::LinkMetricsRecordsFor(paramsOp); + MG_Impl::GLImpl::TexParameteri(0x0DE1, 0x2801, 0x2600); // MIN_FILTER = NEAREST + MGP_FILL(DrawArrays); + const Uint64 afterFirst = Transport::LinkMetricsRecordsFor(paramsOp); + MG_Impl::GLImpl::TexParameteri(0x0DE1, 0x2801, 0x2600); // the SAME value again + MGP_FILL(DrawArrays); + const Uint64 afterSame = Transport::LinkMetricsRecordsFor(paramsOp); + MG_Impl::GLImpl::TexParameteri(0x0DE1, 0x2801, 0x2601); // a DIFFERENT value + MGP_FILL(DrawArrays); + const Uint64 afterChange = Transport::LinkMetricsRecordsFor(paramsOp); + std::printf("params probe: first=%llu same-again=%llu changed=%llu (deltas %llu/%llu)\n", + static_cast(afterFirst - beforeSame), + static_cast(afterSame - beforeSame), + static_cast(afterChange - beforeSame), + static_cast(afterFirst - beforeSame), + static_cast(afterSame - afterFirst)); + std::fflush(nullptr); + } + // PRINT FIRST, ASSERT SECOND: the table is the point of the case, and a gate that fired // before it would hide the numbers on exactly the run that needs them - the red one. // diff --git a/docs/Disaggregated/notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md b/docs/Disaggregated/notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md index afe743dd..bb9656d6 100644 --- a/docs/Disaggregated/notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md +++ b/docs/Disaggregated/notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md @@ -163,14 +163,47 @@ handoff §5.4 说过这条数字拿不到——spawn 下 client 不推进帧窗 **handoff §4.2 的「55,428 → 约 3」不成立**:那一帧剩下的 43,232 次等待正是 §2.4 裁定「必须等」的三条 row(create / respecify / params), 按 96.7 MiB ÷ 每 sprite 一次上传估算,约每 sprite 3.5 次非上传等待。**B 是必要的,但不是那 2–3 分钟停滞的充分解**—— 要动那一段,方向不是「把等待去掉」(那会删掉信号),而是**把记录数压下来**(同样的参数不必每条都发、同一 level 的 respecify 可去重)。这一条留给下一轮。 + +### 2.6 那一帧到底由什么构成(真机,`kind=frame-op` + `kind=frame-sent`) + +第四次运行抓到了同一形状的那一帧(`wait_replies=43,232`、`stage_bytes=101,421,396`),两条行合起来第一次把「付了什么」和「发了什么」对齐: + +| op | 发的记录 | 付的等待 | 说明 | +|---|---|---|---| +| `ResourceRespecify` (3) | 20,633 | **20,633** | 每条一等 | +| `SetTextureParams` (47) | 18,056 | **18,056** | 每条一等 | +| `ResourceCreate` (2) | 4,536 | **4,536** | 每条一等 | +| `ResourceSubData` (48) | 12,671 | **0** | ⭐ B:记录照发,回包一个不等 | +| `DrawVbo` (59) / `SetShaderBuffers` (38) / `SetSamplerViews` (35) | 9,121 / 9,120 / 9,122 | 0 | 这些 row 的等待是 barrier 类,不在本计数里 | +| `ObjectDeath` (78) / `ResourceDestroy` (4) | 7,515 / 3,770 | 0 | 死亡路径不发问 | + +**每条等待正好对应一条记录**(三个 row 都是 1:1),所以 43,232 不是"等重了",而是"真的发了 43,232 条会问问题的记录"。 + +### 2.7 「把记录数压下来」这条路的结论:客户端没有可去重的记录 + +§3 上一轮把「少发记录」列为头号方向。本轮先做了一个**冗余探针**(`RemoteClientControls.ReplyWaitsByOpForAnAtlasShapedLoad` 里那段 `params probe`), +再去读设备上的实际计数,结论是否定的: + +~~~ +params probe: first=1 same-again=1 changed=2 (deltas 1/0) +~~~ + +- **同一个值再设一次不产生记录**(delta 0)——前端 setter 的冗余过滤 + emitter 的版本闩已经把它挡掉了; +- 换一个值才产生一条。 + +所以设备上那 18,056 条 `SetTextureParams` 是**真实的状态变化**,不是重复;`ResourceRespecify`/`ResourceCreate` 同理(respecify 的 `unchanged` memcmp 去重早就在了,`TextureEmit.h:838/858/865`)。 +**结论:客户端侧没有"少发记录"的空间**——剩下的唯一杠杆是「同样多的记录,更少的往返」,也就是把等待批起来(同一条 row 每 K 条等一次)或把单次往返的成本降下来(数据面 stream 的 syscall/wake 路径), +不是继续找冗余。顺带确认:`TCP_NODELAY` 已经在 `SocketTransport.cpp:100` 设了,5 ms 不是 Nagle 造成的。 ## 3. 剩余(下一轮的直接入口) 1. **把记录数压下来(新的头号项)**。§2.5 的真机数字说:那一帧剩下的 43,232 次等待全在「必须等」的三条 row 上, - 约每 sprite 3.5 次非上传记录。等待不能删(§2.4),但**记录可以少发**: - - `SetTextureParams` 只在参数真的变了才发(现在的版本闩是不是漏了?); - - `ResourceRespecify` 同一 level 的重复定义可去重(tracker 已有 `LastDesc`); - - `ResourceCreate` 每 sprite 一次是否必要(纹理其实是同一张图集)。 - 方向是「先看清楚那 43,232 条各是什么」,所以下一轮第一件事是**用逐帧 `frame-op` 行 + `ResourceSubData` 之外的计数器跑一次未缓存的冷启动**(本轮第二次运行图集走了缓存,只有启动两帧有量)。 + 约每 sprite 3.5 次非上传记录。**这条已经被本轮证否(§2.6/§2.7)**:记录是真实的,客户端没有可去重的空间。 + 于是唯一剩下的杠杆是「同样多的记录、更少的往返」,两个候选: + - **把等待批起来**:同一条 row 每 K 条等一次(回复槽池是 8 个,所以 K ≤ 8 是可证明安全的), + 代价是消费点的答案要能延后兑现——`ResourceCreate`/`ResourceRespecify`/`SetTextureParams` 的答案只用来「落闩 + 自愈」,两者都可以延后到下一个同步点; + - **降低单次往返的 5 ms**:`TCP_NODELAY` 已设(`SocketTransport.cpp:100`),所以不是 Nagle; + 数据面是 `stream`(socket),服务端 profile 曾经 65% 在 socket syscall —— 下一轮该重新采一次两侧的栈(handoff §3.3 的 `perf` + `simpleperf` 跑法仍然有效), + 把「5 ms 里有多少是网络、多少是两侧 wake/调度」分开。 2. **受控真机 A/B**:本轮两台设备不同(GSI vs handoff 的 90cee93),55,428 vs 43,232 只能算旁证。要给出可比的数字,需要在**同一台设备**上跑一次带 B、一次不带 B(工作树里已有 `build-split/fix-b2.patch` 的往返做法)。 3. ~~真机端到端~~ **已完成(§2.5)**:A 的崩溃路径在真机上走过两次都没崩;B 在真机上按设计生效(op 48 零等待)。工具在仓库外的 `mgl-device/`: `launch-mc.py`(按版本 JSON 重建启动命令,支持 `MC_QUICKPLAY` / `MC_GAMEDIR`)、`gl-aliases.sh`、`start-phone-server.sh`、`mkeystore`+签名步骤写在 README 里。