[Perf] (MG_Remote/Client): the buffer half of resource_respecify stops waiting - its answer is discarded at both call sites - and the measurement says the texture half is where the other 20,611 waits live

- The two halves of this row are not the same row. PipeFill.cpp:1105 and :1142 both call
  MGPipeRouteResourceRespecify(desc, nullptr) and drop the Bool, so for a buffer the wait bought a
  value that was thrown away. It goes fire-and-forget with the provisional-accept shape
  resource_subdata has had since item B.
- The texture half keeps its answer ON PURPOSE: there the Bool is the self-heal trigger AND the
  gate on the acceptance mirror, and TextureEmit.h:885-888 (ID-18 M3) says advancing that mirror on
  an unconfirmed answer is how the next identical call gets suppressed "against a record that was
  never stored". That half needs the provisional/really-accepted distinction plumbed through the
  reply mailbox before it can stop waiting; it is not a flag flip.
- MEASURED, same frame, same 101,421,396 B staged: waits 25,176 -> 25,154 and by_op 3=20633 ->
  3=20611. So the buffer half is worth 22 waits (0.1%) and the remaining 20,611 are texture
  respecifies - which is what says the next step is the mirror design, not another flag. Wall
  134.4 s -> 130.9 s.
- The ERROR-is-not-a-decline case moves to the texture half: a row that reads no answer cannot fold
  ERROR into a decision, so the rule it exists for belongs to the half that still waits (the
  transport fault is caught at the next row that does). New case
  ABufferRespecifyTakesNoReplyWhileATextureOneStillDoes pins both directions; red-once with the
  branch removed is exit 155. RemoteClientTest 169/169.
