perf(game): the path he actually walks -- meshed p95 4.91s -> 0.41s, RSS 19.5GB -> 1.7GB
The owner: "I want real performance gain for the ingame path... not in deletion, but
in optimization and tricks." Three rounds of ours had improved the commandlet export
he never runs. This one is the streaming path.
MEMORY -- it was retention, not the new lattice
19.5 GB was worker FChunkSDFCache arrays keeping capacity when keys were replaced
(946 MB across 15 workers in diagnostics), shared graph caches with no byte or
lifetime budget, and batch commandlets holding transient UObject worlds until exit.
BlockSkip is bounded to 8^3 bytes per tile; interval scratch is worker-local and
capped. Fixes: release storage on key replacement, LRU with a 256 MiB resident
budget, limit in-flight graph builds, drop oversized entries, CollectGarbage between
batch cases.
peak RSS 19.5 GB -> 1.643 GB (their runs) / 1.71 GB (mine, 3x128^3) -91%
a 3-case stress batch showed no accumulation
WHY SOME EMPTY TILES COST 5.6s AND OTHERS 3us -- a 1.9 MILLION x spread
Fast ones returned straight from the layout/edge proof (verdict AllAir/AllSolid,
refine=0, mesh=0). Slow ones returned Mixed, so the caller ran the FULL mesher grid
and only discovered emptiness by brute force -- logs show empty=1 after ~5 s of mesh
work. The interval machinery now reaches a verdict before a mesh job is enqueued.
IN GAME, 841 requests at streaming radius:
count p50 p95 max
empty 394 6.087 ms 12.113 ms 25.049 ms
meshed 447 114.748 ms 410.314 ms 844.043 ms
LOD0 empty 221 7.126 ms 11.901 ms 25.049 ms
LOD0 meshed 122 355.794 ms 600.945 ms 844.043 ms
against d38d4ca: meshed p50/p95/max 0.6085 / 4.9075 / 5.0199 s
LOD4 empty max 5.618 s
=> meshed p50 5.3x, p95 12.0x, and the multi-second empty outliers are gone.
REQUEST-TO-READY -- the number that answers "I can outrun the generation and fall"
all tiles p50 57.576 ms p95 429.052 ms max 905.633 ms
empty p50 30.943 ms p95 98.302 ms max 305.505 ms
meshed p50 128.521 ms p95 462.235 ms max 905.633 ms
At the harness walking speed of 6 m/s: p95 = 2.574 m, worst case = 5.434 m. A walking
player now outruns tile readiness by a couple of metres, not by a strate.
CAPABILITY PRESERVED, which is the point
The owner asked that speed never come from quietly simplifying the world. It did not:
geometry SHA-256 98eaad61..d3c4f is IDENTICAL to d38d4ca, 153,740 triangles, 82,516
vertices, across three runs. No resolution, sample-count, octave or seed reduction.
Unknown intervals still cost the skip. Prove and skip, never simplify and hope.
Export path, my own build, 3 runs: 1.088 / 1.102 / 1.100 s (was 1.171-1.258 s). ~9%,
below the 17% noise floor, so not claimed as a speed win -- the export was not the
target.
Honest remainder: LOD0 empties are ~7 ms p50 with a 25 ms tail, not the 2-5 ms of a
pure layout proof. Those are bounded, sound interval refinements that never invoke
meshing; going lower needs stronger proof bounds, not more skipping.
Verified by me: build 0 errors, binary timestamp checked against sources, peak RSS
measured, hashes compared. Nothing outside Plugins/VoxelForge; RMC untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
d38d4cabef
commit
412a7ad219
11 files changed
+4374
-752
No files matched your search
@@ -4940,6 +4940,10 @@ int32 RunExploreCase(const FString& Params, FString* OutJson)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// A commandlet can run several fresh manager lifetimes in one process. Release completed
|
||||
// immutable proof caches from the preceding case before constructing this one.
|
||||
VoxelDensityOps::TrimSharedRoomGraphCache();
|
||||
|
||||
// Every case starts from a disabled, empty diagnostic state. The world/cache objects are
|
||||
// local to this invocation and their owner/lifetime keys prevent stale worker caches from
|
||||
// serving the next case's generation.
|
||||
@@ -5529,6 +5533,8 @@ int32 RunExploreCase(const FString& Params, FString* OutJson)
|
||||
static_cast<unsigned long long>(MesherAttributionCalls));
|
||||
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Disabled);
|
||||
}
|
||||
VoxelDensityOps::ReportSharedRoomGraphCacheFootprint();
|
||||
VoxelDensityOps::TrimSharedRoomGraphCache();
|
||||
// Per-case isolation boundary. The UObject world dies with this function; worker-local
|
||||
// caches are keyed by the fresh generator/manager lifetime and the diagnostics are reset before
|
||||
// the next case. No case may inherit profiler counters or a live profiler mode.
|
||||
@@ -5861,6 +5867,12 @@ int32 RunBatch(const FString& Params)
|
||||
{
|
||||
++FailedCases;
|
||||
}
|
||||
|
||||
// RunExploreCase owns a complete transient UObject world. Release that case's strong
|
||||
// roots before starting the next one and collect now; otherwise a batch retains every
|
||||
// completed mesh/settings/manager graph until the commandlet exits, making peak RSS grow
|
||||
// with the number of cases even though no report needs those objects afterward.
|
||||
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> BatchReport = MakeShared<FJsonObject>();
|
||||
|
||||
Reference in new issue
Block a user