[Feat] (MG_Remote, P11 B1): the reserved apply core never takes a lone prime core - several prime cores give the lowest, one gives a big core beside it or none, and the log names which - and the supervisor announces the core it reserves on stdout at start-up, the one source the broker relays

This commit is contained in:
2026-09-29 15:00:30 -04:00
parent 5a97eb8d1b
commit 9af4c8aefd
4 changed files with 127 additions and 32 deletions
+82 -23
View File
@@ -23,16 +23,16 @@
// in-process (No) every big core, the configured spin - unchanged
// forked (Fork) every big core, the configured spin - unchanged
// dialled in (Connect), stream every big core, the configured spin - unchanged
// dialled in, shared segments ONE prime core - the LOWEST-numbered big core, and only when the
// big set is a strict subset of at least two - and
// kConnectedPeerSpinUs unless MOBILEGL_IPC_SPIN_US was set
// dialled in, shared segments ONE core, ReservedApplyCore's: the lowest of several prime cores,
// a big core beside a lone prime core, or none - never the only
// prime core - and kConnectedPeerSpinUs unless MOBILEGL_IPC_SPIN_US
//
// The lowest, not the highest, by measurement (CONTRACT-P11 B1): the scheduler puts a foreground
// app's hot GL thread on the HIGHEST prime core (cpu7 in 43 of 44 samples); pinning the apply
// thread there shared that core with it (rd12 6.5 fps), pinning it to the lowest gave 120. A client
// launched through ExternalClientHelper is also kept off the reserved core (the broker hands the
// helper this same core as its avoid mask), so a client the scheduler would otherwise place on it -
// an adb-shell one - is not starved either.
// The lowest prime core, not the highest, by measurement (CONTRACT-P11 B1): the scheduler puts a
// foreground app's hot GL thread on the HIGHEST prime core (cpu7 in 43 of 44 samples); pinning the
// apply thread there shared that core with it (rd12 6.5 fps), pinning it to the lowest gave 120. A
// client launched through ExternalClientHelper is also kept off the reserved core (the supervisor
// announces it, the broker hands it to the helper), so a client the scheduler would otherwise place
// on it - an adb-shell one - is not starved either.
//
// An explicit mask or `off` wins for every peer, exactly as before: the operator's word is the
// policy. A pure function, so the unit suite can hold every row without a thread or a cpufreq tree.
@@ -82,23 +82,70 @@ namespace MobileGL::MG_Remote::Server {
return count;
}
// The core `auto` reserves for a dialled-in shared-segment peer's apply thread: the lowest big
// core when the big set is a strict subset of the online cpus with at least two members, else 0
// (no prime pair to split). ExternalClientBroker.java mirrors this rule for the helper's avoid mask.
inline Uint64 ReservedApplyCore(Uint64 bigCoreMask, Uint64 onlineMask) {
const Bool asymmetric = bigCoreMask != 0 && (bigCoreMask & onlineMask) != onlineMask;
if (!asymmetric || PopCount64(bigCoreMask) < 2) return 0;
return bigCoreMask & (~bigCoreMask + 1); // the lowest set bit
inline Uint64 LowestBit64(Uint64 mask) { return mask & (~mask + 1); }
// Why ReservedApplyCore answered what it did - the log line names it.
enum class ReservedCoreReason : Uint8 {
LowestOfSeveralPrimeCores = 0, // two or more cores at the peak clock: the lowest of them
BigCoreBesideTheOnlyPrime = 1, // one core at the peak clock: the lowest OTHER big core
OnlyPrimeCoreLeftAlone = 2, // one core at the peak clock and no other big core: none
NoAsymmetry = 3, // every cpu is big, or the topology was unreadable: none
};
inline const char* ReservedCoreReasonText(ReservedCoreReason reason) {
switch (reason) {
case ReservedCoreReason::LowestOfSeveralPrimeCores:
return "auto: the lowest of the prime cores, the rest left to the dialled-in shared-segment client";
case ReservedCoreReason::BigCoreBesideTheOnlyPrime:
return "auto: a big core beside the only prime core, which is left to the dialled-in shared-segment client";
case ReservedCoreReason::OnlyPrimeCoreLeftAlone:
return "auto: unpinned - the only prime core is left to the dialled-in shared-segment client and there "
"is no other big core";
case ReservedCoreReason::NoAsymmetry:
return "auto: unpinned - no asymmetric big/prime topology to split";
}
return "";
}
// The core `auto` reserves for a dialled-in shared-segment peer's apply thread, which spins there.
// `bigCoreMask` = cpus within 15% of the peak clock, `primeMask` = cpus AT the peak clock (both
// DetectCoreTopology's), `onlineMask` = one bit per cpu counted. The rule never hands the spinning
// apply thread the ONLY prime core - a foreground client's GL thread wants it:
// several prime cores -> the lowest prime core (the client takes a higher one)
// one prime core, other big cores -> the lowest of those other big cores
// one prime core and nothing else big -> none (unpinned)
// no asymmetry (every cpu big, or 0) -> none (unpinned)
// The supervisor logs the answer at start-up and the broker hands it to the helper (the core the
// client is kept off), so this function is the one source of that core.
inline Uint64 ReservedApplyCore(Uint64 bigCoreMask, Uint64 primeMask, Uint64 onlineMask,
ReservedCoreReason* outReason = nullptr) {
ReservedCoreReason reason = ReservedCoreReason::NoAsymmetry;
Uint64 core = 0;
primeMask &= bigCoreMask;
const Bool asymmetric = bigCoreMask != 0 && (bigCoreMask & onlineMask) != onlineMask && primeMask != 0;
if (asymmetric) {
if (PopCount64(primeMask) >= 2) {
core = LowestBit64(primeMask);
reason = ReservedCoreReason::LowestOfSeveralPrimeCores;
} else if ((bigCoreMask & ~primeMask) != 0) {
core = LowestBit64(bigCoreMask & ~primeMask);
reason = ReservedCoreReason::BigCoreBesideTheOnlyPrime;
} else {
reason = ReservedCoreReason::OnlyPrimeCoreLeftAlone;
}
}
if (outReason != nullptr) *outReason = reason;
return core;
}
// `affinity` is MOBILEGL_IPC_SERVER_AFFINITY's raw text (null or empty = `auto`); `spinExplicit`
// says MOBILEGL_IPC_SPIN_US was set in this process's environment and `configuredSpinUs` is its
// value (ConfigLoader's, 50 when unset); `sharedSegments` is the session's data plane (false =
// stream); `bigCoreMask` is DetectBigCoreMask()'s answer (0 when the topology is unreadable) and
// `onlineMask` one bit per cpu the process could see.
// stream); `bigCoreMask` / `primeMask` are DetectCoreTopology()'s answer (0 when the topology is
// unreadable) and `onlineMask` one bit per cpu the process could see.
inline ApplyThreadPolicy SelectApplyThreadPolicy(const char* affinity, Bool spinExplicit, Uint32 configuredSpinUs,
ApplyPeer peer, Bool sharedSegments, Uint64 bigCoreMask,
Uint64 onlineMask) {
Uint64 primeMask, Uint64 onlineMask) {
ApplyThreadPolicy policy;
policy.spinUs = configuredSpinUs;
const Bool automatic = affinity == nullptr || affinity[0] == '\0' || std::strcmp(affinity, "auto") == 0;
@@ -131,11 +178,23 @@ namespace MobileGL::MG_Remote::Server {
return policy;
}
policy.spinUs = spinExplicit ? configuredSpinUs : kConnectedPeerSpinUs;
policy.requestedMask = ReservedApplyCore(bigCoreMask, onlineMask);
policy.rule = policy.requestedMask != 0
? "auto: the lowest prime core, the rest left to the dialled-in shared-segment client"
: "auto: no affinity for a dialled-in shared-segment client (no prime pair to split)";
ReservedCoreReason reason = ReservedCoreReason::NoAsymmetry;
policy.requestedMask = ReservedApplyCore(bigCoreMask, primeMask, onlineMask, &reason);
policy.rule = ReservedCoreReasonText(reason);
return policy;
}
// What the supervisor announces at start-up and the broker relays to the helper: the core this
// process's configuration reserves for a dialled-in shared-segment peer's apply thread. An operator's
// affinity (a mask or `off`) reserves nothing - the client is then not kept off any core.
inline Uint64 ReservedApplyCoreForConfig(const char* affinity, Uint64 bigCoreMask, Uint64 primeMask,
Uint64 onlineMask, ReservedCoreReason* outReason = nullptr) {
const Bool automatic = affinity == nullptr || affinity[0] == '\0' || std::strcmp(affinity, "auto") == 0;
if (!automatic) {
if (outReason != nullptr) *outReason = ReservedCoreReason::NoAsymmetry;
return 0;
}
return ReservedApplyCore(bigCoreMask, primeMask, onlineMask, outReason);
}
} // namespace MobileGL::MG_Remote::Server
+26 -9
View File
@@ -66,25 +66,27 @@ namespace MobileGL::MG_Remote::Server {
// the topology cannot be read - Windows, macOS, a container that hides the cpufreq
// tree, or a partially readable one - because with no asymmetry information the honest
// answer is "do not pin", not "pin to a guess".
Uint64 DetectBigCoreMask() {
// P11 B1: and the PRIME set beside it - the cpus AT the peak ceiling - which the dialled-in
// shared-segment rule needs so it never hands the spinning apply thread the only prime core.
void DetectCoreTopology(Uint64* outBig, Uint64* outPrime) {
*outBig = *outPrime = 0;
const Uint cpuCount = std::min(64u, std::max(1u, std::thread::hardware_concurrency()));
Uint64 frequencies[64] = {};
for (Uint cpu = 0; cpu < cpuCount; ++cpu) {
frequencies[cpu] = ReadCpuMaxFrequencyKHz(cpu);
if (frequencies[cpu] == 0) return 0;
if (frequencies[cpu] == 0) return;
}
Uint64 peak = 0;
for (Uint cpu = 0; cpu < cpuCount; ++cpu) peak = std::max(peak, frequencies[cpu]);
if (peak == 0) return 0;
if (peak == 0) return;
const Uint64 threshold = peak * kBigCoreFrequencyPercent / 100;
Uint64 mask = 0;
for (Uint cpu = 0; cpu < cpuCount; ++cpu) {
if (frequencies[cpu] >= threshold) mask |= (1ull << cpu);
if (frequencies[cpu] >= threshold) *outBig |= (1ull << cpu);
if (frequencies[cpu] == peak) *outPrime |= (1ull << cpu);
}
return mask;
}
// One bit per cpu the process can count, the same count DetectBigCoreMask walks - what
// One bit per cpu the process can count, the same count DetectCoreTopology walks - what
// lets ApplyThreadPolicy tell a real prime set from a symmetric machine's "every cpu".
Uint64 OnlineCpuMask() {
const Uint cpuCount = std::min(64u, std::max(1u, std::thread::hardware_concurrency()));
@@ -201,6 +203,20 @@ namespace MobileGL::MG_Remote::Server {
} // namespace
// P11 B1: the one place the reserved core is computed for anybody outside the apply thread - the
// supervisor announces it at start-up and the server app's broker relays it to the helper. The
// supervisor has not loaded MG_Config (its session children do), so the knob is read from the
// environment the children inherit.
Uint64 ReservedApplyCoreForThisProcess(const char** outRule) {
Uint64 big = 0, prime = 0;
DetectCoreTopology(&big, &prime);
ReservedCoreReason reason = ReservedCoreReason::NoAsymmetry;
const Uint64 core = ReservedApplyCoreForConfig(std::getenv("MOBILEGL_IPC_SERVER_AFFINITY"), big, prime,
OnlineCpuMask(), &reason);
if (outRule != nullptr) *outRule = ReservedCoreReasonText(reason);
return core;
}
// ---------------------------------------------------------------------------------
// The private backend object
// ---------------------------------------------------------------------------------
@@ -413,10 +429,11 @@ namespace MobileGL::MG_Remote::Server {
ServerSession& session = *m_session;
const char* raw = AffinityStringFromConfig();
const Bool automatic = raw == nullptr || raw[0] == '\0' || std::strcmp(raw, "auto") == 0;
Uint64 bigCores = 0, primeCores = 0;
if (automatic) DetectCoreTopology(&bigCores, &primeCores);
const ApplyThreadPolicy policy =
SelectApplyThreadPolicy(raw, SpinSetExplicitly(), SpinUsFromConfig(), session.Peer(),
session.PeerSharedSegments(), automatic ? DetectBigCoreMask() : 0,
OnlineCpuMask());
session.PeerSharedSegments(), bigCores, primeCores, OnlineCpuMask());
const Bool recognised = policy.recognised;
const Uint64 requested = policy.requestedMask;
m_affinityMask = ApplyAffinity(requested);
+6
View File
@@ -597,6 +597,12 @@ namespace MobileGL::MG_Remote::Server {
EGLSurface curRead, EGLContext curCtx, EGLDisplay dpy,
EGLSurface draw, EGLSurface read, EGLContext ctx);
// P11 B1 (CONTRACT-P11 B1): the cpu core this process's `auto` policy reserves for a dialled-in
// shared-segment client's apply thread (ApplyThreadPolicy.h ReservedApplyCoreForConfig over this
// machine's topology and MOBILEGL_IPC_SERVER_AFFINITY), 0 when none; `outRule` receives the reason.
// The supervisor announces it at start-up; the server app's broker relays it to the helper.
Uint64 ReservedApplyCoreForThisProcess(const char** outRule);
// ---------------------------------------------------------------------------------
// THE EGL OWNERSHIP MOVE - the part that can sink the phase, expressed as twelve calls
// ---------------------------------------------------------------------------------
+13
View File
@@ -1462,6 +1462,19 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int a
int listener = -1;
if (SocketTransport::Listen(endpoint, &listener) != MOBILEGL_OK) return 72;
WireLogError("MG_Remote server: pid=%d listening on %s", static_cast<int>(::getpid()), endpoint.c_str());
{
// P11 B1 (CONTRACT-P11 B1): the one announcement of the core a dialled-in shared-segment client's
// apply thread will take, on stdout, which the server app reads line by line
// (MobileGLServerService) and its broker hands to the helper - so the helper keeps the client
// off the core THIS server picks rather than a Java re-derivation of the rule.
const char* rule = "";
const auto reserved = static_cast<unsigned long long>(Server::ReservedApplyCoreForThisProcess(&rule));
std::fprintf(stdout, "MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x%llx (%s)\n",
reserved, rule);
std::fflush(stdout);
WireLogError("MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x%llx (%s)",
reserved, rule);
}
unsigned long sessionsFaulted = 0;
const bool tcpEndpoint = endpoint.compare(0, 6, "tcp://") == 0;
const auto knobs = Server::PreAuthKnobs::FromEnvironment();