tools(profile): the instrument lied by 11.5x -- now it checks itself, and batching kills startup cost

Overnight task 1 of the owner's mandate: "better diagnostic, profiler and tools,
so we can batch test way more quicker."

WHY THIS CAME BEFORE ANY MORE OPTIMISATION
ee89c4d delivered a 5x candidate reduction and a 16x cheaper support floor for
0% end-to-end gain, because the profiler inflated by ~11.5x (14.74 us/sample
profiled vs 1.28 us/voxel real). Two rounds of bucket rankings were artefacts of
FPlatformTime::Cycles64() wrapped around ~100 ns operations -- the timer pair
calibrates at ~0.1 us, so it was measuring itself.

PROFILER
Deterministic count sampling plus amortised block timing every 64 density calls.
Coarse counters (calls, candidates) stay always-on -- they were never the problem.
Timing-only scopes are opt-in behind -profiledensityfull. UE CPU trace scopes added.

  before   profiled 14.74 us/sample  vs  1.28 us/voxel real   ~11.5x
  after    profiled 0.463050 s       vs  0.558470 s unprofiled   -17.09%
           geometry 235AC2DC in both

⭐ Every profiled report now prints profiled total, unprofiled total, discrepancy,
ratio and geometry equality, and EXITS CODE 2 if the comparison fails. An
intermediate run hit -22.6% and failed loudly, which is the point: this class of
bug must be impossible to miss rather than something noticed three rounds later.

⚠️ The -17% residual is run-to-run noise, so ~17% is our measurement floor.
Improvements smaller than that cannot be claimed from a single pair of runs.

BATCH EXPLORER
One process, many cases, one JSON. Startup was ~25-30 s of engine boot for ~3 s of
work, which is what made every iteration expensive.

  3 cases in 6.59 s of case time
  standalone vs batched, same binary: hash 235AC2DC, 2,461 tris, 343,000 tunnel
  lookups, 554,381 passage evaluations -- identical on both

-out= is enforced absolute under the plugin's own Saved/.

CACHE BREAKDOWN (the 272 KB/entry question)
The slot table is small. The weight is spatial-index bins and candidate arrays,
room arrays, support-column arrays and tunnel-world indices -- i.e. much of the
411 MiB is the spatial index ee89c4d added, which also bought 0% end-to-end.
Worth revisiting on those terms.

RESEARCH (recorded, not acted on) -- CODEX-TASK-1-PROFILER-BATCH-RESEARCH.md
Ranked follow-ups: deterministic empty-space early-out, density-grid reuse, task
granularity (ParallelForWithExistingTaskContext + MinBatchSize), SIMD MC (~2x over
optimised scalar, but cross-machine determinism is the risk), sparse hierarchy,
RMC partial updates, Transvoxel LOD seams. Sources cited in the doc.
⚠️ Upstream RMC is 5.3.2; the owner's copy identifies as 5.4 with custom
modifications. No upgrade is safe. Left untouched.

PUSHBACK, accepted: "buckets must sum to their parent" needs a parallel-time
definition -- summing worker CPU is not wall time. Reports now separate worker CPU
sum from wall-clock estimate.

End-to-end unchanged, as expected for a tooling change: 2.746 / 2.691 s on the
128^3 seed 1337 region (was 2.678-2.705). Build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-09 05:02:21 +02:00
1 parent ee89c4d8c2
commit e63f016835
10 files changed
+1494 -124

No files matched your search

