mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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:
@@ -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 上,
|
||||
|
||||
Reference in New Issue
Block a user