mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Perf] (MG_Pipe, MG_Remote/Client): on the wire arm a texture upload stops owning a reply - the wait moves from every record to the SEG_STAGE window
- MGPipeSubDataWantsItsReply is the ONE place that decides whether a resource_subdata record waits
for its answer (WireTables.cpp:370 and PipeRoute.h:328 both read it, and the comment beside it
says that a second spelling is how the client comes to wait for an answer the emitter told the
server not to bother with). The buffer half has answered "no" since P5e; the texture half now
does too, ON THE WIRE ARM ONLY.
- THE ANSWER IS DERIVABLE THERE, which is the argument - not that nobody reads it:
* The emission is already gated on the answer's content. A texture record is produced only where
FamilyIsLive(kMGPipeSubsystemTextureResources, ...) holds, and under split that gate IS
CapsMirrorInstance().ServerConsumes(bit 10) (PipeFill.cpp:1719-1740) - the same published fact
the applier's own acceptance belt reads (PipeApply.cpp:1408-1419, NoP4aConsumer). So the one
refusal this acceptance exists to carry, "no consumer" (ID-39 / D-D5 / ID-18 M3, which is why
the texture half ever asked), cannot be the answer to a record this client emitted.
* What remains is a bug class, and it is loud already: a record that names no texture, declares
no texels or carries no bytes is an MGP_TRIP_WIRE_REPORT (PipeApply.cpp:1201-1218, :2245-2252),
and a handle the applier does not hold is counted and named (ResolveResourceIn, :721-728) with
the client's own tracker preventing it from arising (PipeFill.cpp:1045-1051).
* Every answer the client can still get for free, it still gets: the emitter reads this route's
Bool exactly as before, so a record the CLIENT cancelled (the device-lost latch, an exhausted
stage) still answers "not accepted" and the level stays dirty and is retried - D-D5's safe
direction, unchanged.
- THE WAIT MOVES TO THE STAGE. A run that does not fit SEG_STAGE reclaims retired bytes and parks
on the stage doorbell (PipeWireCodec.cpp StageAllocate / ReclaimStagedBytes) - the one wait a
run-ahead client owes, and it is proportional to the WINDOW rather than to the call count. On
Minecraft 26.2's atlas frame that is the handoff's 55,428 reply waits at ~4.4 ms each: 96 MiB of
uploads through a 32 MiB window.
- THE GATE IS THE NUMBER ITSELF. LinkMetricsReplyWaits is added so a case can read back the same
`wait_replies` the per-frame P65LinkMetrics line prints - LinkMetrics.cpp:76-80 counts exactly
the records that own a reply slot. TextureUploadsStopOwningAReplyOnTheWireArm asserts that a pure
glTexSubImage2D crosses the wire and moves it by ZERO, while a resource_create row - kWaitReply,
and it always was - still moves it, so the zero cannot be "nothing was counted". Red-once run:
with the arm test removed the child exits 142.
- Monolith and every non-wire arm are untouched, and that is pinned twice: the existing
RemoteRunAhead.OnlyTheTextureHalfOfResourceSubDataWantsItsReply still reads true with no arm
installed, and the new test sits inside MOBILEGL_BUILD_DISAGGREGATED with the original expression
as its fall-through, so a pull build preprocesses to the line it had (G1).
- Verification debt, stated rather than implied: the host gate is the COUNT, not a wall clock.
In-process control sessions run lockstep (the caps peer publishes no kCapRunAheadApply), so the
verb barrier still waits there; the end-to-end wall-clock win is the device measurement the
handoff describes and it has not been taken yet.
This commit is contained in:
@@ -298,14 +298,56 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// THE BUFFER HALF DOES NOT: its Bool is discarded at the only call site there is
|
// THE BUFFER HALF DOES NOT: its Bool is discarded at the only call site there is
|
||||||
// (MGPipeEmitResourceSubData), and because the row carries kReplySlot the client used to
|
// (MGPipeEmitResourceSubData), and because the row carries kReplySlot the client used to
|
||||||
// wait for it anyway - one full round trip per 64 KB persistent-map block, whose answer
|
// wait for it anyway - one full round trip per 64 KB persistent-map block, whose answer
|
||||||
// nobody looked at. THE TEXTURE HALF DOES: DrainTextureSubData clears the level's dirty
|
// nobody looked at.
|
||||||
// flag on an ACCEPTED reply, so its answer is load-bearing (D-D5; making that half
|
//
|
||||||
// fire-and-forget is a trailing item, not this package's).
|
// THE TEXTURE HALF DOES NOT EITHER, ON THE WIRE ARM (P12, the handoff's item B), and the
|
||||||
|
// argument is that its answer is DERIVABLE there - not that anybody stopped reading it.
|
||||||
|
// DrainTextureSubData still clears the level's dirty flag on an accepted answer (D-D5), and
|
||||||
|
// it still gets one; what it does not get any more is a ROUND TRIP for it:
|
||||||
|
//
|
||||||
|
// * THE EMISSION IS ALREADY GATED ON THE ANSWER'S CONTENT. A texture record is produced
|
||||||
|
// only where FamilyIsLive(kMGPipeSubsystemTextureResources, ...) holds, and under split
|
||||||
|
// that gate IS CapsMirrorInstance().ServerConsumes(bit 10) (PipeFill.cpp:1719-1740) - the
|
||||||
|
// same published fact the applier's own acceptance belt reads (PipeApply.cpp:1408-1419,
|
||||||
|
// NoP4aConsumer). So the one server-side refusal this acceptance exists to carry - "no
|
||||||
|
// consumer", the ID-39 / D-D5 / ID-18 M3 case that is WHY the texture half ever asked -
|
||||||
|
// cannot be the answer to a record this client emitted.
|
||||||
|
// * WHAT REMAINS IS A BUG CLASS, AND IT IS LOUD ALREADY. A record whose resource target
|
||||||
|
// names no texture, or that declares no texels or no bytes, is an MGP_TRIP_WIRE_REPORT on
|
||||||
|
// the server (PipeApply.cpp:1201-1218, :2245-2252); a record for a handle the applier
|
||||||
|
// does not hold is counted and named (ResolveResourceIn, :721-728), and the client's own
|
||||||
|
// tracker prevents it by creating before it ever sends sub-data (PipeFill.cpp:1045-1051).
|
||||||
|
// * AND EVERY ANSWER THE CLIENT CAN STILL GET FOR FREE, IT STILL GETS: the emitter reads
|
||||||
|
// this route's Bool exactly as before, so a record the CLIENT cancelled (the device-lost
|
||||||
|
// latch, an exhausted stage) still answers "not accepted" and the level stays dirty and
|
||||||
|
// is retried - D-D5's safe direction, unchanged.
|
||||||
|
//
|
||||||
|
// THE WAIT DOES NOT DISAPPEAR, IT MOVES TO THE STAGE. A record whose run does not fit
|
||||||
|
// SEG_STAGE reclaims retired bytes and parks on the stage doorbell (PipeWireCodec.cpp
|
||||||
|
// StageAllocate / ReclaimStagedBytes) - the one wait a run-ahead client owes, and the one
|
||||||
|
// that is proportional to the WINDOW rather than to the call count. That is the whole of item
|
||||||
|
// B: on Minecraft 26.2's atlas frame, 55,428 reply waits at ~4.4 ms each (96 MiB of uploads
|
||||||
|
// through a 32 MiB window) become the handful of stage retirements those same 96 MiB need.
|
||||||
|
// (Measured on the host by RemoteClientControls.TextureUploadsStopOwningAReplyOnTheWireArm,
|
||||||
|
// which counts the reply slots the client opens for a batch of uploads: N -> 0.)
|
||||||
|
//
|
||||||
|
// MONOLITH IS UNTOUCHED, and the arm test is what says so: there the applier's answer is
|
||||||
|
// returned by Mono_ResourceSubData on the calling thread and costs nothing, so there is
|
||||||
|
// nothing to stop waiting for and the predicate keeps its old answer.
|
||||||
//
|
//
|
||||||
// The test is MGPSubData::Target == kMGPipeResourceTargetBuffer, whole field, which is the
|
// The test is MGPSubData::Target == kMGPipeResourceTargetBuffer, whole field, which is the
|
||||||
// invariant MGPipeTypes.h asserts beside the packer and which the applier's own
|
// invariant MGPipeTypes.h asserts beside the packer and which the applier's own
|
||||||
// SubDataNamesABuffer already reads.
|
// SubDataNamesABuffer already reads.
|
||||||
inline Bool MGPipeSubDataWantsItsReply(const MGPSubData& record) {
|
inline Bool MGPipeSubDataWantsItsReply(const MGPSubData& record) {
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
// THE WIRE ARM ONLY. Everything above is an argument about a client talking to a server
|
||||||
|
// over SEG_CMD/SEG_STAGE; a monolith run inside a split build (every unit and
|
||||||
|
// integration-gpu lane is one) keeps the answer it always gave.
|
||||||
|
if (MGPipeInstalledArm() == MGPipeRouteArm::kClientWire) return false;
|
||||||
|
#endif
|
||||||
|
// THE PULL BUILD PREPROCESSES TO EXACTLY THIS LINE AND NOTHING ELSE, which is what G1
|
||||||
|
// compares: the arm test above sits inside the build switch, so the pull arm emits the
|
||||||
|
// expression this function has always had.
|
||||||
return record.Target != kMGPipeResourceTargetBuffer;
|
return record.Target != kMGPipeResourceTargetBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ void LinkMetricsReplyApplied(std::uint64_t startedNs) {
|
|||||||
++w->samples; w->replyNs += ns; ++w->histogram[bucket];
|
++w->samples; w->replyNs += ns; ++w->histogram[bucket];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::uint64_t LinkMetricsReplyWaits() { return metrics.total.waitReplies; }
|
||||||
void LinkMetricsStageBytes(std::uint64_t bytes) {
|
void LinkMetricsStageBytes(std::uint64_t bytes) {
|
||||||
if (!metrics.active) return;
|
if (!metrics.active) return;
|
||||||
metrics.current.stageBytes += bytes; metrics.total.stageBytes += bytes;
|
metrics.current.stageBytes += bytes; metrics.total.stageBytes += bytes;
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ namespace MobileGL::MG_Remote::Transport {
|
|||||||
std::uint64_t LinkMetricsBeginReply(bool wantsReply);
|
std::uint64_t LinkMetricsBeginReply(bool wantsReply);
|
||||||
void LinkMetricsReplyApplied(std::uint64_t startedNs);
|
void LinkMetricsReplyApplied(std::uint64_t startedNs);
|
||||||
void LinkMetricsStageBytes(std::uint64_t bytes);
|
void LinkMetricsStageBytes(std::uint64_t bytes);
|
||||||
|
|
||||||
|
// THE SAME NUMBER THE PER-FRAME LINE PRINTS AS `wait_replies` (P65LinkMetrics), read back
|
||||||
|
// in-process. P12 item B needs it as a GATE rather than as a log line: "the texture half no
|
||||||
|
// longer buys a round trip per record" is otherwise only observable by timing, and a timing
|
||||||
|
// is a measurement, not a gate. It reads 0 while MOBILEGL_PIPE_STATS is unset - which is
|
||||||
|
// exactly why a case that asserts 0 for one row must ALSO assert that a reply-owning row
|
||||||
|
// still counts, or it would pass on a session where nothing was counted at all.
|
||||||
|
std::uint64_t LinkMetricsReplyWaits();
|
||||||
void LinkMetricsPresent();
|
void LinkMetricsPresent();
|
||||||
void LinkMetricsServerPresent(std::uint64_t serial);
|
void LinkMetricsServerPresent(std::uint64_t serial);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -862,4 +862,52 @@ TEST(RemoteClientControls, DeviceLostRespecifyDoesNotDieOfTheUncarriedBytesCheck
|
|||||||
ExpectChildSuccess(child);
|
ExpectChildSuccess(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =====================================================================================
|
||||||
|
// P12 item B: the texture half stops owning a reply on the wire arm
|
||||||
|
// =====================================================================================
|
||||||
|
//
|
||||||
|
// THE NUMBER IS THE GATE. `wait_replies` is what the handoff measured 55,428 of in one
|
||||||
|
// Minecraft frame; LinkMetrics.cpp:76-80 counts exactly the records that OWN a reply slot, so
|
||||||
|
// it is the same quantity the per-frame P65LinkMetrics line prints. The case drives it over a
|
||||||
|
// live split session: a pure level upload must cross the wire and open NO reply wait, while a
|
||||||
|
// create row - which is kWaitReply and always was - still moves the number.
|
||||||
|
//
|
||||||
|
// THE SECOND HALF IS THE NEGATIVE CONTROL FOR THE FIRST. Without it, a session that counted
|
||||||
|
// nothing at all (metrics off, no records) would pass the half that exists to fail.
|
||||||
|
//
|
||||||
|
// THE RED, RUN ONCE: take the `kClientWire` arm out of MGPipeSubDataWantsItsReply (PipeRoute.h)
|
||||||
|
// and the upload delta becomes 1 - the child exits 142.
|
||||||
|
TEST(RemoteClientControls, TextureUploadsStopOwningAReplyOnTheWireArm) {
|
||||||
|
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>();
|
||||||
|
Uint8 pixels[4 * 4 * 4];
|
||||||
|
std::memset(pixels, 0x21, sizeof(pixels));
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(0x0DE1, texture);
|
||||||
|
// THE CONTROL ROW. resource_create is kWaitReply (PipeCalls.def), so it owns a reply
|
||||||
|
// slot and this counter MUST move on it.
|
||||||
|
const auto beforeCreate = Transport::LinkMetricsReplyWaits();
|
||||||
|
MG_Impl::GLImpl::TexImage2D(0x0DE1, 0, 0x8058, 4, 4, 0, 0x1908, 0x1401, pixels);
|
||||||
|
MGP_FILL(DrawArrays);
|
||||||
|
if (Transport::LinkMetricsReplyWaits() <= beforeCreate) ::_exit(140);
|
||||||
|
// THE ROW UNDER TEST. A second write into the same level is a pure resource_subdata: the
|
||||||
|
// resource is created, so no create crosses, and the level is dirty again - the only
|
||||||
|
// record it can produce is the one item B is about.
|
||||||
|
const auto beforeRecords = ClientWireRecordsEmitted();
|
||||||
|
const auto beforeWaits = Transport::LinkMetricsReplyWaits();
|
||||||
|
MG_Impl::GLImpl::TexSubImage2D(0x0DE1, 0, 0, 0, 2, 2, 0x1908, 0x1401, pixels);
|
||||||
|
MGP_FILL(DrawArrays);
|
||||||
|
if (ClientWireRecordsEmitted() <= beforeRecords) ::_exit(141);
|
||||||
|
if (Transport::LinkMetricsReplyWaits() != beforeWaits) ::_exit(142);
|
||||||
|
ClientSessionInstance().Stop();
|
||||||
|
});
|
||||||
|
ExpectChildSuccess(child);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include <MG_Remote/CapsCodec.h>
|
#include <MG_Remote/CapsCodec.h>
|
||||||
#include <MG_Remote/Client/CapsMirror.h>
|
#include <MG_Remote/Client/CapsMirror.h>
|
||||||
#include <MG_Remote/Transport/ReplySlot.h>
|
#include <MG_Remote/Transport/ReplySlot.h>
|
||||||
|
#include <MG_Remote/Transport/LinkMetrics.h>
|
||||||
#include <MG_Pipe/PipeRoute.h>
|
#include <MG_Pipe/PipeRoute.h>
|
||||||
#include <MG_Remote/Client/EmitTables.h>
|
#include <MG_Remote/Client/EmitTables.h>
|
||||||
#include <MG_Remote/Client/WireTables.h>
|
#include <MG_Remote/Client/WireTables.h>
|
||||||
|
|||||||
Reference in New Issue
Block a user