perf #418: publish landblocks under the meter, not one per streaming tick
Phase-1 measurement (new [publish-timing]/[stream-tick] probe surfaces, ACDREAM_PROBE_REVEAL_TIMING=1) refuted the ~31 ms-per-admission hypothesis: the hold runs at ~64 fps with the streaming tick at ~32 Hz, the whole 625-block window costs only ~500 ms of publication CPU (far blocks ~0.17 ms, near 2-43 ms), and steady state showed ZERO meter yields with ~0.22 ms of the 2 ms budget used - yet exactly one block published per tick against a ~400-deep completion queue. The real limiter: Runtime's collision-generation activation is a deliberate two-poll transaction (the first TryAcquireCollisionPrefixMutationPermission poll parks residents and refuses by design), and LandblockPresentationPipeline.Advance's metered arm returned Completed=false on ANY nonterminal commit, which DrainAndApply treats as end-of-frame. One landblock per 32 Hz tick = the flat 32/s, with the authored budget ~90% idle. Fix: the metered arm now uses the same Runtime-owned gate the unmetered arm and the synchronous CompletePublication API always used (CanContinueMutationSynchronously). The second poll runs in the same frame under the same meter, so the unchanged 2 ms elapsed-time ceiling is now genuinely the authoritative per-frame bound; with any real debt (live residents parked mid-game, pending withdrawals, dispatch backlog) publication defers to the next frame exactly as before. No budget values change, no reveal-gate/readiness change, and the streamed result is byte-identical - only the frame scheduling of identical operations. Measured A/B (this binary, two runs): totalMs 12689 / 12734 vs baseline 26728/27395/27503; loaded slope 32/s -> bursts of 100-360/s, 625/625 in ~6-7 s vs ~23 s. The remaining ~12.7 s floor is fully attributed in docs/ISSUES.md: ~8 s of real budgeted readiness work plus retail's authored tunnel exit (TunnelContinue 2-5 s + two 1 s fades, golden constants), so the <12 s acceptance needs a lead decision on the hold-time budget, not another hidden limiter. New regression pin: MeteredLoaded_NonterminalCommitWithoutDebt_CompletesInOneMeteredAdvance. Gates: Release build 0 errors; App tests 5576/3 skips/0 failed; Runtime tests 1756/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
45f379560a
commit
11106c70e7
6 changed files with 408 additions and 5 deletions
|
|
@ -64,6 +64,13 @@ public sealed class LandblockPresentationPipeline
|
|||
public bool SpatialPresentationCommitted;
|
||||
public bool EnvCellReplayCommitted;
|
||||
public bool LiveRecoveryCommitted;
|
||||
|
||||
/// <summary>
|
||||
/// #418 measurement only (<c>ACDREAM_PROBE_REVEAL_TIMING=1</c>): the
|
||||
/// per-stage wall-clock attribution of this one publication. Null
|
||||
/// whenever the probe env is unset.
|
||||
/// </summary>
|
||||
public PublicationStageTimings? Timing;
|
||||
}
|
||||
|
||||
private readonly Action<LandblockBuild, LandblockMeshData>?
|
||||
|
|
@ -460,6 +467,7 @@ public sealed class LandblockPresentationPipeline
|
|||
Origin: completedBuild.Origin),
|
||||
meshData),
|
||||
Tier = LandblockStreamTier.Far,
|
||||
Timing = PublicationTimingProbe.CreateTimings(),
|
||||
};
|
||||
_publications.Add(acceptedResult, transaction);
|
||||
}
|
||||
|
|
@ -509,6 +517,7 @@ public sealed class LandblockPresentationPipeline
|
|||
LandblockId = farLandblock.LandblockId,
|
||||
Cost = LandblockStreamResultCost.Estimate(farBuild, meshData),
|
||||
Tier = LandblockStreamTier.Far,
|
||||
Timing = PublicationTimingProbe.CreateTimings(),
|
||||
};
|
||||
_publications.Add(acceptedResult, transaction);
|
||||
}
|
||||
|
|
@ -553,6 +562,7 @@ public sealed class LandblockPresentationPipeline
|
|||
Cost = estimate
|
||||
?? LandblockStreamResultCost.Estimate(build, meshData),
|
||||
Tier = tier,
|
||||
Timing = PublicationTimingProbe.CreateTimings(),
|
||||
};
|
||||
_publications.Add(result, created);
|
||||
return created;
|
||||
|
|
@ -565,6 +575,26 @@ public sealed class LandblockPresentationPipeline
|
|||
bool ensureProgress)
|
||||
{
|
||||
bool progressed = false;
|
||||
void RunTimed(string stage, Action operation)
|
||||
{
|
||||
if (transaction.Timing is not { } timing)
|
||||
{
|
||||
operation();
|
||||
return;
|
||||
}
|
||||
|
||||
long start = System.Diagnostics.Stopwatch.GetTimestamp();
|
||||
try
|
||||
{
|
||||
operation();
|
||||
}
|
||||
finally
|
||||
{
|
||||
timing.Add(
|
||||
stage,
|
||||
System.Diagnostics.Stopwatch.GetTimestamp() - start);
|
||||
}
|
||||
}
|
||||
bool TryRun(
|
||||
StreamingWorkCost cost,
|
||||
string stage,
|
||||
|
|
@ -572,7 +602,7 @@ public sealed class LandblockPresentationPipeline
|
|||
{
|
||||
if (meter is null)
|
||||
{
|
||||
operation();
|
||||
RunTimed(stage, operation);
|
||||
progressed = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -585,7 +615,7 @@ public sealed class LandblockPresentationPipeline
|
|||
return false;
|
||||
try
|
||||
{
|
||||
operation();
|
||||
RunTimed(stage, operation);
|
||||
meter.Complete();
|
||||
progressed = true;
|
||||
return true;
|
||||
|
|
@ -810,8 +840,33 @@ public sealed class LandblockPresentationPipeline
|
|||
}
|
||||
if (transaction.PhysicsPublication.RuntimeMutationPending)
|
||||
{
|
||||
if (meter is not null
|
||||
|| !_physicsPublisher
|
||||
// #418: Runtime's collision-generation activation is a
|
||||
// deliberate two-poll transaction — the first
|
||||
// CommitCollisionGeneration poll closes the prefix
|
||||
// quiescence boundary (parking any affected residents)
|
||||
// and returns nonterminal; only a later poll may
|
||||
// consume mutation permission. The metered path used
|
||||
// to defer that later poll to the NEXT frame
|
||||
// unconditionally, which serialized the whole
|
||||
// completion drain to ONE landblock per streaming tick
|
||||
// (DrainAndApply breaks on an incomplete publication):
|
||||
// the measured login hold published exactly 32
|
||||
// blocks/s at a 32 Hz tick rate with ZERO meter
|
||||
// yields and ~0.2 ms of its 2 ms budget used. Runtime
|
||||
// explicitly supports consuming that finite suffix
|
||||
// synchronously when it reports no cross-cutting debt
|
||||
// (CanContinueMutationSynchronously — the same gate
|
||||
// the unmetered path and the synchronous
|
||||
// CompletePublication API already use), so the
|
||||
// metered path now polls again within the same frame
|
||||
// under the SAME meter: every extra poll still passes
|
||||
// TryRun/TryReserve, so the elapsed-time budget
|
||||
// remains the authoritative per-frame bound. With any
|
||||
// real debt (live residents parked mid-game, pending
|
||||
// withdrawals, dispatch backlog) Runtime keeps
|
||||
// reporting nonterminal-with-debt and this defers to
|
||||
// the next frame exactly as before.
|
||||
if (!_physicsPublisher
|
||||
.CanContinueMutationSynchronously())
|
||||
{
|
||||
return new LandblockPublicationAdvance(
|
||||
|
|
@ -976,6 +1031,13 @@ public sealed class LandblockPresentationPipeline
|
|||
}
|
||||
|
||||
_publications.Remove(result);
|
||||
if (transaction.Timing is { } completedTiming)
|
||||
{
|
||||
PublicationTimingProbe.EmitPublication(
|
||||
transaction.LandblockId,
|
||||
transaction.Kind.ToString(),
|
||||
completedTiming);
|
||||
}
|
||||
return new LandblockPublicationAdvance(true, progressed);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue