Files
VoxelForge/Source/VoxelForge/Public/VoxelDensityProfile.h
T
Fr0zkaandClaude Opus 5 26b34765d5 perf(generation): exact worm block skip with a proven 3D Perlin bound -- measured NEUTRAL
The sampler put the worm carve's first noise call (VoxelGenerator.cpp:3985, FusedWorm) at 19.4% of
LOD0. The worm only carves where N1 + N2 < T with N2 >= 0, so where N1 >= T it contributes nothing,
bit for bit. A block proven to have N1 >= T everywhere can skip both noise calls exactly.

- The Lipschitz bound for this Perlin3D implementation: L = (15/4) * sqrt(3) * 1.25 = 8.118988160,
  in scaled-N1 units, with a 1e-3 float-rounding margin. Adversarial sampling found a worst ratio
  of 3.187 (0.39 L). The existing 2D floor-relief bound (8.5) is a component bound of a different
  field and was confirmed not to transfer.
- A soundness test that can fail (VoxelForgeWormBlockSkipTest): 6,476 proved blocks, 355,945 exact
  lattice samples, 0 violations. A deliberately halved L proves a bad block; the full L rejects it.
- voxel.WormBlockSkip, default 1.

Field exact: export OBJ byte-identical (b3e5f4c3..9b377), 841/841 keyed tiles identical to
NextHeadStaticTrace_20260913, capability 20,830 / 10,909.

RESULT, stated plainly: no measurable speed gain. The skip rate is 4.07% of worm-eligible LOD0
samples and 0% at LOD1-4, because the bound is ~2.5x looser than the worst observed ratio at the
block size used. Paired on-vs-off, two interleaved runs each: static worker +2.15%, generation p50
+1.8%; moving worker -1.42%, request p50 -2.5%. Within noise. The next round turns the default OFF.
The proven bound and its test are kept for a tighter skip later (smaller or hierarchical blocks,
or a per-cell bound).