This commit is contained in:
BZLZHH
2026-09-25 22:53:38 +08:00
parent 5ad8370b37
commit f2b55b9d2d
3 changed files with 86 additions and 6 deletions
+28 -5
View File
@@ -470,11 +470,34 @@ namespace MobileGL::MG_Remote::Client {
} else {
MG_Pipe::MGPipeClearRespecifiedLevel(record);
}
Int32 status = 0;
const Uint64 seq =
session.EmitAndWait(MGPWireOp::ResourceRespecify, &record, sizeof(record), nullptr,
0, nullptr, 0, &status);
(void)seq;
// P12: THE BUFFER HALF'S ANSWER IS READ BY NOBODY. Both call sites discard this
// record's Bool outright (PipeFill.cpp:1105 and :1142: `MGPipeRouteResourceRespecify(
// desc, nullptr);`), so for a buffer the wait buys a value that is thrown away - pure
// cost, and on the device run this row was the second largest source of round trips in
// the frame. It goes fire-and-forget, with the same provisional-accept shape
// resource_subdata has had since item B.
//
// THE TEXTURE HALF KEEPS ITS ANSWER, and that is deliberate rather than left over:
// there the Bool is the self-heal trigger AND the gate on the acceptance mirror, and
// advancing that mirror on an answer nobody waited for is exactly ID-18 M3
// (TextureEmit.h:885-888: "a refused respecify must leave LastDesc naming the
// descriptor that actually landed, or the next identical call is suppressed against a
// record that was never stored"). A texture respecify still waits until that mirror
// can be advanced on something better than a provisional accept.
const Bool bufferHalf =
static_cast<MG_Pipe::MGPipeResourceTarget>(record.Target) ==
MG_Pipe::MGPipeResourceTarget::Buffer;
Int32 status = Wire::ReplySink::kStatusError;
if (bufferHalf) {
const Uint64 seq = session.EmitAndWaitTails(
MGPWireOp::ResourceRespecify, &record, sizeof(record), nullptr, 0, nullptr, 0,
&status, nullptr, /*wantReply=*/false);
status = seq == Wire::kInvalidSeq ? Wire::ReplySink::kStatusDeclined
: Wire::ReplySink::kStatusOk;
} else {
(void)session.EmitAndWait(MGPWireOp::ResourceRespecify, &record, sizeof(record),
nullptr, 0, nullptr, 0, &status);
}
++g_emitted;
// ERROR IS NOT A DECLINE (M4 / codex 5 / R-5). status is 0 OK / 1 DECLINED / 2 ERROR;
// an escape may not fold 2 into `false`, which is what a bare `return status == 0`
@@ -296,11 +296,18 @@ TEST(RemoteClientControls, LongFenceWaitBudgetStillWakesOnShutdown) {
}
TEST(RemoteClientControls, ResourceRespecifyErrorIsNotADecline) {
// THE TEXTURE HALF, WHICH IS THE HALF THAT STILL READS ITS ANSWER (P12). This case used a
// zeroed descriptor, which is a BUFFER target - and the buffer half is now fire-and-forget
// because both of its call sites discard the Bool (PipeFill.cpp:1105, :1142). A row that reads
// no answer cannot fold ERROR into a decision, so the rule this case exists for moved to the
// half that does read one: the transport fault is caught here, at the next row that waits,
// instead of at a record whose verdict nobody ever looked at.
const auto child = RunInChild([] {
StartControlSession();
ErrorPeer peer;
peer.Install();
MGPResourceDesc desc{};
desc.Target = static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex2D);
(void)MGPipeRouteResourceRespecify(desc, nullptr);
});
ExpectNamedAbort(child, "Fatal{ReplyError, \"resource_respecify\"}");
@@ -1094,4 +1101,54 @@ TEST(RemoteClientControls, AParameterChangeForAConfirmedObjectTakesNoReply) {
});
ExpectChildSuccess(child);
}
// =====================================================================================
// P12: a BUFFER respecify takes no reply; a TEXTURE one still does (deliberately)
// =====================================================================================
//
// The two halves of resource_respecify are not the same row. The buffer half's Bool is discarded
// at BOTH call sites (PipeFill.cpp:1105, :1142), so its wait bought a value that was thrown away -
// fire-and-forget costs nothing and saves a round trip per glBufferData, which is the shape a
// chunk uploader produces thousands of times per frame. The texture half's Bool is load-bearing:
// it triggers the self-heal AND gates the acceptance mirror, and advancing that mirror on a
// provisional accept is ID-18 M3 (TextureEmit.h:885-888). So this case asserts BOTH directions -
// the asymmetry is the design, not an oversight.
//
// THE RED, RUN ONCE: take the bufferHalf branch out of Wire_Escape_ResourceRespecify and exit 155
// fires (the buffer respecify starts waiting again).
TEST(RemoteClientControls, ABufferRespecifyTakesNoReplyWhileATextureOneStillDoes) {
const auto child = RunInChild([] {
::setenv("MOBILEGL_PIPE_STATS", "1", 1);
CapsPeer backend;
StartControlSession(&backend);
MG_Config::Features.PipePush |= kMGPipeSubsystemResources |
kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers;
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
const Uint32 respecifyOp = static_cast<Uint32>(MGPWireOp::ResourceRespecify);
Uint8 seed[64];
std::memset(seed, 0x5a, sizeof(seed));
// ---- the BUFFER half: records out, replies not taken ----
GLuint buffer = 0;
MG_Impl::GLImpl::GenBuffers(1, &buffer);
MG_Impl::GLImpl::BindBuffer(0x8892, buffer);
MG_Impl::GLImpl::BufferData(0x8892, sizeof(seed), seed, 0x88E4);
MGP_FILL(DrawArrays);
const Uint64 recordsBefore = Transport::LinkMetricsRecordsFor(respecifyOp);
const Uint64 waitsBefore = Transport::LinkMetricsReplyWaitsFor(respecifyOp);
MG_Impl::GLImpl::BufferData(0x8892, sizeof(seed) * 2, seed, 0x88E4);
MGP_FILL(DrawArrays);
if (Transport::LinkMetricsRecordsFor(respecifyOp) <= recordsBefore) ::_exit(154);
if (Transport::LinkMetricsReplyWaitsFor(respecifyOp) != waitsBefore) ::_exit(155);
// ---- the TEXTURE half: the deliberate exception ----
Uint8 pixels[8 * 8 * 4];
std::memset(pixels, 0x21, sizeof(pixels));
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(0x0DE1, texture);
MG_Impl::GLImpl::TexImage2D(0x0DE1, 0, 0x8058, 8, 8, 0, 0x1908, 0x1401, pixels);
MGP_FILL(DrawArrays);
if (Transport::LinkMetricsReplyWaitsFor(respecifyOp) == waitsBefore) ::_exit(156);
ClientSessionInstance().Stop();
});
ExpectChildSuccess(child);
}
#endif
@@ -218,7 +218,7 @@ create 被拒 → 闩不置位 → 下一条参数记录照样阻塞取真答案
**下一个 20,633**:`ResourceRespecify` 仍逐条等(在 frame 25 里占 20,633/25,176 = 82%)。它与 params 同形(答案只用于 `NoteRespecified` 与自愈),
"对象已确认"这个前提同样成立——按同一条规则处理,估算落在 **~4.5k waits ≈ 24 s** 量级(create 那 4,536 是确认本身,应当保留)。
### 2.12 `ResourceRespecify` 的两半根本不同:buffer 是白送的,texture 才是那 20,611按"已确认对象不再等"处理这一行时,代码里两半的差别是决定性的:- **buffer 半**:`PipeFill.cpp:1105` 与 `:1142` **两处都把返回值丢掉**(`MGPipeRouteResourceRespecify(desc, nullptr);`), 所以那条等待买到的值当场被扔——改 fire-and-forget 是**纯收益、语义零变化**。- **texture 半**:返回值既是自愈触发器,又是**验收镜像 `LastDesc` 的闸**, `TextureEmit.h:885-888` 明写不许在未确认的答案上前进(ID-18 M3:否则下一次相同调用会被去重掉,而服务端从没存过)。于是本轮只落 buffer 半(提交见下),并用它把两半的占比量了出来——**同一帧的 `by_op`**:| | waits | `by_op` ||---|---|---|| 只做了 params(frame 25) | 25,176 | `2=4536, `**`3=20633`**`, 5=6, 9=1` || 再放开 buffer 半(frame 10) | **25,154** | `2=4536, `**`3=20611`**`, 5=6, 9=1` |**buffer 半只值 22 次等待(0.1%)——20,633 里几乎全是 texture respecify**,也就是带 ID-18 M3 约束的那一半。墙钟 134.4 s → 130.9 s(同一帧、同样 101,421,396 B staged)。**texture 半的正解(下一步,尚未实现)**:让镜像**只在真答案上前进**——fire-and-forget 之后 `NoteRespecified` 收 `accepted && !provisional`,于是最坏情况退化成"多补发一条相同 respecify"(安全方向),而绝不会变成"该重发的被去重掉"。这需要在 MG_Pipe 的回复信箱上把"临时接受"和"真接受"分开(信箱本来就有"最后一次调用"的纪律,`MGPipeTakeReplyBool` 就是这么读的),改动落在 route/mailbox + `EmitResourceRespecify` 一处判断。按同一模型估算:**25,154 → ~4.5k waits ≈ 24 s 量级**(create 那 4,536 是确认本身)。
## 3. 剩余(下一轮的直接入口)
1. **把记录数压下来(新的头号项)**。§2.5 的真机数字说:那一帧剩下的 43,232 次等待全在「必须等」的三条 row 上,