@@ -346,42 +346,47 @@ bool FCaveSpatialIndex::GetColumnCandidates(
SIZE_T FChunkSDFCache::GetAllocatedSize() const
{
SIZE_T Bytes = 0;
auto AddArray = [&Bytes](const auto& Array)
return static_cast<SIZE_T>(GetAllocatedSizeBreakdown().TotalBytes());
}
VoxelDensityProfile::FCacheMemoryBreakdown FChunkSDFCache::GetAllocatedSizeBreakdown() const
{
VoxelDensityProfile::FCacheMemoryBreakdown Breakdown;
auto ArrayBytes = [](const auto& Array) -> uint64
{
Bytes += static_cast<SIZE_T>(Array.GetAllocatedSize());
return static_cast<uint64>(Array.GetAllocatedSize());
};
AddArray(Rooms);
AddArray(RoomFloorJoins);
AddArray(Tunnels);
AddArray(Pits);
AddArray(Chimneys);
AddArray(Columns);
AddArray(SupportColumnEntries);
AddArray(SupportColumns);
Breakdown.RoomsBytes = ArrayBytes(Rooms);
Breakdown.RoomFloorJoinsBytes = ArrayBytes(RoomFloorJoins);
Breakdown.TunnelsBytes = ArrayBytes(Tunnels);
Breakdown.PitsBytes = ArrayBytes(Pits);
Breakdown.ChimneysBytes = ArrayBytes(Chimneys);
Breakdown.ColumnsBytes = ArrayBytes(Columns);
Breakdown.SupportColumnEntriesBytes = ArrayBytes(SupportColumnEntries);
Breakdown.SupportColumnsBytes = ArrayBytes(SupportColumns);
for (const FTunnelSupportFloorColumn& Column : SupportColumns)
{
AddArray(Column.Intervals);
Breakdown.SupportColumnIntervalsBytes += ArrayBytes(Column.Intervals);
}
for (const FCachedTunnel& Tunnel : Tunnels)
{
AddArray(Tunnel.ControlPoints);
AddArray(Tunnel.ControlRadii);
AddArray(Tunnel.WorldControlPoints);
AddArray(Tunnel.WorldControlRadii);
Breakdown.TunnelsBytes += ArrayBytes(Tunnel.ControlPoints);
Breakdown.TunnelsBytes += ArrayBytes(Tunnel.ControlRadii);
Breakdown.TunnelsBytes += ArrayBytes(Tunnel.WorldControlPoints);
Breakdown.TunnelsBytes += ArrayBytes(Tunnel.WorldControlRadii);
}
const FCaveSpatialIndex* Indices[] = {
&RoomIndex, &RoomFloorJoinIndex, &TunnelIndex, &TunnelWorldIndex,
};
for (const FCaveSpatialIndex* Index : Indices)
{
AddArray(Index->Bins);
AddArray(Index->CandidateIndices);
}
return Bytes;
Breakdown.RoomIndexBinsBytes = ArrayBytes(RoomIndex.Bins);
Breakdown.RoomIndexCandidatesBytes = ArrayBytes(RoomIndex.CandidateIndices);
Breakdown.RoomFloorJoinIndexBinsBytes = ArrayBytes(RoomFloorJoinIndex.Bins);
Breakdown.RoomFloorJoinIndexCandidatesBytes = ArrayBytes(RoomFloorJoinIndex.CandidateIndices);
Breakdown.TunnelIndexBinsBytes = ArrayBytes(TunnelIndex.Bins);
Breakdown.TunnelIndexCandidatesBytes = ArrayBytes(TunnelIndex.CandidateIndices);
Breakdown.TunnelWorldIndexBinsBytes = ArrayBytes(TunnelWorldIndex.Bins);
Breakdown.TunnelWorldIndexCandidatesBytes = ArrayBytes(TunnelWorldIndex.CandidateIndices);
return Breakdown;
}
//=============================================================================
@@ -3311,7 +3316,7 @@ void VoxelCaveMorphology::BuildChunkCache(
const float MaxInfluence = FMath::Max(
RoomRadiusEnvelope + FloorReliefEnvelope,
FMath::Abs(Params.TunnelWarpStrength) + TunnelRadiusEnvelope
) + BlendEnvelope;
) + BlendEnvelope * 3.0f;
const float MaxTunnelLen = FMath::Max(Params.MaxTunnelLength, 0.0f);
@@ -3897,7 +3902,7 @@ void VoxelCaveMorphology::BuildChunkCache(
BoundR = FMath::Max(
BoundR, FVector::Dist(CT.BoundCenter, CT.ControlPoints[PointIndex]));
}
BoundR += MaxTunnelRadius + FMath::Max(BlendK, 0.0f);
BoundR += MaxTunnelRadius + FMath::Max(BlendK, 0.0f) * 3.0f;
CT.BoundRadiusSq = BoundR * BoundR;
// Keep a separate bound for the world-space structural backstop. The SDF and world
@@ -4042,7 +4047,8 @@ void VoxelCaveMorphology::BuildChunkCache(
Join.CeilingZ = CommonFloor + JoinRequiredHeight;
Join.BoundCenter = (Join.Start + Join.End) * 0.5f;
const float BoundRadius = 0.5f * FVector::Dist(Join.Start, Join.End)
+ FMath::Abs(Join.Radius) + FMath::Max(BlendK, 0.0f);
+ FMath::Abs(Join.Radius)
+ FMath::Max(BlendK, 0.0f) * 3.0f;
Join.BoundRadiusSq = BoundRadius * BoundRadius;
if (SphereTouchesSearchXY(Join.BoundCenter, Join.BoundRadiusSq))
@@ -4392,7 +4398,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
int32 RoomCount = 0;
const bool bIndexedRooms = Cache.RoomIndex.GetBin(
WorldX, WorldY, WorldZ, RoomFirst, RoomCount);
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::CaveRoomCandidates,
@@ -4409,7 +4415,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
// --- DISTANCE CULL ---
const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Room.Center);
if (DistSq > Room.CullRadiusSq) return;
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::CaveRoomEvaluated);
@@ -4515,7 +4521,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
int32 TunnelCount = 0;
const bool bIndexedTunnels = Cache.TunnelIndex.GetBin(
WorldX, WorldY, WorldZ, TunnelFirst, TunnelCount);
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::CaveTunnelCandidates,
@@ -4536,7 +4542,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
// --- BOUNDING SPHERE CULL ---
const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Tunnel.BoundCenter);
if (DistSq > Tunnel.BoundRadiusSq) return;
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::CaveTunnelEvaluated);
@@ -4714,12 +4720,12 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
int32 TunnelCount = 0;
const bool bIndexedTunnels = Cache.TunnelWorldIndex.GetBin(
WorldX, WorldY, WorldZ, TunnelFirst, TunnelCount);
if (SupportColumn != nullptr && VoxelDensityProfile::IsEnabled())
if (SupportColumn != nullptr && VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelSupportFloorQueries);
}
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelCoreCandidates,
@@ -4753,7 +4759,7 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
{
return;
}
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelCoreEvaluated);
@@ -4765,7 +4771,7 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
? Tunnel.WorldControlRadii : Tunnel.ControlRadii;
if (bHasWorldChain)
{
if (SupportColumn != nullptr && VoxelDensityProfile::IsEnabled())
if (SupportColumn != nullptr && VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelSupportFloorChecks);
@@ -4881,7 +4887,7 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
int32 TunnelCount = 0;
const bool bIndexedTunnels = Cache.TunnelWorldIndex.GetBin(
WorldX, WorldY, WorldZ, TunnelFirst, TunnelCount);
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelCoreCandidates,
@@ -4941,7 +4947,7 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
if (TestTunnel(TunnelIdx))
{
bSupport = true;
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelCoreEvaluated);
@@ -4957,7 +4963,7 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
if (TestTunnel(TunnelIdx))
{
bSupport = true;
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelCoreEvaluated);
@@ -4976,7 +4982,7 @@ void VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
{
OutColumn.Reset();
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelSupportColumnBuilds);
@@ -4993,7 +4999,7 @@ void VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
}
}
if (VoxelDensityProfile::IsEnabled())
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelSupportColumnCandidates,
@@ -5131,7 +5137,7 @@ float VoxelCaveMorphology::EvaluateSDF(
const float Margin = FMath::Max(
RoomRadiusEnvelope + FloorReliefEnvelope,
FMath::Abs(Params.TunnelWarpStrength) + TunnelRadiusEnvelope
) + BlendEnvelope;
) + BlendEnvelope * 3.0f;
FChunkSDFCache TempCache;
BuildChunkCache(