Run ledger (from Codex's report): the first build failed on an ambiguous test overload and was
fixed; two timing runs are void because the orchestrator stopped the session so the owner could
use the PC; the eight valid timing runs, the sampler run and the diagnostics runs completed. No
new crash folders. PERF-SAMPLED.md now lists all eight earlier-round UECC crash records.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
2026-09-14 04:29:11 +02:00

453 lines
16 KiB
C++

// Opt-in density-path profiling used by the editor commandlet and targeted PIE diagnostics.
// Disabled by default so the shipping density path pays only one predictable branch in the
// scoped timers below.
#pragma once
#include "CoreMinimal.h"
#include "HAL/PlatformTime.h"
#include <atomic>
namespace VoxelDensityProfile
{
enum class EMode : uint8
{
Disabled,
Sampled,
// Low-overhead attribution: keeps the parent and stage ledger trustworthy, samples
// hot component scopes, and collects classifier detail without the old full-scope storm.
Attribution,
Full,
};
// Count-based sampling is deterministic with respect to the profiler's own counters, but the
// report is intentionally diagnostic only. It never participates in density or mesh output.
constexpr uint32 DefaultSampleInterval = 128;
enum class EBucket : uint8
{
GetDensityAt,
ApplyDisturbances,
PassageModifier,
PassageLandingAir,
PassageLandingFloor,
PassageLandingRoomFloor,
PassageTunnelAir,
PassageStructuralPosts,
TunnelCoreSupport,
TunnelCoreSdf,
TunnelCorePosts,
StructuralTail,
RoomGraphSource,
SdfCarve,
CaveRoughnessMod,
CaveTerraceMod,
LayerLineMod,
RibbingMod,
CaveOverhangMod,
CaveCliffMod,
ScallopMod,
CaveArchMod,
RoomColumnMod,
DomeMod,
PinchMod,
FloorBiasMod,
WormFieldSource,
OriginSpineOp,
BoundarySealOp,
PassageCarveOp,
XYEdgeSealOp,
OtherOp,
// Attribution components. These are nested diagnostics; the non-overlapping phase
// buckets below remain the density accounting ledger.
FusedEvaluator,
InterpretedOpStack,
OperatorBlock,
RoomGraphBuild,
RoomGraphSdf,
TunnelCoreWorld,
FusedDetail,
FusedWorm,
FusedStructural,
ClassifierTotal,
ClassifierIntervalProof,
ClassifierExactCore,
ClassifierExactFinal,
ClassifierRoomTail,
RuntimeStreamBuilding,
// Non-overlapping top-level phases. The named operation buckets above are intentionally
// nested diagnostics; these phase buckets are the accounting ledger that must add up to
// GetDensityAt without double-counting a stack op inside its enclosing phase.
DensityPrologue,
DensityCore,
DensityDisturbances,
DensityStructuralPosts,
DensityBoundarySeal,
DensityDiffLayer,
DensityTail,
// Mesher-side accounting. GenerateMesh is the inclusive total; the following buckets
// cover its regular-grid work and its non-grid setup/finalisation.
MesherGenerateMesh,
MesherDensityGrid,
MesherCellClassification,
MesherGradientNormals,
MesherVertexInterpolation,
MesherStreamBuilding,
MesherOther,
Count
};
constexpr int32 BucketCount = static_cast<int32>(EBucket::Count);
enum class ECounter : uint8
{
TunnelCacheLookup,
TunnelCacheHit,
TunnelCacheMiss,
SdfCacheBuild,
RoomGraphBuildGeneratorTile,
RoomGraphBuildGeneratorTunnelCore,
RoomGraphBuildOpShared,
RoomGraphBuildOpLocal,
RoomGraphBuildClassifierShared,
RoomGraphBuildClassifierLocal,
RoomGraphBuildUnknown,
CaveRoomCandidates,
CaveRoomEvaluated,
CaveTunnelCandidates,
CaveTunnelEvaluated,
TunnelCoreCandidates,
TunnelCoreEvaluated,
TunnelSupportColumnBuilds,
TunnelSupportColumnCandidates,
TunnelSupportFloorQueries,
TunnelSupportFloorChecks,
TunnelSupportFloorBackstopFires,
TunnelRoomFloorBackstopFires,
TunnelAuthoredFloorSamples,
TunnelAuthoredFloorProtectionClamps,
TunnelAuthoredFloorDisturbanceSkips,
TunnelFloorProfileFallbacks,
TunnelFloorProfileBuilds,
TunnelFloorProfileSegments,
TunnelFloorProfileLedges,
TunnelFloorProfileLedgesBelow2Voxels,
TunnelFloorProfileLedges2To4Voxels,
TunnelFloorProfileLedges4To8Voxels,
TunnelFloorProfileLedges8PlusVoxels,
PassageSupportFloorBackstopFires,
PassageNativeFloorCompositions,
PassageCandidates,
PassageEvaluated,
MesherBlockTests,
MesherBlockAllSolid,
MesherBlockAllAir,
TileClassifyCalls,
TileClassifyAllSolid,
TileClassifyAllAir,
TileClassifyMixed,
FusedTunnelSamples,
FusedTunnelDetailSamples,
FusedTunnelDetailSkipped,
WormEligibleSamples,
WormBlockProofs,
WormBlockSkippedSamples,
OpBlockBuilds,
OpBlockSamples,
OpBlockOperators,
OpBlockActiveOperators,
OpBlockPrunedOperators,
MesherDensityGridBytes,
MesherSharedGridReadBytes,
MesherOutputArrayBytes,
MesherOutputAllocatedBytes,
OpBlockScratchBytes,
OpBlockCopyBytes,
Count
};
constexpr int32 CounterCount = static_cast<int32>(ECounter::Count);
// Export the one fast-path bit so callers in the editor module can skip instrumentation
// without crossing the DLL boundary for every scoped timer or counter. It is diagnostic
// state only; it never participates in density or mesh decisions.
VOXELFORGE_API extern std::atomic<bool> GEnabled;
VOXELFORGE_API extern std::atomic<EMode> GMode;
inline thread_local bool GSampledScopeActive = false;
// Resident allocator-backed bytes for one worker's generation cache family. The profiler
// aggregates this by worker; the fields intentionally describe allocations owned by the
// cache, not the inline size of the enclosing C++ object. SlotStorageBytes is the one
// exception: it is the inline/static storage reserved for the worker's fixed cache table.
struct FCacheMemoryBreakdown
{
uint64 SlotStorageBytes = 0;
uint64 OpStackBytes = 0;
uint64 RoomsBytes = 0;
uint64 RoomFloorJoinsBytes = 0;
uint64 TunnelsBytes = 0;
uint64 PitsBytes = 0;
uint64 ChimneysBytes = 0;
uint64 ColumnsBytes = 0;
uint64 SupportColumnEntriesBytes = 0;
uint64 SupportColumnsBytes = 0;
uint64 SupportColumnIntervalsBytes = 0;
uint64 SpatialIndexBytes = 0;
uint64 TotalBytes() const
{
return SlotStorageBytes + OpStackBytes
+ RoomsBytes + RoomFloorJoinsBytes + TunnelsBytes + PitsBytes
+ ChimneysBytes + ColumnsBytes + SupportColumnEntriesBytes
+ SupportColumnsBytes + SupportColumnIntervalsBytes + SpatialIndexBytes;
}
uint64 DynamicBytes() const
{
return TotalBytes() - SlotStorageBytes;
}
FCacheMemoryBreakdown& operator+=(const FCacheMemoryBreakdown& Other)
{
SlotStorageBytes += Other.SlotStorageBytes;
OpStackBytes += Other.OpStackBytes;
RoomsBytes += Other.RoomsBytes;
RoomFloorJoinsBytes += Other.RoomFloorJoinsBytes;
TunnelsBytes += Other.TunnelsBytes;
PitsBytes += Other.PitsBytes;
ChimneysBytes += Other.ChimneysBytes;
ColumnsBytes += Other.ColumnsBytes;
SupportColumnEntriesBytes += Other.SupportColumnEntriesBytes;
SupportColumnsBytes += Other.SupportColumnsBytes;
SupportColumnIntervalsBytes += Other.SupportColumnIntervalsBytes;
SpatialIndexBytes += Other.SpatialIndexBytes;
return *this;
}
};
struct FSnapshot
{
uint64 Calls[BucketCount]{};
// Sum of sampled worker time. Useful for CPU-work accounting, but not directly comparable
// with an export wall clock when the mesher runs in parallel.
uint64 Cycles[BucketCount]{};
uint64 Samples[BucketCount]{};
// Largest estimated worker contribution. This is the report-facing parallel wall
// estimate; it avoids mistaking several parallel workers for several exports.
uint64 WallCycles[BucketCount]{};
uint32 ActiveWorkers[BucketCount]{};
uint64 Counters[CounterCount]{};
// Classifier detail is sampled separately from the timer buckets. Calls is the exact
// number of classifier invocations; the remaining fields are worker-local samples
// expanded at Snapshot() using Calls / SampledCalls.
struct FClassifierStats
{
uint64 Calls = 0;
uint64 SampledCalls = 0;
uint64 RefineNodes = 0;
uint64 StackBoxCalls = 0;
uint64 WholeMixedNodes = 0;
uint64 WholeSolidNodes = 0;
uint64 WholeAirNodes = 0;
uint64 NeedsFinalFieldNodes = 0;
uint64 SplitNodes = 0;
uint64 MaxRefinementDepth = 0;
uint64 ExactCoreSamples = 0;
uint64 ExactFinalSamples = 0;
uint64 ExactCoreCacheHits = 0;
uint64 ExactFinalCacheHits = 0;
uint64 ExactCoreLeaves = 0;
uint64 ExactFinalLeaves = 0;
uint64 StackBoxCycles = 0;
uint64 ExactCoreCycles = 0;
uint64 ExactFinalCycles = 0;
uint64 RoomTailQueries = 0;
uint64 RoomTailEvaluated = 0;
uint64 RoomTailCycles = 0;
uint64 RoomPropagateCycles = 0;
uint64 RoomExactPrimitiveCycles = 0;
uint64 RoomCacheWindowCycles = 0;
uint64 RoomNumRooms = 0;
uint64 RoomNumTunnels = 0;
uint64 RoomNumRoomFloorJoins = 0;
uint64 RoomNumPits = 0;
uint64 RoomNumChimneys = 0;
};
FClassifierStats Classifier;
uint64 TimerPairCycles = 0;
uint32 SampleInterval = DefaultSampleInterval;
EMode Mode = EMode::Disabled;
// Opt-in worker-cache footprint snapshot. These are current per-thread values (not
// allocation-event counters), so Snapshot() reports the resident cache footprint at the
// end of a profiling run rather than multiplying bytes by cache churn.
uint64 TunnelCacheWorkers = 0;
uint64 TunnelCacheCapacityEntries = 0;
uint64 TunnelCacheValidEntries = 0;
uint64 TunnelCacheStaticBytes = 0;
uint64 TunnelCacheDynamicBytes = 0;
uint64 TunnelCacheEntryBytes = 0;
uint64 TunnelCacheLargestEntryBytes = 0;
uint64 TunnelCacheLargestWorkerValidEntries = 0;
uint64 TunnelCacheLargestWorkerBytes = 0;
FCacheMemoryBreakdown TunnelCacheBreakdown;
uint64 RoomGraphCacheWorkers = 0;
uint64 RoomGraphCacheCapacityEntries = 0;
uint64 RoomGraphCacheValidEntries = 0;
uint64 RoomGraphCacheStaticBytes = 0;
uint64 RoomGraphCacheDynamicBytes = 0;
uint64 RoomGraphCacheEntryBytes = 0;
uint64 RoomGraphCacheLargestEntryBytes = 0;
uint64 RoomGraphCacheLargestWorkerValidEntries = 0;
uint64 RoomGraphCacheLargestWorkerBytes = 0;
FCacheMemoryBreakdown RoomGraphCacheBreakdown;
};
VOXELFORGE_API void SetEnabled(bool bEnabled);
VOXELFORGE_API void SetMode(EMode Mode, uint32 SampleInterval = DefaultSampleInterval);
// Counters are collected only while a diagnostic mode is enabled. In the ordinary clean
// path this is a single relaxed load, so the measurement hooks stay dormant.
FORCEINLINE bool IsEnabledFast()
{
return GEnabled.load(std::memory_order_relaxed);
}
FORCEINLINE bool AreCountersEnabled() { return IsEnabledFast(); }
FORCEINLINE bool ShouldProbeScopeFast(EBucket Bucket)
{
const EMode Mode = GMode.load(std::memory_order_relaxed);
if (Mode == EMode::Full)
{
return true;
}
if (Mode == EMode::Sampled)
{
return Bucket == EBucket::GetDensityAt
|| Bucket == EBucket::MesherGenerateMesh
|| Bucket == EBucket::MesherDensityGrid
|| Bucket == EBucket::MesherOther;
}
if (Mode == EMode::Attribution)
{
switch (Bucket)
{
case EBucket::GetDensityAt:
case EBucket::MesherGenerateMesh:
case EBucket::MesherDensityGrid:
case EBucket::MesherCellClassification:
case EBucket::MesherGradientNormals:
case EBucket::MesherVertexInterpolation:
case EBucket::MesherStreamBuilding:
case EBucket::MesherOther:
case EBucket::RoomGraphBuild:
return true;
default:
return GSampledScopeActive;
}
}
return false;
}
VOXELFORGE_API bool IsEnabled();
VOXELFORGE_API bool IsCycleTimingEnabled();
VOXELFORGE_API EMode GetMode();
VOXELFORGE_API uint32 GetSampleInterval();
VOXELFORGE_API uint64 GetTimerPairCycles();
VOXELFORGE_API void CalibrateTimer();
VOXELFORGE_API void Reset();
VOXELFORGE_API FSnapshot Snapshot();
// Worker-local snapshot for a tile profile. GenerateTileResult is single-owner on its worker,
// so this avoids attributing another worker's concurrent tile to the current tile diagnostic.
VOXELFORGE_API FSnapshot SnapshotCurrentThread();
VOXELFORGE_API void AddCounter(ECounter Counter, uint64 Amount = 1);
VOXELFORGE_API void AddMeasurement(EBucket Bucket, uint64 Cycles, uint64 Calls = 1);
VOXELFORGE_API void RecordCall(EBucket Bucket);
VOXELFORGE_API void RecordClassifierCall(bool bSampled);
VOXELFORGE_API void AddClassifierStats(const FSnapshot::FClassifierStats& Stats);
VOXELFORGE_API bool ShouldSample(EBucket Bucket);
VOXELFORGE_API void AddSampledMeasurement(EBucket Bucket, uint64 RawCycles);
VOXELFORGE_API void SetWorkerTunnelCacheFootprint(
uint64 CapacityEntries, uint64 ValidEntries,
uint64 StaticBytes, uint64 DynamicBytes,
uint64 EntryBytes, uint64 LargestEntryBytes,
uint64 LargestWorkerValidEntries,
const FCacheMemoryBreakdown& Breakdown);
VOXELFORGE_API void SetWorkerRoomGraphCacheFootprint(
uint64 CapacityEntries, uint64 ValidEntries,
uint64 StaticBytes, uint64 DynamicBytes,
uint64 EntryBytes, uint64 LargestEntryBytes,
uint64 LargestWorkerValidEntries,
const FCacheMemoryBreakdown& Breakdown);
VOXELFORGE_API const TCHAR* CounterName(ECounter Counter);
VOXELFORGE_API EBucket BucketFromName(const TCHAR* Name);
VOXELFORGE_API const TCHAR* BucketName(EBucket Bucket);
struct FScopeToken
{
bool bEntered;
bool bSampled;
bool bBlockTiming;
bool bPreviousSampled;
bool bPreviousSampledScopeActive;
uint64 StartCycles;
};
VOXELFORGE_API FScopeToken BeginScope(EBucket Bucket);
VOXELFORGE_API void EndScope(EBucket Bucket, const FScopeToken& Token);
class FScopedTimer final
{
public:
explicit FScopedTimer(EBucket InBucket)
{
if (IsEnabledFast() && ShouldProbeScopeFast(InBucket))
{
Bucket = InBucket;
Token = BeginScope(InBucket);
bActive = Token.bEntered;
}
}
explicit FScopedTimer(const TCHAR* InName)
{
if (IsEnabledFast())
{
Bucket = BucketFromName(InName);
if (ShouldProbeScopeFast(Bucket))
{
Token = BeginScope(Bucket);
bActive = Token.bEntered;
}
}
}
FORCEINLINE ~FScopedTimer()
{
End();
}
// End a range before its lexical scope ends. This lets GetDensityAt split its enclosing
// total into disjoint prologue/core/post phases without moving the large cache block.
FORCEINLINE void End()
{
if (!bActive)
{
return;
}
EndScope(Bucket, Token);
Token.bEntered = false;
bActive = false;
}
FScopedTimer(const FScopedTimer&) = delete;
FScopedTimer& operator=(const FScopedTimer&) = delete;
private:
EBucket Bucket;
FScopeToken Token;
bool bActive = false;
};
}