wip(assembler): round S14 -- 4-way column cache + memoized MC gradients (output unchanged)

Worker-local XY column cache uses deterministic 4-way sets at LOD3-8: LOD4
misses 8,224 -> 1,437, conflicts 7,407 -> 0 (direct-map control: 6.62 s /
43.5 worker-s vs 4.24 s / 21.7). Exact LOD0 lattice gradients memoized per
tile: MC-exclusive 242 -> 201 ns/sample, two-pass loop unchanged. Hashes
identical, no recipe bump. Primary cold trace 4.24 s full set, ring 0.45 s,
75/75 at 40 m/s; a repeat ran uniformly ~40% slower on every LOD (machine
contention suspected); tick p95 to re-check on an idle machine. CVars
voxel.CoreColumnCacheDirectMapped / voxel.CoreMCGradientMemoization.
Report: Standalone/Research/WorldAssemblerM3/Round18.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-19 17:58:54 +02:00
1 parent bd5e38594f
commit ec2afe9c93
11 files changed
+769 -50

No files matched your search

@@ -123,6 +123,7 @@ namespace VoxelForgeCore
std::uint64_t unclampedFieldCalls = 0;
std::uint64_t columnCacheHits = 0;
std::uint64_t columnCacheMisses = 0;
std::uint64_t columnCacheConflictMisses = 0;
double classificationMilliseconds = 0.0;
double columnComputeMilliseconds = 0.0;
double columnEvaluationExclusiveMilliseconds = 0.0;
@@ -131,6 +132,15 @@ namespace VoxelForgeCore
double capRefinementColumnComputeMilliseconds = 0.0;
double classificationExclusiveMilliseconds = 0.0;
double marchingCubesExclusiveMilliseconds = 0.0;
double marchingCubesDensityGridMilliseconds = 0.0;
double marchingCubesCellClassificationMilliseconds = 0.0;
double marchingCubesGradientNormalsMilliseconds = 0.0;
double marchingCubesEdgeInterpolationMilliseconds = 0.0;
double marchingCubesVertexDedupStreamMilliseconds = 0.0;
double marchingCubesOtherMilliseconds = 0.0;
std::uint64_t marchingCubesGradientFallbackDensityCalls = 0;
std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0;
std::uint64_t marchingCubesGradientMemoHits = 0;
double groundRefinementExclusiveMilliseconds = 0.0;
double capRefinementExclusiveMilliseconds = 0.0;
double meshAssemblyMilliseconds = 0.0;
@@ -639,6 +649,7 @@ namespace VoxelForgeCore
std::uint64_t pointCalls = 0;
std::uint64_t columnHits = 0;
std::uint64_t columnMisses = 0;
std::uint64_t columnConflictMisses = 0;
double columnComputeMilliseconds = 0.0;
bool captureCoordinates = false;
bool coordinateCaptureComplete = true;
@@ -693,6 +704,7 @@ namespace VoxelForgeCore
std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept;
static void resetThreadColumnCacheStats(bool captureCoordinates = false,
bool clearCache = false, std::uint32_t octaveDrop = 0) noexcept;
static void setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept;
static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept;
FieldInterval interval(std::int64_t minimumXSubvoxels,
std::int64_t minimumYSubvoxels,
@@ -1021,6 +1033,15 @@ namespace VoxelForgeCore
// Opt-in diagnostic only; records XY columns evaluated by this LOD8 tile.
// Diagnostic seulement; enregistre les colonnes XY evaluees par cette tuile LOD8.
bool captureColumnCoordinates = false;
// Separate cold pass only; samples the MC inner stages without changing output.
// Passe froide separee seulement; mesure les etapes internes MC sans changer la sortie.
bool profileMesherStages = false;
// Negative control for the shared XY-column cache; 1 restores the old direct-map indexing.
// Controle negatif du cache XY partage; 1 restaure l'ancien indexage direct.
bool legacyDirectMappedColumnCache = false;
// Diagnostic/performance control; memoizes exact MC lattice gradients at LOD0.
// Controle de mesure/performance; memorise les gradients MC exacts au LOD0.
bool memoizeMcGradients = true;
// LOD8-only planar horizon interior grid; zero follows the MC step, 128 restores S9.
// Grille interieure LOD8 seulement; zero suit la maille MC, 128 restaure S9.
std::int32_t lod8HorizonInteriorStepVoxels = 0;
+29 -1
View File
@@ -1325,6 +1325,11 @@ namespace VoxelForgeCore
std::optional<TileMesh> World::generateTile(const TileKey& key,const TileGenerationOptions& options,
std::string& error) const
{
// Fine owner tiles had zero direct-map conflict evictions in the cold audit. Keep their
// single-probe path and use associativity for the coarse coordinate families that collide.
// Les tuiles proches n'avaient aucun conflit; garder leur acces simple et associer les LOD grossiers.
Heightfield2D::setThreadColumnCacheLegacyDirectMapped(
options.legacyDirectMappedColumnCache||key.lod<=2);
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt())
{ error="world must be compiled before generating tiles";return std::nullopt; }
const auto& world=*impl_->owner;
@@ -1342,6 +1347,7 @@ namespace VoxelForgeCore
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
mesher.bStandaloneEnableGradientMemoization=options.memoizeMcGradients;
mesher.bGenerateSkirts=world.bGenerateSkirts;
mesher.SkirtCells=static_cast<float>(world.SkirtCells)
*static_cast<float>(world.OwnerVoxelSizeMicrons)
@@ -1436,6 +1442,7 @@ namespace VoxelForgeCore
}
}
int64 sampleCount=0;std::vector<float> densityValues;
FVoxelMarchingCubesStageTimings McStageTimings;
TileMesh out;out.key=key;
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
@@ -1523,6 +1530,7 @@ namespace VoxelForgeCore
out.diagnostics.unclampedFieldCalls=Counts.NativeUnclampedFieldCalls;
out.diagnostics.columnCacheHits=Columns.columnHits;
out.diagnostics.columnCacheMisses=Columns.columnMisses;
out.diagnostics.columnCacheConflictMisses=Columns.columnConflictMisses;
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
Counts.Milliseconds-classificationColumnComputeMilliseconds);
@@ -1670,7 +1678,8 @@ namespace VoxelForgeCore
const auto MarchingCubesStart=std::chrono::steady_clock::now();
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
options.profileMesherStages?&McStageTimings:nullptr);
const double MarchingCubesWallMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MarchingCubesStart).count();
const double ColumnMillisecondsAfterMeshing=
@@ -1679,6 +1688,24 @@ namespace VoxelForgeCore
MarchingCubesWallMilliseconds-(ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing));
out.diagnostics.marchingCubesColumnComputeMilliseconds=std::max(0.0,
ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing);
if(options.profileMesherStages)
{
out.diagnostics.marchingCubesDensityGridMilliseconds=McStageTimings.DensityGridMilliseconds;
out.diagnostics.marchingCubesCellClassificationMilliseconds=
McStageTimings.CellClassificationMilliseconds;
out.diagnostics.marchingCubesGradientNormalsMilliseconds=
McStageTimings.GradientNormalsMilliseconds;
out.diagnostics.marchingCubesEdgeInterpolationMilliseconds=
McStageTimings.EdgeInterpolationMilliseconds;
out.diagnostics.marchingCubesVertexDedupStreamMilliseconds=
McStageTimings.VertexDedupStreamMilliseconds;
out.diagnostics.marchingCubesOtherMilliseconds=McStageTimings.OtherMilliseconds;
out.diagnostics.marchingCubesGradientFallbackDensityCalls=
McStageTimings.GradientFallbackDensityCalls;
out.diagnostics.marchingCubesEdgeMidpointDensityCalls=
McStageTimings.EdgeMidpointDensityCalls;
out.diagnostics.marchingCubesGradientMemoHits=McStageTimings.GradientMemoHits;
}
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count();
out.diagnostics.farHorizonVersion=key.lod>=4?options.farHorizonVersion:0;
@@ -1765,6 +1792,7 @@ namespace VoxelForgeCore
out.diagnostics.unclampedFieldCalls=Counts.NativeUnclampedFieldCalls;
out.diagnostics.columnCacheHits=Columns.columnHits;
out.diagnostics.columnCacheMisses=Columns.columnMisses;
out.diagnostics.columnCacheConflictMisses=Columns.columnConflictMisses;
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
+79 -14
View File
@@ -1361,11 +1361,35 @@ namespace world_assembly_detail
std::int64_t x = 0, y = 0;
HeightfieldPointSample column{};
};
// Coarse LOD tiles revisit a wider set of gradient-sample columns than the full-resolution
// band. Keep those columns resident for a complete worker tile so the 3D mesher does not
// repeat the procedural biome evaluation for every Z sample.
// Keep 65,536 entries in each worker cache; four ways tolerate coordinate families that
// deliberately share low hash bits at coarse LODs.
// Chaque cache worker garde 65 536 entrees; quatre voies absorbent les coordonnees grossieres
// qui partagent les bits faibles du hash.
constexpr std::size_t HeightfieldColumnCacheWays=4;
constexpr std::size_t HeightfieldColumnCacheSetCount=65536/HeightfieldColumnCacheWays;
inline thread_local std::array<HeightfieldColumnCacheEntry, 65536> HeightfieldColumnCache{};
inline thread_local std::array<std::uint8_t, HeightfieldColumnCacheSetCount>
HeightfieldColumnCacheNextWay{};
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false;
inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y) noexcept
{
// Avalanche every coordinate bit before masking. The old direct hash kept too little
// entropy from widely spaced coarse LOD coordinates in its low 16 bits.
// Melanger tous les bits de coordonnee avant le masque; l'ancien hash perdait de
// l'entropie sur les coordonnees espacees des LOD grossiers.
std::uint64_t mixed=static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,29)
^recipe.contentHash^std::rotl(plan.contentHash,17);
mixed^=mixed>>30;
mixed*=0xBF58476D1CE4E5B9ull;
mixed^=mixed>>27;
mixed*=0x94D049BB133111EBull;
mixed^=mixed>>31;
return static_cast<std::size_t>(mixed)&(HeightfieldColumnCacheSetCount-1);
}
inline HeightfieldPointSample EvaluateColumn(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
@@ -1480,15 +1504,48 @@ namespace world_assembly_detail
inline HeightfieldPointSample CachedColumn(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
{
const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull)
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23)
^static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&recipe))
^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41);
auto& entry=HeightfieldColumnCache[static_cast<std::size_t>(mixed)&
(HeightfieldColumnCache.size()-1)];
if(entry.recipe==&recipe&&entry.plan==&plan&&entry.recipeHash==recipe.contentHash
&&entry.planHash==plan.contentHash&&entry.x==x&&entry.y==y)
{++HeightfieldColumnStats.columnHits;return entry.column;}
HeightfieldColumnCacheEntry* replacement=nullptr;
if(HeightfieldColumnCacheLegacyDirectMapped)
{
// Preserve S13's exact direct-map index as an in-process negative control.
// Conserver l'index direct exact de S13 comme controle negatif en cours d'execution.
const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull)
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23)
^static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&recipe))
^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41);
replacement=&HeightfieldColumnCache[static_cast<std::size_t>(mixed)
&(HeightfieldColumnCache.size()-1)];
if(replacement->recipe==&recipe&&replacement->plan==&plan
&&replacement->recipeHash==recipe.contentHash
&&replacement->planHash==plan.contentHash&&replacement->x==x&&replacement->y==y)
{++HeightfieldColumnStats.columnHits;return replacement->column;}
if(replacement->recipe!=nullptr)++HeightfieldColumnStats.columnConflictMisses;
}
else
{
const std::size_t set=HeightfieldColumnCacheSet(recipe,plan,x,y);
const std::size_t first=set*HeightfieldColumnCacheWays;
std::size_t replacementWay=HeightfieldColumnCacheWays;
for(std::size_t way=0;way<HeightfieldColumnCacheWays;++way)
{
auto& candidate=HeightfieldColumnCache[first+way];
if(candidate.recipe==&recipe&&candidate.plan==&plan
&&candidate.recipeHash==recipe.contentHash&&candidate.planHash==plan.contentHash
&&candidate.x==x&&candidate.y==y)
{++HeightfieldColumnStats.columnHits;return candidate.column;}
if(candidate.recipe==nullptr&&replacementWay==HeightfieldColumnCacheWays)
replacementWay=way;
}
if(replacementWay==HeightfieldColumnCacheWays)
{
++HeightfieldColumnStats.columnConflictMisses;
replacementWay=HeightfieldColumnCacheNextWay[set];
}
HeightfieldColumnCacheNextWay[set]=static_cast<std::uint8_t>(
(replacementWay+1)%HeightfieldColumnCacheWays);
replacement=&HeightfieldColumnCache[first+replacementWay];
}
auto& entry=*replacement;
const auto start=std::chrono::steady_clock::now();
HeightfieldPointSample column=EvaluateColumn(recipe,plan,x,y);
HeightfieldColumnStats.columnComputeMilliseconds+=std::chrono::duration<double,std::milli>(
@@ -1977,19 +2034,27 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
{
// Diagnostic probes may clear old tile entries before comparing isolated sample grids.
// Les sondes de mesure peuvent vider les entrees d'une tuile avant de comparer deux grilles.
if(clearCache)world_assembly_detail::HeightfieldColumnCache.fill(
world_assembly_detail::HeightfieldColumnCacheEntry{});
if(clearCache)
{
world_assembly_detail::HeightfieldColumnCache.fill(
world_assembly_detail::HeightfieldColumnCacheEntry{});
world_assembly_detail::HeightfieldColumnCacheNextWay.fill(0);
}
auto& Stats=world_assembly_detail::HeightfieldColumnStats;
world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop;
Stats.pointCalls=0;
Stats.columnHits=0;
Stats.columnMisses=0;
Stats.columnConflictMisses=0;
Stats.columnComputeMilliseconds=0.0;
Stats.captureCoordinates=captureCoordinates;
Stats.coordinateCaptureComplete=true;
Stats.evaluatedCoordinates.clear();
}
void Heightfield2D::setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept
{ world_assembly_detail::HeightfieldColumnCacheLegacyDirectMapped=enabled; }
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
{ return world_assembly_detail::HeightfieldColumnStats; }