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);
|
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_ParseFastIsFiniteSwitch();
|
||||||
VF_ParseFusedEvaluatorSwitch();
|
VF_ParseFusedEvaluatorSwitch();
|
||||||
VF_ParseWormBlockSkipSwitch();
|
VF_ParseWormBlockSkipSwitch();
|
||||||
|
VF_ParseChunkRoutingCacheSwitch();
|
||||||
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::GetDensityAt);
|
VoxelDensityProfile::EBucket::GetDensityAt);
|
||||||
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
||||||
@@ -2815,7 +2839,45 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
FMath::FloorToInt(WorldZ / CHUNK_SIZE)
|
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)
|
if (StrateManager && bIsGapChunk)
|
||||||
{
|
{
|
||||||
@@ -2908,20 +2970,25 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
thread_local FVoxelStrateRegionManifest CP_ComposerRegions;
|
thread_local FVoxelStrateRegionManifest CP_ComposerRegions;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const uint32 LayoutVersion = StrateManager->GetLayoutVersion();
|
const uint32 LayoutVersion = bUseChunkRoutingCache
|
||||||
const uint64 ManagerLifetimeId = StrateManager->GetCacheLifetimeId();
|
? RouteLayoutVersion : StrateManager->GetLayoutVersion();
|
||||||
|
const uint64 ManagerLifetimeId = bUseChunkRoutingCache
|
||||||
|
? RouteManagerLifetimeId : StrateManager->GetCacheLifetimeId();
|
||||||
// TunnelNetwork/Underwater operator-stack parameters are selected from chunk Z only.
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// the full XYZ key because their native cache is built for one XY window.
|
||||||
const ECaveGeneratorType QueryGeneratorType =
|
const ECaveGeneratorType QueryGeneratorType = bUseChunkRoutingCache
|
||||||
StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
? CP_RouteGeneratorType
|
||||||
|
: StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
||||||
const bool bShareTunnelStackByZ = !bIsGapChunk
|
const bool bShareTunnelStackByZ = !bIsGapChunk
|
||||||
&& (QueryGeneratorType == ECaveGeneratorType::TunnelNetwork
|
&& (QueryGeneratorType == ECaveGeneratorType::TunnelNetwork
|
||||||
|| QueryGeneratorType == ECaveGeneratorType::Underwater)
|
|| QueryGeneratorType == ECaveGeneratorType::Underwater)
|
||||||
&& StrateManager->UsesOperatorStackForChunk(ChunkCoord);
|
&& (bUseChunkRoutingCache
|
||||||
|
? CP_RouteUsesOperatorStack
|
||||||
|
: StrateManager->UsesOperatorStackForChunk(ChunkCoord));
|
||||||
const FIntVector TunnelCacheKey = bShareTunnelStackByZ
|
const FIntVector TunnelCacheKey = bShareTunnelStackByZ
|
||||||
? FIntVector(0, 0, ChunkCoord.Z) : ChunkCoord;
|
? FIntVector(0, 0, ChunkCoord.Z) : ChunkCoord;
|
||||||
FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
|
FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
|
||||||
@@ -3052,7 +3119,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
CP_Version = LayoutVersion;
|
CP_Version = LayoutVersion;
|
||||||
CP_Chunk = ChunkCoord;
|
CP_Chunk = ChunkCoord;
|
||||||
CP_UsesTileCacheWindow = bTileCacheContext;
|
CP_UsesTileCacheWindow = bTileCacheContext;
|
||||||
CP_GenType = StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
CP_GenType = bUseChunkRoutingCache
|
||||||
|
? CP_RouteGeneratorType
|
||||||
|
: StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
||||||
CP_UseCustomRecipe = false;
|
CP_UseCustomRecipe = false;
|
||||||
bool bAllowCustomRecipe = true;
|
bool bAllowCustomRecipe = true;
|
||||||
#if WITH_EDITOR
|
#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. ──
|
// ── 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
|
// 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.
|
// 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;
|
CP_UseOpStack = CP_UseOpStack || CP_UseCustomRecipe;
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
// A composer override is already an explicit, validated stack description. It must
|
// A composer override is already an explicit, validated stack description. It must
|
||||||
|
|||||||
Reference in new issue
Block a user