[Perf] (P12): with the budget off and a deep reply buffer the window pays - 28.66 s -> 11.45 s at W=4, and the round trip does NOT grow, so the ~5 ms is a wakeup the server batches away

The second one-line experiment, and the one that decides whether the retention design
is worth building. Ring 1024 plus the residency budget DISABLED (its constant raised
out of reach), W=2 and W=4:

  arm                        waits   rtt_mean   by_op 2    wall
  W=1 (today's default)      4,543   5,672 us     4,536   28.66 s
  budget on + ring 8         9,078   2,598 us     9,071   27.04 s
  ring 1024 + budget 7       3,450   5,367 us     3,443   23.04 s
  ring 1024 + budget OFF W=2 2,560   4,916 us     2,553   16.80 s
  ring 1024 + budget OFF W=4 1,691   4,224 us     1,684   11.45 s

rtt_mean does NOT grow (4,916 / 4,224 us, both BELOW the 5,672 us baseline) while the
waits fall by 2x and 2.7x and the wall follows. So the ~5 ms is a WAKEUP round trip and
the server drains a batch per wakeup - which is exactly what makes W pay, and it is the
question the audit listed as undecidable from the repo and the watershed for whether
this is worth doing at all. It is.

AND IT BEATS THE HANDOFF'S OWN FLOOR: handoff-2 section 0.4 put the window's landing at
15-20 s, treating the client's ~13.4 s CPU as ADDITIVE with the waits. This frame turns
out to be wait-dominated, so amortising four creates per wakeup lands at 11.45 s, 3.5 s
below that estimate.

1024 IS A STAND-IN, NOT THE DESIGN: it works only because 1024 happens to be deep enough
for this workload, and 1024 retained answers is ~2 GiB worst case (a ReadPixels answer can
be 2 MiB). Retention gives the same property - the answer is still there - with a bound
tied to the client's own deferral depth, which is why the implementation the audit
specified is what gets built, not "make the ring 1024".

Also corrects the note's section 7 path 1 (audit F15): on the stream arm SEG_REPLY and the
Hello's ReplySlotCount have nothing to do with answers - they travel as Reply frames and
StreamLink::ReadReply reads the client's own store - so "deepen the pool" does not address
the arm that was measured, and should not be paid for.

Reverted (ring 8, budget 7, probes deleted); 172 + 13 green.
This commit is contained in:
BZLZHH
2026-09-26 03:01:20 +08:00
parent 82de3f6826
commit 1ef7439625
@@ -253,7 +253,41 @@ if (x.replySeq != seq) return ... MOBILEGL_ERR_PROTOCOL_MISMATCH; // 等的是
实验状态已还原(环回到 8、探针全部删除),四个车道 172+13+22+103 全绿。
## 11. 复算
## 11. 第 0b 步:预算关掉 + 环 1024 —— 收益是量出来的,不是外推的
§10 证明「环够深,延后的答案就都读得回来」,但那一轮 `by_op 2 = 3,443` 里绝大多数是**预算**触发的
(3,395),所以「W 变小能不能换来墙钟」还没答。于是再做一次一行实验:**环 1024 + 把预算关掉**
(`kCreateReplyResidency` 抬到不可能达到),W=2 与 W=4 各跑一轮:
| 臂 | waits | rtt_mean | `by_op 2` | **墙钟** |
|---|---|---|---|---|
| W=1(现默认) | 4,543 | 5,672 µs | 4,536 | 28.66 s |
| 预算开 + 环 8 | 9,078 | 2,598 µs | 9,071 | 27.04 s |
| 环 1024 + 预算 7 | 3,450 | 5,367 µs | 3,443 | 23.04 s |
| **环 1024 + 预算关,W=2** | **2,560** | **4,916 µs** | **2,553** | **16.80 s** |
| **环 1024 + 预算关,W=4** | **1,691** | **4,224 µs** | **1,684** | **11.45 s** |
(W=4 那轮的窗口读数:`taken=4498/4500 pushed=4500 blocked=0 readfail=0`。)
**结论一:分水岭答了,而且是好的一侧。** 等待数减半/减到 1/2.7,墙钟跟着减半/减到 1/2.5,
而 **`rtt_mean` 没有变大**(4,916 / 4,224 µs,比基线的 5,672 µs 还低)。这说明那 ~5 ms 是**唤醒往返**,
而且**服务端一次唤醒吃掉一整批**——所以 W 深一档就少一档唤醒,等待数与墙钟同步下降。
(审计把这条列为「仓库里判不出来、只有真机能答」的分水岭,它同时也是「这件事值不值得做」的分水岭。)
**结论二:收益比 handoff 的地板还低。** handoff §0.4 估计「窗口生效后 15-20 s」,而那把客户端 CPU(~13.4 s)
当成与等待**相加**;实测这个帧是等待主导的,等待摊薄之后 W=4 到 **11.45 s**——比估计的地板还低 3.5 s。
**注意 1024 是替身,不是设计。** 它能工作只因为「这个负载下 1024 恰好够深」;1024 个条目最坏是 ~2 GiB
(`ReadPixels` 的答案可到 2 MiB),审计已点名。**保留表给的正是同一个性质(答案还在)而有界**,
所以这一步的结论是「按审计的形状去实现」,而不是「把环改成 1024」。
实验状态已还原(环回 8、预算回 7、探针删除),172 + 13 全绿。
**顺带更正 §7 的第 1 条路**(审计 F15):在 stream 臂上 `SEG_REPLY` / Hello 的 `ReplySlotCount` **与答案无关**
——答案走 Reply 帧、`StreamLink::ReadReply` 读客户端自己的 store,池只用来给 `MaxReplyBytes()` 定尺寸。
所以「加深池子 + 抬 `ReplyBytes`」根本没有针对实测那条臂,别为它付 8× 常驻内存。
## 12. 复算
~~~sh
# 两臂的帧读数(handoff-2 §1 的读法)