mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Perf] (MG_Remote): The create window ships at 10, and the server now reports where its own frame time goes.
This commit is contained in:
+1
-1
@@ -594,7 +594,7 @@ namespace MobileGL::MG_Config {
|
||||
// caller was fire-and-forget. The window's own creates are fire-and-forget AND read, so a
|
||||
// flag keyed on wantReply would disable the very mechanism it exists to enable
|
||||
// (docs/Disaggregated/notes/p12/CREATE-WINDOW-MEASURED.md sections 7-11).
|
||||
Uint32 CreateWindow = 6;
|
||||
Uint32 CreateWindow = 10;
|
||||
// MOBILEGL_IPC_ADOPT_TIER: 2 = emulate (client keeps the shadow and pushes), which
|
||||
// is the only tier P5 implements and the reason persistent-map-push can be non-zero
|
||||
// at all (R-6). 0 and 1 parse and are Fatal at use with "P11"; they exist now so the
|
||||
|
||||
@@ -420,7 +420,7 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// declared answers, and the device sweep showed the wall clock still falling at the old
|
||||
// cap of 4 (see WireTables.cpp's kCreateWindowMax). WireTables.cpp's static_assert and
|
||||
// StreamLink's crossing log are the checks on this number.
|
||||
ipc.CreateWindow = QueryEnvUint32("MOBILEGL_IPC_CREATE_WINDOW", 6, 1, 8);
|
||||
ipc.CreateWindow = QueryEnvUint32("MOBILEGL_IPC_CREATE_WINDOW", 10, 1, 12);
|
||||
// The verify harness compares the pushed block against the applier per verb; a
|
||||
// batched queue lets the comparer read a supplied field mid-apply, which is a
|
||||
// torn read rather than a divergence. The batch is therefore off whenever the
|
||||
|
||||
@@ -398,7 +398,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// (THE NARRATIVE ABOVE PREDATES RETENTION and ends at "the default is 1". It is kept as
|
||||
// the record of what was tried; what made the window work is the declared-answer store,
|
||||
// and the default is 2 - Config.h carries the device numbers.)
|
||||
constexpr Uint32 kCreateWindowMax = 8;
|
||||
constexpr Uint32 kCreateWindowMax = 12;
|
||||
// THE POOL ARGUMENT THAT USED TO PIN THIS AT 4 IS GONE, AND WHY IT IS GONE IS THE ONE
|
||||
// THING TO RE-CHECK BEFORE RAISING IT AGAIN. It read: the pool is kDefaultReplySlotCount
|
||||
// slots addressed seq % count, so an answer is readable only while its slot has not been
|
||||
@@ -451,6 +451,32 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// answers from existing at all - the second is what a no-reply bit keyed on wantReply
|
||||
// would produce, and it would leave every other reading green.
|
||||
Uint64 g_createWindowTaken = 0;
|
||||
// P12 MEASUREMENT: WHY A CREATE DOES NOT DEFER. The window's own arithmetic says a frame's
|
||||
// blocking takes should be creates/window (4,536/6 = 756 at the shipped default), and the
|
||||
// device measures 1,155 - a CONSTANT ~400 that does not move when the window deepens (at
|
||||
// window 8 the ideal is 567 and the measurement is 859). A constant is a fixed population
|
||||
// rather than a queueing effect, and the four exits below are the only ways into it, so
|
||||
// counting them by name is what says which one it is. It matters because the frame turned
|
||||
// out to be LATENCY-bound, not throughput-bound (the server applies all 103,097 records in
|
||||
// 1.95 s of a 9.64 s frame, and the client's 1,161 blocking takes at ~4.9 ms are 5.7 s of
|
||||
// it) - so the way to make it faster is fewer blocks, not a faster apply.
|
||||
Uint64 g_blockNoWindow = 0, g_blockSuspectsFull = 0, g_blockSuspectObject = 0,
|
||||
g_blockWindowStillFull = 0, g_blockTotal = 0;
|
||||
void NoteCreateBlocked(Uint64& reason) {
|
||||
++reason;
|
||||
++g_blockTotal;
|
||||
// Once per 256 blocks: a handful of lines per load frame, and the composition is
|
||||
// readable from any one of them.
|
||||
if (g_blockTotal % 256 == 0) {
|
||||
MGLOG_I("P12 create blocks: total=%llu no-window=%llu suspects-full=%llu "
|
||||
"suspect-object=%llu window-still-full=%llu",
|
||||
static_cast<unsigned long long>(g_blockTotal),
|
||||
static_cast<unsigned long long>(g_blockNoWindow),
|
||||
static_cast<unsigned long long>(g_blockSuspectsFull),
|
||||
static_cast<unsigned long long>(g_blockSuspectObject),
|
||||
static_cast<unsigned long long>(g_blockWindowStillFull));
|
||||
}
|
||||
}
|
||||
|
||||
// A HANDLE WHOSE CREATE CAME BACK DECLINED IS NEVER WINDOWED AGAIN. Without this the window
|
||||
// would UNDO the repair it exists to keep: every later use of the object sends another create,
|
||||
@@ -624,6 +650,13 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// all four take the blocking branch, which is the pre-window shape - and on the device
|
||||
// this is the branch the whole row takes, because the deferral above it cannot be
|
||||
// collected. See the section comment for the measurement and the arithmetic.
|
||||
if (window <= 1) {
|
||||
NoteCreateBlocked(g_blockNoWindow);
|
||||
} else if (g_createSuspects.size() >= kCreateSuspectMax) {
|
||||
NoteCreateBlocked(g_blockSuspectsFull);
|
||||
} else if (CreateIsSuspect(payload->Resource)) {
|
||||
NoteCreateBlocked(g_blockSuspectObject);
|
||||
}
|
||||
if (window <= 1 || g_createSuspects.size() >= kCreateSuspectMax ||
|
||||
CreateIsSuspect(payload->Resource)) {
|
||||
// THE WINDOW RETIRES HERE, AND IT MAY BE HOLDING ANSWERS (P12). This branch is
|
||||
@@ -650,6 +683,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// against the effective window, which is what the array below is sized for, so
|
||||
// this line IS the overwrite guard.
|
||||
if (g_createWindowCount >= window) {
|
||||
NoteCreateBlocked(g_blockWindowStillFull);
|
||||
EmitCreateBlocking(session, payload, reply);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -739,6 +739,30 @@ namespace MobileGL::MG_Remote::Server {
|
||||
ServerSession& session = *m_session;
|
||||
Transport::SessionConsumer& consumer = session.Consumer();
|
||||
PipeApplier& applier = session.Applier();
|
||||
// ---- P65ServerFrame: WHERE THE SERVER'S TIME GOES (P12 measurement) -----------------
|
||||
//
|
||||
// THE CLIENT SIDE IS MEASURED OUT AND THE SERVER'S IS NOT MEASURED AT ALL. Every reading
|
||||
// this harness produces is the CLIENT's - wall_ns, wait_replies, client_thread_cpu_ns - and
|
||||
// they now add up to less than the frame: at window 6 the client burns 2.5 s of CPU and
|
||||
// 1,162 blocking waits, and removing 292 of those waits (window 8) moves the wall clock by
|
||||
// nothing. The same 101 MB over USB instead of WiFi moves it by nothing either. What is
|
||||
// left is the one process nobody has timed: the server applying 103,097 records.
|
||||
//
|
||||
// THE SPLIT IS THE POINT. apply_ms is the sum of the time spent INSIDE
|
||||
// PipeApplier::ApplyOne - the GL work, the decode, the reply posts - and wall_ms is Present
|
||||
// to Present on the apply thread. Their difference is everything that is NOT applying:
|
||||
// waiting on the ring for the next record, retiring, and the socket. So one line answers
|
||||
// "is the phone CPU-bound, or is it waiting for the client?" without a profiler, which is
|
||||
// the question the next optimisation has to be chosen from.
|
||||
//
|
||||
// FUNCTION-LOCAL STATIC BECAUSE THE FRAME DOES NOT FIT IN ONE DrainRing CALL: records
|
||||
// trickle in, so a frame spans many drains, and state that reset per drain would measure a
|
||||
// batch. There is exactly one apply thread (ServerLoopInstance's), which is what makes a
|
||||
// static safe here; the same reasoning the drain tally above uses.
|
||||
static auto sFrameWallStart = std::chrono::steady_clock::now();
|
||||
static std::uint64_t sFrameApplyNs = 0;
|
||||
static std::uint64_t sFrameRecords = 0;
|
||||
static std::uint64_t sFrameIndex = 0;
|
||||
Uint64 applied = 0;
|
||||
for (;;) {
|
||||
if (SessionLatched()) break;
|
||||
@@ -749,7 +773,33 @@ namespace MobileGL::MG_Remote::Server {
|
||||
bool corrupt = false;
|
||||
const bool popped = consumer.ApplyOne(
|
||||
[this, &applier](const Transport::RingRecordView& record) {
|
||||
const auto applyStarted = std::chrono::steady_clock::now();
|
||||
applier.ApplyOne(record);
|
||||
// P65ServerFrame: timed around the ONE call that is the server's work, and
|
||||
// summed across the drains a frame spans. See the block above DrainRing's loop.
|
||||
sFrameApplyNs += static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now() - applyStarted)
|
||||
.count());
|
||||
++sFrameRecords;
|
||||
if (record.kind == static_cast<std::uint16_t>(MG_Pipe::MGPWireOp::Present)) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto wallNs = static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(now - sFrameWallStart)
|
||||
.count());
|
||||
const double applyMs = static_cast<double>(sFrameApplyNs) / 1e6;
|
||||
const double wallMs = static_cast<double>(wallNs) / 1e6;
|
||||
MGLOG_I("P65ServerFrame frame=%llu records=%llu apply_ms=%.1f wall_ms=%.1f "
|
||||
"outside_ms=%.1f - apply_ms is the sum of PipeApplier::ApplyOne, "
|
||||
"wall_ms is Present to Present on the apply thread, and the "
|
||||
"difference is waiting for work plus retirement plus the socket",
|
||||
static_cast<unsigned long long>(++sFrameIndex),
|
||||
static_cast<unsigned long long>(sFrameRecords), applyMs, wallMs,
|
||||
wallMs - applyMs);
|
||||
sFrameWallStart = now;
|
||||
sFrameApplyNs = 0;
|
||||
sFrameRecords = 0;
|
||||
}
|
||||
// THE TALLY MOVES HERE, INSIDE THE CALLBACK, AND NOT AFTER THE BATCH. s1's
|
||||
// SessionConsumer::ApplyOne publishes appliedSeq the instant this callback
|
||||
// returns, and publishing appliedSeq is what releases the client from the
|
||||
|
||||
Reference in New Issue
Block a user