perf(core): tile-drain instrumentation; parallel FI sign-edge repair, FI first tile 1.28 -> 0.85 s (L55); R24
Opt-in --instrument-tile-drain records per-tile queue/dispatch/finish timelines, stage spans and counters (proof skips, X8 lanes, GradAt/midpoint memo, skirts, FI cache stats); <2% when off. Measured: SW 13.3 and FI 50.2 worker-s across LOD0-8, tails are work-saturated. FI sign-edge repair now computes in parallel and publishes in sorted serial order (exact output): sign-edge 535 -> 47 ms, FI first tile 1.28 -> 0.85 s, near ring 1.46 -> 1.04 s, whole view 6.09 -> 5.65 s. Proof block size, lane batching, SW midpoint memo and helper cap 12 measured and left off. fast/medium/full PASS, Clang and GCC. Research R24: slab + motif generator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
e35a77f676
commit
aee1487410
13 files changed
+882
-74
No files matched your search
@@ -41,6 +41,7 @@ struct FStandaloneDesiredTileSet
|
||||
struct FStandaloneCpuTile
|
||||
{
|
||||
FVoxelTileKey Key;
|
||||
int32 WorkerId=-1;
|
||||
uint64 RequestedCrossing=0;
|
||||
std::vector<FStandaloneCpuVertex> Vertices;
|
||||
std::vector<uint32> Indices;
|
||||
@@ -358,6 +359,25 @@ struct FStandaloneTileJob
|
||||
std::chrono::steady_clock::time_point StartedAt{};
|
||||
};
|
||||
|
||||
struct FStandaloneCpuTileTimelineEvent
|
||||
{
|
||||
enum class EType : uint8 { Queued, Dispatch, Finish };
|
||||
EType Type=EType::Queued;
|
||||
FVoxelTileKey Key;
|
||||
int32 WorkerId=-1;
|
||||
size_t QueueDepth=0;
|
||||
size_t ActiveWorkers=0;
|
||||
size_t PriorityRank=0;
|
||||
std::chrono::steady_clock::time_point At{};
|
||||
std::chrono::steady_clock::time_point QueuedAt{};
|
||||
std::chrono::steady_clock::time_point StartedAt{};
|
||||
std::chrono::steady_clock::time_point FinishedAt{};
|
||||
double WorkerMilliseconds=-1.0;
|
||||
uint64 DensitySamples=0;
|
||||
uint32 SkirtTriangles=0;
|
||||
VoxelForgeCore::TileGenerationStats Diagnostics{};
|
||||
};
|
||||
|
||||
struct FStandaloneTileJobHeapOrder
|
||||
{
|
||||
bool operator()(const FStandaloneTileJob& A,const FStandaloneTileJob& B)const
|
||||
@@ -453,9 +473,45 @@ public:
|
||||
{
|
||||
TimelineOrigin=Origin;
|
||||
TimelineFrameOrdinal=FrameOrdinal;
|
||||
TimelineQueueEvents.clear();
|
||||
TimelineWorkerEvents.clear();
|
||||
TimelineWorkerEvents.resize(static_cast<size_t>(WorkerCount));
|
||||
bColdTimelineTrace=true;
|
||||
}
|
||||
|
||||
void DumpColdTimelineTrace() const
|
||||
{
|
||||
if(!bColdTimelineTrace)return;
|
||||
std::vector<FStandaloneCpuTileTimelineEvent> Events=TimelineQueueEvents;
|
||||
size_t Total=Events.size();
|
||||
for(const auto& WorkerEvents:TimelineWorkerEvents)Total+=WorkerEvents.size();
|
||||
Events.reserve(Total);
|
||||
for(const auto& WorkerEvents:TimelineWorkerEvents)
|
||||
Events.insert(Events.end(),WorkerEvents.begin(),WorkerEvents.end());
|
||||
std::stable_sort(Events.begin(),Events.end(),[](const auto& A,const auto& B)
|
||||
{return A.At<B.At;});
|
||||
std::printf("[CpuTileTimeline] mode=instrumented events=%zu workers=%d flush=after_run\n",
|
||||
Events.size(),WorkerCount);
|
||||
for(const auto& Event:Events)
|
||||
{
|
||||
const char* Type=Event.Type==FStandaloneCpuTileTimelineEvent::EType::Queued
|
||||
?"queued":Event.Type==FStandaloneCpuTileTimelineEvent::EType::Dispatch
|
||||
?"dispatch":"finish";
|
||||
std::printf("[CpuTileTimeline] event=%s key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu worker_id=%d queue_depth=%zu active_workers=%zu priority_rank=%zu queued_ms=%.3f dispatch_ms=%.3f finish_ms=%.3f service_ms=%.3f worker_ms=%.3f\n",
|
||||
Type,Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
|
||||
TimelineMilliseconds(Event.At),static_cast<unsigned long long>(TimelineFrame()),
|
||||
Event.WorkerId,Event.QueueDepth,Event.ActiveWorkers,Event.PriorityRank,
|
||||
TimelineMilliseconds(Event.QueuedAt),TimelineMilliseconds(Event.StartedAt),
|
||||
TimelineMilliseconds(Event.FinishedAt),
|
||||
Event.StartedAt.time_since_epoch().count()!=0
|
||||
?std::chrono::duration<double,std::milli>(Event.At-Event.StartedAt).count():-1.0,
|
||||
Event.WorkerMilliseconds);
|
||||
if(Event.Type==FStandaloneCpuTileTimelineEvent::EType::Finish)
|
||||
PrintTileStageEvent(Event);
|
||||
}
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
FStandaloneStreamingUpdate Start(const FVector& CameraPositionVoxels,
|
||||
const std::vector<FVoxelTileKey>& ResidentKeys={},
|
||||
uint64 CrossingIndex=0)
|
||||
@@ -465,7 +521,7 @@ public:
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
bStop=false;
|
||||
}
|
||||
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this](){WorkerLoop();});
|
||||
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this,I](){WorkerLoop(I);});
|
||||
bHasLastCameraPosition=false;TravelDirection=FVector::ZeroVector;
|
||||
return UpdateDesired(CameraPositionVoxels,ResidentKeys,CrossingIndex);
|
||||
}
|
||||
@@ -479,7 +535,7 @@ public:
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
bStop=false;
|
||||
}
|
||||
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this](){WorkerLoop();});
|
||||
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this,I](){WorkerLoop(I);});
|
||||
bHasLastCameraPosition=false;TravelDirection=FVector::ZeroVector;
|
||||
return UpdateDesiredSet(InitialDesired,ResidentKeys,CrossingIndex);
|
||||
}
|
||||
@@ -619,7 +675,6 @@ public:
|
||||
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
|
||||
{
|
||||
if(Desired.contains(Job.Key))return false;
|
||||
if(bColdTimelineTrace)TraceTileEvent("cancelled",Job.Key,FClock::now(),Job.QueuedAt);
|
||||
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
|
||||
++CancelledByCrossing[Job.RequestedCrossing];return true;
|
||||
});
|
||||
@@ -653,6 +708,14 @@ public:
|
||||
|
||||
void SetMesherStageProfilingEnabled(bool Enabled)
|
||||
{bProfileMesherStages=Enabled;}
|
||||
void SetTileDrainInstrumentationEnabled(bool Enabled)
|
||||
{bInstrumentTileDrain=Enabled;}
|
||||
void SetExactMidpointDensityMemoEnabled(bool Enabled)
|
||||
{bExactMidpointDensityMemo=Enabled;}
|
||||
void SetActiveLaneBatchingEnabled(bool Enabled)
|
||||
{bActiveLaneBatching=Enabled;}
|
||||
void SetProofBlockCells(int32 Cells)
|
||||
{ProofBlockCells=std::clamp(Cells,1,32);}
|
||||
void SetUniformBlockProofAuditEnabled(bool Enabled)
|
||||
{bAuditUniformBlockProofs=Enabled;}
|
||||
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
|
||||
@@ -693,7 +756,6 @@ private:
|
||||
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
|
||||
{
|
||||
if(Desired.contains(Job.Key))return false;
|
||||
if(bColdTimelineTrace)TraceTileEvent("cancelled",Job.Key,FClock::now(),Job.QueuedAt);
|
||||
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
|
||||
++CancelledByCrossing[Job.RequestedCrossing];return true;
|
||||
});
|
||||
@@ -770,7 +832,9 @@ private:
|
||||
Job.RequestedCrossing=CrossingIndex;Job.PriorityRank=PriorityRanks.at(Key);
|
||||
Job.QueuedAt=FClock::now();
|
||||
Queue.push_back(Job);
|
||||
if(bColdTimelineTrace)TraceTileEvent("queued",Key,Job.QueuedAt);
|
||||
if(bColdTimelineTrace)
|
||||
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Queued,Key,-1,
|
||||
Queue.size(),Running.size(),Job.PriorityRank,Job.QueuedAt,Job.QueuedAt);
|
||||
Outstanding.insert(Key);++Stats.NewJobs;
|
||||
}
|
||||
CurrentDesired=std::move(NewDesired);
|
||||
@@ -825,7 +889,11 @@ private:
|
||||
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
|
||||
Options.useSharedColumnCache=bSharedColumnCache;
|
||||
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
||||
Options.profileMesherStages=bProfileMesherStages&&!ForceGenerate;
|
||||
Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate;
|
||||
Options.instrumentTileDrain=bInstrumentTileDrain&&!ForceGenerate;
|
||||
Options.exactMidpointDensityMemo=bExactMidpointDensityMemo&&!ForceGenerate;
|
||||
Options.activeLaneBatching=bActiveLaneBatching&&!ForceGenerate;
|
||||
Options.proofBlockCells=static_cast<std::uint32_t>(ProofBlockCells);
|
||||
Options.auditUniformBlockProofs=bAuditUniformBlockProofs&&!ForceGenerate;
|
||||
Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop;
|
||||
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
|
||||
@@ -928,7 +996,7 @@ private:
|
||||
return Out;
|
||||
}
|
||||
|
||||
void WorkerLoop()
|
||||
void WorkerLoop(int32 WorkerId)
|
||||
{
|
||||
constexpr uint32 PriorityTaskBurst=3;
|
||||
uint32 ConsecutivePriorityTasks=0;
|
||||
@@ -936,6 +1004,7 @@ private:
|
||||
{
|
||||
FStandaloneTileJob Job;
|
||||
std::function<void()> PriorityTask;
|
||||
size_t DispatchQueueDepth=0,DispatchActiveWorkers=0;
|
||||
{
|
||||
std::unique_lock<std::mutex> Lock(Mutex);
|
||||
Wake.wait(Lock,[&](){return bStop||!PriorityTasks.empty()||!Queue.empty();});
|
||||
@@ -952,6 +1021,7 @@ private:
|
||||
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
|
||||
Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now();
|
||||
Running.insert(Job.Key);ConsecutivePriorityTasks=0;
|
||||
DispatchQueueDepth=Queue.size();DispatchActiveWorkers=Running.size();
|
||||
}
|
||||
}
|
||||
if(PriorityTask)
|
||||
@@ -959,14 +1029,21 @@ private:
|
||||
PriorityTask();
|
||||
continue;
|
||||
}
|
||||
if(bColdTimelineTrace)TraceTileEvent("started",Job.Key,Job.StartedAt,Job.QueuedAt,Job.StartedAt);
|
||||
if(bColdTimelineTrace)
|
||||
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Dispatch,Job.Key,
|
||||
WorkerId,DispatchQueueDepth,DispatchActiveWorkers,Job.PriorityRank,
|
||||
Job.StartedAt,Job.QueuedAt,Job.StartedAt);
|
||||
FStandaloneCpuTile Tile=Generate(Job);
|
||||
if(bColdTimelineTrace)TraceTileEvent("finished",Tile.Key,Tile.CompletedAt,
|
||||
Tile.QueuedAt,Tile.StartedAt,Tile.CompletedAt,Tile.WorkerSeconds*1000.0);
|
||||
Tile.WorkerId=WorkerId;
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
if(bStop)return;
|
||||
Running.erase(Tile.Key);
|
||||
if(bColdTimelineTrace)
|
||||
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Finish,Tile.Key,
|
||||
WorkerId,Queue.size(),Running.size(),Job.PriorityRank,Tile.CompletedAt,
|
||||
Tile.QueuedAt,Tile.StartedAt,Tile.CompletedAt,Tile.WorkerSeconds*1000.0,
|
||||
&Tile.CoreDiagnostics,Tile.DensitySamples,Tile.SkirtTriangleCount);
|
||||
Ready.push_back(std::move(Tile));
|
||||
}
|
||||
}
|
||||
@@ -982,14 +1059,65 @@ private:
|
||||
{
|
||||
return TimelineFrameOrdinal?TimelineFrameOrdinal->load(std::memory_order_relaxed):0;
|
||||
}
|
||||
void TraceTileEvent(const char* Event,const FVoxelTileKey& Key,FClock::time_point At,
|
||||
FClock::time_point Queued={},FClock::time_point Started={},FClock::time_point Finished={},
|
||||
double WorkerMs=-1.0)const
|
||||
static void PrintTileStageEvent(const FStandaloneCpuTileTimelineEvent& Event)
|
||||
{
|
||||
std::printf("[CpuTileTimeline] event=%s key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu queued_ms=%.3f started_ms=%.3f finished_ms=%.3f worker_ms=%.3f\n",
|
||||
Event,Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,TimelineMilliseconds(At),
|
||||
static_cast<unsigned long long>(TimelineFrame()),TimelineMilliseconds(Queued),
|
||||
TimelineMilliseconds(Started),TimelineMilliseconds(Finished),WorkerMs);
|
||||
const auto& D=Event.Diagnostics;
|
||||
std::printf("[CpuTileStages] key=(%d,%d,%d,lod%d) worker_id=%d worker_ms=%.3f samples=%llu classify_ms=%.3f column_ms=%.3f column_exclusive_ms=%.3f mc_ms=%.3f mc_grid_ms=%.3f mc_cell_ms=%.3f mc_gradient_ms=%.3f mc_interpolation_ms=%.3f mc_dedup_ms=%.3f mc_skirt_ms=%.3f mc_other_ms=%.3f connector_ms=%.3f ground_ms=%.3f cap_ms=%.3f horizon_ms=%.3f mesh_assembly_ms=%.3f candidate_cells=%llu classified_cells=%llu surface_cells=%llu proof_solid_cells=%llu proof_air_cells=%llu proof_density_samples=%llu zband_reject_cells=%llu x8_calls=%llu x8_active_lanes=%llu grad_calls=%llu grad_memo_hits=%llu grad_fallback_calls=%llu midpoint_attempts=%llu midpoint_memo_hits=%llu skirt_edges=%llu skirt_boundary_edges=%llu skirt_density_probes=%llu skirt_triangles=%u fi_local_hit=%llu fi_local_miss=%llu fi_local_conflicts=%llu fi_shared_hit=%llu fi_shared_miss=%llu fi_shared_computes=%llu fi_shared_conflicts=%llu fi_lock_wait_us=%.3f\n",
|
||||
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
|
||||
Event.WorkerId,Event.WorkerMilliseconds,
|
||||
static_cast<unsigned long long>(Event.DensitySamples),D.classificationMilliseconds,
|
||||
D.columnComputeMilliseconds,D.columnEvaluationExclusiveMilliseconds,
|
||||
D.marchingCubesExclusiveMilliseconds+D.marchingCubesSkirtMilliseconds,
|
||||
D.marchingCubesDensityGridMilliseconds,D.marchingCubesCellClassificationMilliseconds,
|
||||
D.marchingCubesGradientNormalsMilliseconds,D.marchingCubesEdgeInterpolationMilliseconds,
|
||||
D.marchingCubesVertexDedupStreamMilliseconds,D.marchingCubesSkirtMilliseconds,
|
||||
D.marchingCubesOtherMilliseconds,D.farConnectorShellMilliseconds,
|
||||
D.groundRefinementExclusiveMilliseconds,D.capRefinementExclusiveMilliseconds,
|
||||
D.farHorizonMilliseconds,D.meshAssemblyMilliseconds,
|
||||
static_cast<unsigned long long>(D.marchingCubesCandidateCells),
|
||||
static_cast<unsigned long long>(D.marchingCubesCellClassificationCalls),
|
||||
static_cast<unsigned long long>(D.marchingCubesSurfaceCellCalls),
|
||||
static_cast<unsigned long long>(D.marchingCubesProofSkippedSolidCells),
|
||||
static_cast<unsigned long long>(D.marchingCubesProofSkippedAirCells),
|
||||
static_cast<unsigned long long>(D.marchingCubesProofSkippedDensitySamples),
|
||||
static_cast<unsigned long long>(D.marchingCubesZBandRejectedCells),
|
||||
static_cast<unsigned long long>(D.marchingCubesX8DensityCalls),
|
||||
static_cast<unsigned long long>(D.marchingCubesX8ActiveLanes),
|
||||
static_cast<unsigned long long>(D.marchingCubesGradientCalls),
|
||||
static_cast<unsigned long long>(D.marchingCubesGradientMemoHits),
|
||||
static_cast<unsigned long long>(D.marchingCubesGradientFallbackDensityCalls),
|
||||
static_cast<unsigned long long>(D.marchingCubesEdgeMidpointDensityCalls),
|
||||
static_cast<unsigned long long>(D.marchingCubesEdgeMidpointDensityMemoHits),
|
||||
static_cast<unsigned long long>(D.marchingCubesSkirtEdgeCandidates),
|
||||
static_cast<unsigned long long>(D.marchingCubesSkirtBoundaryCandidates),
|
||||
static_cast<unsigned long long>(D.marchingCubesSkirtDensityProbes),
|
||||
Event.SkirtTriangles,
|
||||
static_cast<unsigned long long>(D.islandLocalCacheHits),
|
||||
static_cast<unsigned long long>(D.islandLocalCacheMisses),
|
||||
static_cast<unsigned long long>(D.islandLocalCacheConflictReplacements),
|
||||
static_cast<unsigned long long>(D.islandSharedCacheHits),
|
||||
static_cast<unsigned long long>(D.islandSharedCacheMisses),
|
||||
static_cast<unsigned long long>(D.islandSharedCacheComputes),
|
||||
static_cast<unsigned long long>(D.islandSharedCacheConflictReplacements),
|
||||
static_cast<double>(D.islandSharedCacheLockWaitNanoseconds)/1000.0);
|
||||
}
|
||||
void RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType Type,
|
||||
const FVoxelTileKey& Key,int32 WorkerId,size_t QueueDepth,size_t ActiveWorkers,
|
||||
size_t PriorityRank,FClock::time_point At,FClock::time_point Queued={},
|
||||
FClock::time_point Started={},FClock::time_point Finished={},double WorkerMs=-1.0,
|
||||
const VoxelForgeCore::TileGenerationStats* Diagnostics=nullptr,
|
||||
uint64 DensitySamples=0,uint32 SkirtTriangles=0)
|
||||
{
|
||||
FStandaloneCpuTileTimelineEvent Event;
|
||||
Event.Type=Type;Event.Key=Key;Event.WorkerId=WorkerId;
|
||||
Event.QueueDepth=QueueDepth;Event.ActiveWorkers=ActiveWorkers;
|
||||
Event.PriorityRank=PriorityRank;Event.At=At;Event.QueuedAt=Queued;
|
||||
Event.StartedAt=Started;Event.FinishedAt=Finished;Event.WorkerMilliseconds=WorkerMs;
|
||||
Event.DensitySamples=DensitySamples;Event.SkirtTriangles=SkirtTriangles;
|
||||
if(Diagnostics)Event.Diagnostics=*Diagnostics;
|
||||
if(WorkerId>=0&&static_cast<size_t>(WorkerId)<TimelineWorkerEvents.size())
|
||||
TimelineWorkerEvents[static_cast<size_t>(WorkerId)].push_back(Event);
|
||||
else TimelineQueueEvents.push_back(Event);
|
||||
}
|
||||
FStandaloneOwnerWorld* World=nullptr;
|
||||
inline static std::atomic<bool> bCoarseFirstSchedulingEnabled{true};
|
||||
@@ -1009,6 +1137,10 @@ private:
|
||||
bool bSharedColumnCache=true;
|
||||
int32 Lod8HorizonInteriorStepVoxels=0;
|
||||
bool bProfileMesherStages=false;
|
||||
bool bInstrumentTileDrain=false;
|
||||
bool bExactMidpointDensityMemo=false;
|
||||
bool bActiveLaneBatching=false;
|
||||
int32 ProofBlockCells=8;
|
||||
bool bAuditUniformBlockProofs=false;
|
||||
std::atomic<uint64> CacheHitCount{0};
|
||||
mutable std::mutex Mutex;
|
||||
@@ -1028,4 +1160,6 @@ private:
|
||||
bool bColdTimelineTrace=false;
|
||||
FClock::time_point TimelineOrigin{};
|
||||
const std::atomic<uint64>* TimelineFrameOrdinal=nullptr;
|
||||
std::vector<FStandaloneCpuTileTimelineEvent> TimelineQueueEvents;
|
||||
std::vector<std::vector<FStandaloneCpuTileTimelineEvent>> TimelineWorkerEvents;
|
||||
};
|
||||
Reference in new issue
Block a user