perf(generation): per-chunk routing cache -- ~4% less worker CPU, exact
GetDensityAt asked the strate manager on every sample whether the chunk is a gap chunk, which generator type it uses, and whether it uses the operator stack. All three are constant per chunk. voxel.ChunkRoutingCache (default 1) memoises them per worker thread (Codex; reviewed by me): - thread_local storage only, so no cross-thread sharing; - key = generator owner id + manager pointer + manager lifetime id + layout version + full chunk coord, the same identity pattern as the existing CP_* refetch; - the editor composer override is still queried in the CP_* refetch, not cached here. Attribution for the re-rank's ~28% shared cost (offline-symbolised sampler, exclusive): GetDensityWithParams 16.4%, GetDensityAt 6.2%, unknown 5.8%, FChunkSDFSpatialIndex::GetRange 3.8%, FScopedTimer::End 1.3%, VF_ForEachSpatialCandidate 1.2%, UsesOperatorStackForChunk 1.0%. The tunnel-cache lookup was already near-perfect (48 misses in 2,248,091 samples). A thread-local timer cache was tried and rejected as slower (+0.4% static, +1.2% moving). Spatial-iteration and mesher-loop changes found no isolated exact win. Routing A/B: static worker 28.50 -> 27.78 s (-2.5%), moving 63.01 -> 59.81 s (-5.1%). Final DEFAULT build vs the committed-tip DLL (two interleaved runs each, same command line): static worker 28.79 -> 27.58 s (-4.2%) LOD0 p50/p95 52.3/94.8 -> 50.1/92.6 ms moving worker 62.25 -> 59.97 s (-3.7%) LOD0 p50/p95 51.4/95.8 -> 48.8/92.3 ms Field exact: OBJ SHA-256 b3e5f4c3..9b377 on both builds, 0 per-tile differences, capability 20,830 / 10,909; TilePostReachProof, sealed-proof and strate-crossing tests pass. Ledger: every launch was serialized with 0 editors before it. Two attribution attempts were rejected on harness bookkeeping/output-path bugs before a clean run; one crossing run was invalid (profiling left on, exit -1) and its retry passed. No crash folders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
c7b832db4c
commit
410788f7dd
1 file changed
+79
-8
@@ -194,6 +194,29 @@ namespace
|
||||
GVoxelForgeSpatialIndex = FMath::Clamp(GVoxelForgeSpatialIndex, -1, 1);
|
||||
}
|
||||
|
||||
int32 GVoxelForgeChunkRoutingCache = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeChunkRoutingCache(
|
||||
TEXT("voxel.ChunkRoutingCache"),
|
||||
GVoxelForgeChunkRoutingCache,
|
||||
TEXT("Memoize chunk-Z gap/generator/operator-stack routing for density samples."));
|
||||
bool GVoxelForgeChunkRoutingCacheSwitchParsed = false;
|
||||
|
||||
void VF_ParseChunkRoutingCacheSwitch()
|
||||
{
|
||||
if (GVoxelForgeChunkRoutingCacheSwitchParsed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
GVoxelForgeChunkRoutingCacheSwitchParsed = true;
|
||||
int32 CommandLineValue = GVoxelForgeChunkRoutingCache;
|
||||
if (FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.ChunkRoutingCache="), CommandLineValue))
|
||||
{
|
||||
GVoxelForgeChunkRoutingCache = CommandLineValue;
|
||||
}
|
||||
GVoxelForgeChunkRoutingCache = GVoxelForgeChunkRoutingCache != 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
@@ -2797,6 +2820,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
VF_ParseFastIsFiniteSwitch();
|
||||
VF_ParseFusedEvaluatorSwitch();
|
||||
VF_ParseWormBlockSkipSwitch();
|
||||
VF_ParseChunkRoutingCacheSwitch();
|
||||
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
||||
VoxelDensityProfile::EBucket::GetDensityAt);
|
||||
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
||||
@@ -2815,7 +2839,45 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
FMath::FloorToInt(WorldZ / CHUNK_SIZE)
|
||||
);
|
||||
|
||||
const bool bIsGapChunk = StrateManager && StrateManager->IsGapChunk(ChunkCoord);
|
||||
const bool bUseChunkRoutingCache = GVoxelForgeChunkRoutingCache != 0;
|
||||
thread_local uint64 CP_RouteGeneratorOwnerId = 0;
|
||||
thread_local const UVoxelStrateManager* CP_RouteManager = nullptr;
|
||||
thread_local uint64 CP_RouteManagerLifetimeId = 0;
|
||||
thread_local uint32 CP_RouteLayoutVersion = 0xFFFFFFFFu;
|
||||
thread_local FIntVector CP_RouteChunk(INT32_MAX, INT32_MAX, INT32_MAX);
|
||||
thread_local bool CP_RouteIsGapChunk = false;
|
||||
thread_local ECaveGeneratorType CP_RouteGeneratorType = ECaveGeneratorType::TunnelNetwork;
|
||||
thread_local bool CP_RouteUsesOperatorStack = false;
|
||||
uint32 RouteLayoutVersion = 0;
|
||||
uint64 RouteManagerLifetimeId = 0;
|
||||
bool bIsGapChunk = false;
|
||||
if (StrateManager && bUseChunkRoutingCache)
|
||||
{
|
||||
RouteLayoutVersion = StrateManager->GetLayoutVersion();
|
||||
RouteManagerLifetimeId = StrateManager->GetCacheLifetimeId();
|
||||
const bool bRouteKeyChanged = CP_RouteGeneratorOwnerId != DensityCacheOwnerId
|
||||
|| CP_RouteManager != StrateManager
|
||||
|| CP_RouteManagerLifetimeId != RouteManagerLifetimeId
|
||||
|| CP_RouteLayoutVersion != RouteLayoutVersion
|
||||
|| CP_RouteChunk != ChunkCoord;
|
||||
if (bRouteKeyChanged)
|
||||
{
|
||||
CP_RouteGeneratorOwnerId = DensityCacheOwnerId;
|
||||
CP_RouteManager = StrateManager;
|
||||
CP_RouteManagerLifetimeId = RouteManagerLifetimeId;
|
||||
CP_RouteLayoutVersion = RouteLayoutVersion;
|
||||
CP_RouteChunk = ChunkCoord;
|
||||
CP_RouteIsGapChunk = StrateManager->IsGapChunk(ChunkCoord);
|
||||
CP_RouteGeneratorType = StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
||||
CP_RouteUsesOperatorStack =
|
||||
StrateManager->UsesOperatorStackForChunk(ChunkCoord);
|
||||
}
|
||||
bIsGapChunk = CP_RouteIsGapChunk;
|
||||
}
|
||||
else if (StrateManager)
|
||||
{
|
||||
bIsGapChunk = StrateManager->IsGapChunk(ChunkCoord);
|
||||
}
|
||||
|
||||
if (StrateManager && bIsGapChunk)
|
||||
{
|
||||
@@ -2908,20 +2970,25 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
thread_local FVoxelStrateRegionManifest CP_ComposerRegions;
|
||||
#endif
|
||||
|
||||
const uint32 LayoutVersion = StrateManager->GetLayoutVersion();
|
||||
const uint64 ManagerLifetimeId = StrateManager->GetCacheLifetimeId();
|
||||
const uint32 LayoutVersion = bUseChunkRoutingCache
|
||||
? RouteLayoutVersion : StrateManager->GetLayoutVersion();
|
||||
const uint64 ManagerLifetimeId = bUseChunkRoutingCache
|
||||
? RouteManagerLifetimeId : StrateManager->GetCacheLifetimeId();
|
||||
// TunnelNetwork/Underwater operator-stack parameters are selected from chunk Z only.
|
||||
// A coarse tile samples thousands of exact XY chunk keys, but those keys describe the
|
||||
// same immutable stack whenever the selected archetype is operator-stack backed. Share
|
||||
// that prepared state by Z; the stack still queries the actual world position, and its
|
||||
// room-graph cache remains keyed by the real XY search window. Legacy tunnel states keep
|
||||
// the full XYZ key because their native cache is built for one XY window.
|
||||
const ECaveGeneratorType QueryGeneratorType =
|
||||
StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
||||
const ECaveGeneratorType QueryGeneratorType = bUseChunkRoutingCache
|
||||
? CP_RouteGeneratorType
|
||||
: StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
||||
const bool bShareTunnelStackByZ = !bIsGapChunk
|
||||
&& (QueryGeneratorType == ECaveGeneratorType::TunnelNetwork
|
||||
|| QueryGeneratorType == ECaveGeneratorType::Underwater)
|
||||
&& StrateManager->UsesOperatorStackForChunk(ChunkCoord);
|
||||
&& (bUseChunkRoutingCache
|
||||
? CP_RouteUsesOperatorStack
|
||||
: StrateManager->UsesOperatorStackForChunk(ChunkCoord));
|
||||
const FIntVector TunnelCacheKey = bShareTunnelStackByZ
|
||||
? FIntVector(0, 0, ChunkCoord.Z) : ChunkCoord;
|
||||
FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
|
||||
@@ -3052,7 +3119,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
CP_Version = LayoutVersion;
|
||||
CP_Chunk = ChunkCoord;
|
||||
CP_UsesTileCacheWindow = bTileCacheContext;
|
||||
CP_GenType = StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
||||
CP_GenType = bUseChunkRoutingCache
|
||||
? CP_RouteGeneratorType
|
||||
: StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
||||
CP_UseCustomRecipe = false;
|
||||
bool bAllowCustomRecipe = true;
|
||||
#if WITH_EDITOR
|
||||
@@ -3106,7 +3175,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
// ── OPSTACK Phase 1 : (re)construire la pile si cette strate l'a demandée. ──
|
||||
// Une seule branche ajoutée au chemin densité, et elle est FROIDE : la construction est
|
||||
// par chunk (comme le refetch de params juste au-dessus), jamais par voxel.
|
||||
CP_UseOpStack = StrateManager->UsesOperatorStackForChunk(ChunkCoord);
|
||||
CP_UseOpStack = bUseChunkRoutingCache
|
||||
? CP_RouteUsesOperatorStack
|
||||
: StrateManager->UsesOperatorStackForChunk(ChunkCoord);
|
||||
CP_UseOpStack = CP_UseOpStack || CP_UseCustomRecipe;
|
||||
#if WITH_EDITOR
|
||||
// A composer override is already an explicit, validated stack description. It must
|
||||
|
||||
Reference in new issue
Block a user