research: L118 passage-tile designs (hash-exact, no readiness gain; kept as patch)

Tagged the passage bucket: per-tile prepareSweptTubeBase dominates (0.47-0.92 s summed per view),
smooth-min second. Segment lower-bound pruning (-17% pairs) and deferred fallback floor work stayed
hash-exact but moved near-ready <3%. Still +17..+26% vs L109.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-30 04:39:40 +01:00
1 parent c2b8d39573
commit 6e52fb48b2
6 files changed
+1023

No files matched your search

@@ -0,0 +1,58 @@
# Linux118 — passage tile cost
## Result
The L115 output is exact: owner4 hashes match at W1/W8/W12 under Clang and GCC. Walk audits passed, and the fast tier passed with 259 checks plus 5 diagnostic-only baseline comparisons.
The near-ready target was not met. The final candidate is 17.2–25.7% slower than L109 across the eight W8/W12 cells; whole-view readiness is 9.4–13.9% slower. Both designs leave readiness close to L115.
## Field tags
One W1 probe per stratum, summed over LOD0–2 passage-feature tiles. `SmoothMinDistance` time is estimated from 1/128 sampled calls. Point time contains `CachedColumn`; cache read time is inside `CachedColumn`, so those columns overlap.
| Stratum | Feature tiles | Prepare ms | Smooth-min calls / ms est. | Point / CachedColumn ms est. | Shared reads ms est. (initial / locked) |
| --- | ---: | ---: | ---: | ---: | ---: |
| S0 | 158 | 467 | 7.14M / 137 | 404 / 226 | 24 / 4 |
| S1 | 195 | 919 | 15.60M / 289 | 0 / 0 | 0 / 0 |
| S2 | 187 | 848 | 13.93M / 249 | 0 / 0 | 0 / 0 |
| S3 | 182 | 567 | 2.62M / 49 | 0 / 0 | 0 / 0 |
The Heightfield2D/cache tags occur in S0; the other three strata do not use that path in these views.
## Designs and passage counters
Design 1 added a conservative segment-to-sample lower bound in `prepareSweptTubeBase`. It skips distance, floor, and shell work when a segment cannot improve the sample. Hashes stayed exact. It pruned 17–18% of candidate pairs, but the readiness matrix remained near L115.
Design 2 deferred floor-profile interpolation in exact fallbacks until a segment could improve the nearest route or floor result. It also stayed hash-exact, with only a small timing change. Restricting fallbacks to prepared feature spans changed hashes, so that restriction was removed.
W8 passage-feature tile totals, averaged over two profiles per stratum. L118 pairs show evaluated plus bound-pruned counts; their sum equals L115's candidate-pair total.
| Stratum | Sweep pairs: L109 → L115; L118 evaluated + pruned | Fallbacks: L109 → L115 → L118 | Visits: L109 → L115 → L118 |
| --- | ---: | ---: | ---: |
| S0 | 3.36M → 7.56M; 6.25M + 1.31M | 0.241M → 0.413M → 0.413M | 1.19M → 3.41M → 3.41M |
| S1 | 6.19M → 16.22M; 13.25M + 2.97M | 0.369M → 0.719M → 0.719M | 1.91M → 7.60M → 7.59M |
| S2 | 6.15M → 15.52M; 12.69M + 2.83M | 0.315M → 0.634M → 0.634M | 1.66M → 6.88M → 6.87M |
| S3 | 4.09M → 9.63M; 7.98M + 1.64M | 0.074M → 0.131M → 0.131M | 0.48M → 1.41M → 1.41M |
## Readiness
Five interleaved measured runs per version, stratum, and worker count, after one warm-up per version. Values are median seconds: near LOD0–2 / whole view.
| Stratum | Workers | Near L109 → L115 → L118 (Δ vs L109) | Whole L109 → L115 → L118 (Δ vs L109) |
| --- | ---: | ---: | ---: |
| S0 | 8 | 0.771 → 0.941 → 0.939 (+21.8%) | 1.731 → 1.968 → 1.938 (+12.0%) |
| S0 | 12 | 0.781 → 0.927 → 0.932 (+19.4%) | 1.716 → 1.878 → 1.897 (+10.6%) |
| S1 | 8 | 0.964 → 1.235 → 1.212 (+25.7%) | 1.886 → 2.122 → 2.088 (+10.7%) |
| S1 | 12 | 0.908 → 1.166 → 1.136 (+25.1%) | 1.754 → 2.022 → 1.997 (+13.8%) |
| S2 | 8 | 0.991 → 1.257 → 1.241 (+25.2%) | 1.863 → 2.136 → 2.121 (+13.8%) |
| S2 | 12 | 0.929 → 1.172 → 1.143 (+23.1%) | 1.736 → 1.989 → 1.976 (+13.8%) |
| S3 | 8 | 0.830 → 0.981 → 0.997 (+20.1%) | 1.726 → 1.918 → 1.912 (+10.8%) |
| S3 | 12 | 0.780 → 0.933 → 0.914 (+17.2%) | 1.625 → 1.795 → 1.778 (+9.4%) |
The timing gate recorded one-minute load 0.65 and 99.2% CPU idle. Each headless run used CPUs 0–11, the 6 GiB address-space limit, and a 1,740 s timeout. Design 1 timings are preserved in `Saved/Linux/l118/timing-design1/`; final timings are in `Saved/Linux/l118/timing/`.
## Unreal and workspace
The post-timing Linux `VFHostEditor` build passed (27/27 actions, 71.13 s) with `-NoUBA -MaxParallelActions=4`; it compiled `VFCore_Core_src_VoxelForgeCore_cpp.cpp`. Unreal ran without `prlimit`. Three existing RealtimeMesh deprecation warnings remain.
Work stayed on `wip/l111-halfpipe-spline` at `c2b8d39`. No commit, push, or stash was made. Logs and summaries are under `Saved/Linux/l118/`.
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Compare L118 owner4 hashes to L115 at W1/W8/W12 under Clang and GCC."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l118/hashes"
DATA = ROOT / "Standalone/data/owner_world.json"
BASELINE = ROOT / "Saved/Linux/l115/hashes/summary.json"
BINARIES = {
"clang": ROOT / "Saved/Linux/l118/bin/VoxelForgeCoreHeadless-l118-clang",
"gcc": ROOT / "Saved/Linux/l118/bin/VoxelForgeCoreHeadless-l118-gcc",
}
HASH = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
def run(workers: int, compiler: str) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
label = f"owner4-w{workers}-{compiler}"
log = OUT / f"{label}.log"
layout = OUT / f"{label}.layout.json"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[compiler]), str(DATA),
"--workers=" + str(workers), "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=8", "--stream-hash",
f"--resolved-layout-output={layout}",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
hashes = HASH.findall(content)
if result.returncode or len(hashes) != 12:
raise SystemExit(f"hash run failed or incomplete: {label}; exit={result.returncode} "
f"hashes={len(hashes)} log={log}")
row = {"workers": workers, "compiler": compiler, "exit_code": result.returncode,
"hashes": hashes,
"source_stamp": (STAMP.search(content).group(1) if STAMP.search(content) else None),
"elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT))}
print("[L118HashRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if not DATA.is_file() or not BASELINE.is_file() or any(not path.is_file()
for path in BINARIES.values()):
raise SystemExit("missing L118 hash input or L115 reference")
reference = json.loads(BASELINE.read_text(encoding="utf-8"))["runs"]
reference = [row for row in reference if row["dataset"] == "owner4"
and row["recipe"] == "surface-world"]
rows = [run(workers, compiler) for workers in (1, 8, 12)
for compiler in ("clang", "gcc")]
comparisons = []
for row in rows:
matches = [old for old in reference if old["workers"] == row["workers"]
and old["compiler"] == row["compiler"]]
if len(matches) != 1:
raise SystemExit(f"expected one L115 reference for W{row['workers']}/{row['compiler']}")
match = row["hashes"] == matches[0]["hashes"]
comparisons.append({"workers": row["workers"], "compiler": row["compiler"],
"matches_l115": match, "l115_log": matches[0]["log"],
"l118_log": row["log"]})
if not match:
raise SystemExit(f"L118 hash mismatch versus L115 at W{row['workers']}/{row['compiler']}")
summary = {"runs": rows, "comparisons": comparisons,
"l115_hashes_identical": len({tuple(r["hashes"]) for r in reference}) == 1,
"candidate_matches_l115_all_workers_and_compilers": True}
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Collect two W8 passage-tile counter profiles per owner stratum."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l118/passage-counters"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARY = ROOT / "Saved/Linux/l118/bin/VoxelForgeCoreHeadless-l118-clang"
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
def rows(path: Path, tag: str) -> dict[str, dict[str, str]]:
result = {}
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
if line.startswith(tag + " "):
field = dict(FIELDS.findall(line))
result[field["key"]] = field
return result
def run(strate: int, replicate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
label = f"s{strate}-w8-profile{replicate}"
log = OUT / f"{label}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARY), str(DATA),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--workers=8", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
f"--camera={camera}", "--profile-lod-work", "--profile-passage-composer",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
if result.returncode:
raise SystemExit(f"passage-counter profile failed: {log}; exit={result.returncode}")
composer = rows(log, "[PassageComposerTile]")
stages = rows(log, "[CpuTileStages]")
selected = []
for key, comp in composer.items():
lod = int(key.rsplit("lod", 1)[1].rstrip(")"))
if lod > 2 or int(comp["features"]) == 0:
continue
stage = stages.get(key)
if stage is None:
raise SystemExit(f"missing CpuTileStages record for {label} {key}")
fallbacks = [int(value) for value in stage["sweep_exact_fallback_by_caller"].split(",")]
selected.append({
"features": int(comp["features"]),
"sweep_pairs": int(comp["sweep_pairs"]),
"sweep_bound_pruned": int(comp.get("sweep_bound_pruned", "0")),
"exact_fallbacks": sum(fallbacks),
"segment_visits": int(comp["carve_segment_visits"]),
})
total = {name: sum(item[name] for item in selected)
for name in ("sweep_pairs", "sweep_bound_pruned", "exact_fallbacks", "segment_visits")}
row = {"strate": strate, "workers": 8, "replicate": replicate,
"passage_tiles_lod02": len(selected), "totals": total,
"elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT))}
print("[L118PassageCounters] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if not DATA.is_file() or not BINARY.is_file():
raise SystemExit("missing owner data or L118 Clang binary")
result = [run(strate, replicate) for strate in range(4) for replicate in (1, 2)]
summary = []
for strate in range(4):
group = [item for item in result if item["strate"] == strate]
summary.append({"strate": strate, "workers": 8, "replicates": len(group),
"passage_tiles_lod02_median": sorted(
row["passage_tiles_lod02"] for row in group)[len(group) // 2],
"totals_mean": {name: sum(row["totals"][name] for row in group) / len(group)
for name in group[0]["totals"]}})
(OUT / "summary.json").write_text(
json.dumps({"runs": result, "summary": summary}, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Collect opt-in L118 field timing tags for one W1 view per owner stratum."""
from __future__ import annotations
import json
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l118/tag-probe"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARY = ROOT / "Saved/Linux/l118/bin/VoxelForgeCoreHeadless-l118-clang"
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
TAGS = re.compile(r"^\[L118FieldTags\] (.*)$", re.M)
PASSAGE = re.compile(r"^\[PassageComposerTile\] (.*)$", re.M)
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
def run(strate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
label = f"s{strate}-w1-tags"
log = OUT / f"{label}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARY), str(DATA),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--workers=1", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
f"--camera={camera}", "--profile-lod-work", "--profile-passage-composer",
]
started = time.monotonic()
env = dict(os.environ, VOXELFORGE_L118_TAG_TIMING="1")
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, env=env, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
tags = [dict(FIELDS.findall(match)) for match in TAGS.findall(content)]
passage = [dict(FIELDS.findall(match)) for match in PASSAGE.findall(content)]
if result.returncode or not tags or not passage:
raise SystemExit(f"tag probe failed or emitted no tags: {log}; exit={result.returncode}")
feature_keys = {
row["key"] for row in passage
if int(row["key"].rsplit("lod", 1)[1].rstrip(")")) <= 2
and int(row["features"]) > 0
}
near = [row for row in tags if row["key"] in feature_keys]
totals = {
"passage_feature_tiles": len(near),
"sweep_prepare_ms": sum(float(row["sweep_prepare_ms"]) for row in near),
"smoothmin_calls": sum(int(row["smoothmin_calls"]) for row in near),
"smoothmin_ms_est": sum(float(row["smoothmin_ms_est"]) for row in near),
"point_ms_est": sum(float(row["point_ms_est"]) for row in near),
"cached_column_ms_est": sum(float(row["cached_column_ms_est"]) for row in near),
"shared_initial_read_ms_est": sum(
float(row["shared_initial_read_ms_est"]) for row in near),
"shared_locked_read_ms_est": sum(
float(row["shared_locked_read_ms_est"]) for row in near),
}
row = {"strate": strate, "workers": 1, "elapsed_s": round(time.monotonic() - started, 3),
"tagged_tiles": len(tags), "near_lod02_feature_tiles": totals,
"log": str(log.relative_to(ROOT))}
print("[L118TagProbe] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if not DATA.is_file() or not BINARY.is_file():
raise SystemExit("missing owner data or L118 Clang binary")
rows = [run(strate) for strate in range(4)]
(OUT / "summary.json").write_text(json.dumps(rows, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""Five interleaved L80 readiness runs for L109, L115, and L118."""
from __future__ import annotations
import json
import os
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l118/timing"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
"L115": ROOT / "Saved/Linux/l115/bin/VoxelForgeCoreHeadless-l115-clang",
"L118": ROOT / "Saved/Linux/l118/bin/VoxelForgeCoreHeadless-l118-clang",
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
NEAR = re.compile(
r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$",
re.M,
)
def run(strate: int, workers: int, version: str, phase: str, replicate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
label = f"s{strate}-w{workers}-{version}-{phase}{replicate}"
log = OUT / f"{label}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[version]), str(DATA),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
f"--workers={workers}", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
f"--camera={camera}", "--profile-lod-work", "--stream-scheduler-policy=3",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
pipeline_match = PIPELINE.search(content)
near_match = NEAR.search(content)
if result.returncode or pipeline_match is None or near_match is None:
raise SystemExit(f"readiness run failed or trace missing: {log}; exit={result.returncode}")
pipeline = dict(FIELDS.findall(pipeline_match.group(1)))
row = {
"strate": strate, "workers": workers, "version": version,
"phase": phase, "replicate": replicate, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - started, 3),
"lod02_ready_s": float(near_match.group(3)),
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
"log": str(log.relative_to(ROOT)),
}
print("[L118TimingRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def cpu_idle_percent(interval_s: float = 2.0) -> float:
def totals() -> tuple[int, int]:
fields = Path("/proc/stat").read_text(encoding="utf-8").splitlines()[0].split()
values = [int(value) for value in fields[1:]]
return sum(values), values[3] + (values[4] if len(values) > 4 else 0)
total_before, idle_before = totals()
time.sleep(interval_s)
total_after, idle_after = totals()
total_delta = max(1, total_after - total_before)
return 100.0 * (idle_after - idle_before) / total_delta
def main() -> int:
if not DATA.is_file() or any(not binary.is_file() for binary in BINARIES.values()):
raise SystemExit("missing L109/L115/L118 timing input")
OUT.mkdir(parents=True, exist_ok=True)
load_1m = os.getloadavg()[0]
idle_pct = cpu_idle_percent()
uptime = subprocess.run(["uptime"], cwd=ROOT, text=True, capture_output=True,
check=True).stdout.strip()
top = subprocess.run(["ps", "-eo", "pid,pcpu,comm", "--sort=-pcpu"], cwd=ROOT,
text=True, capture_output=True, check=True).stdout
(OUT / "machine-load-before.txt").write_text(
f"uptime: {uptime}\n1-minute load: {load_1m:.2f}\n"
f"2-second CPU idle: {idle_pct:.1f}%\ntop CPU users:\n{top}",
encoding="utf-8")
print(f"[L118TimingGate] load_1m={load_1m:.2f} cpu_idle_2s={idle_pct:.1f}% "
f"uptime={uptime}", flush=True)
if load_1m >= 0.9:
raise SystemExit(f"timing refused: 1-minute load {load_1m:.2f} is not below 0.9")
if idle_pct < 97.0:
raise SystemExit(f"timing refused: CPU idle {idle_pct:.1f}% is below 97%")
rows = []
versions = ("L109", "L115", "L118")
for strate in range(4):
for workers in (8, 12):
for version in versions:
run(strate, workers, version, "warmup", 0)
for replicate in range(1, 6):
offset = (replicate - 1) % len(versions)
order = versions[offset:] + versions[:offset]
for version in order:
rows.append(run(strate, workers, version, "measured", replicate))
(OUT / "runs-partial.json").write_text(
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
summary = []
for strate in range(4):
for workers in (8, 12):
item = {"strate": strate, "workers": workers, "replicates": 5}
medians = {}
for version in versions:
group = [row for row in rows if row["strate"] == strate
and row["workers"] == workers and row["version"] == version]
medians[version] = {}
for key in ("lod02_ready_s", "whole_view_ready_s"):
values = [row[key] for row in group]
medians[version][key] = statistics.median(values)
item[f"{version}_{key}_values"] = values
item[f"{version}_{key}_median"] = medians[version][key]
for comparison in ("L109", "L115"):
for key in ("lod02_ready_s", "whole_view_ready_s"):
item[f"L118_vs_{comparison}_{key}_delta_pct"] = round(
100.0 * (medians["L118"][key] / medians[comparison][key] - 1.0), 2)
summary.append(item)
(OUT / "summary.json").write_text(
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,557 @@
diff --git a/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h b/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h
index cbb0885..3784247 100644
--- a/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h
+++ b/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h
@@ -2,6 +2,7 @@
#include "VoxelForgeCore/CpuCycleCounter.h"
+#include <algorithm>
#include <array>
#include <atomic>
#include <cstddef>
@@ -24,12 +25,15 @@ namespace VoxelForgeCoreShared
std::uint64_t lockWaitNanoseconds = 0;
std::uint64_t sampledInitialReadCycles = 0;
std::uint64_t sampledInitialReads = 0;
+ std::uint64_t sampledInitialReadNanoseconds = 0;
std::uint64_t sampledLockedReadCycles = 0;
std::uint64_t sampledLockedReads = 0;
+ std::uint64_t sampledLockedReadNanoseconds = 0;
std::uint64_t sampledLockCycles = 0;
std::uint64_t sampledLocks = 0;
std::uint64_t sampledBuildCycles = 0;
std::uint64_t sampledBuilds = 0;
+ bool captureNanoseconds = false;
};
// Exact input identity for a pure XY column value. LOD is deliberately not part of this key:
@@ -140,12 +144,19 @@ namespace VoxelForgeCoreShared
static thread_local std::uint64_t timingSequence=0;
const bool sampleCycles=metrics&&((++timingSequence&127u)==0);
const std::uint64_t initialReadStart=sampleCycles?ReadCpuCycles():0;
+ const auto initialReadStartNs=sampleCycles&&metrics->captureNanoseconds
+ ?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{};
const bool initialHit=TryReadSet(set, keyWords, tag, value);
if(sampleCycles)
{
metrics->sampledInitialReadCycles+=AdjustedCpuCycleDelta(
initialReadStart,ReadCpuCycles());
++metrics->sampledInitialReads;
+ if(metrics->captureNanoseconds)
+ metrics->sampledInitialReadNanoseconds+=static_cast<std::uint64_t>(
+ std::max<std::int64_t>(0,std::chrono::duration_cast<
+ std::chrono::nanoseconds>(std::chrono::steady_clock::now()
+ -initialReadStartNs).count()));
}
if (initialHit)
{
@@ -156,12 +167,19 @@ namespace VoxelForgeCoreShared
FSpinGuard lock(set.Writer, metrics,sampleCycles);
const std::uint64_t lockedReadStart=sampleCycles?ReadCpuCycles():0;
+ const auto lockedReadStartNs=sampleCycles&&metrics->captureNanoseconds
+ ?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{};
const bool lockedHit=TryReadSet(set, keyWords, tag, value);
if(sampleCycles)
{
metrics->sampledLockedReadCycles+=AdjustedCpuCycleDelta(
lockedReadStart,ReadCpuCycles());
++metrics->sampledLockedReads;
+ if(metrics->captureNanoseconds)
+ metrics->sampledLockedReadNanoseconds+=static_cast<std::uint64_t>(
+ std::max<std::int64_t>(0,std::chrono::duration_cast<
+ std::chrono::nanoseconds>(std::chrono::steady_clock::now()
+ -lockedReadStartNs).count()));
}
if (lockedHit)
{
diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
index ac27df1..ad6e8c2 100644
--- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
+++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
@@ -147,6 +147,7 @@ namespace VoxelForgeCore
// by the opt-in passage composer profile, except the mesh composition itself.
std::uint64_t passageSweepRouteSegmentCandidates = 0;
std::uint64_t passageSweepSegmentSamplePairs = 0;
+ std::uint64_t passageSweepBoundPrunedPairs = 0;
std::uint64_t passageSweepOutsideInfluencePairs = 0;
std::uint64_t passageSweepMinimumUpdates = 0;
std::uint64_t passageSweepBaseInfluenceSamples = 0;
@@ -158,6 +159,19 @@ namespace VoxelForgeCore
std::uint64_t passageSweepBaseInfluenceHits = 0;
std::uint64_t passageSweepOutOfInfluenceHits = 0;
std::uint64_t passageSweepMissingSamples = 0;
+ double passageSweepPrepareMilliseconds = 0.0;
+ std::uint64_t passageSmoothMinCalls = 0;
+ std::uint64_t passageSmoothMinSampledNanoseconds = 0;
+ std::uint64_t passageSmoothMinSamples = 0;
+ std::uint64_t heightfieldPointSampledNanoseconds = 0;
+ std::uint64_t heightfieldPointSamples = 0;
+ std::uint64_t heightfieldCachedColumnSampledNanoseconds = 0;
+ std::uint64_t heightfieldCachedColumnSamples = 0;
+ std::uint64_t heightfieldSharedInitialReadSampledNanoseconds = 0;
+ std::uint64_t heightfieldSharedInitialReadSamples = 0;
+ std::uint64_t heightfieldSharedLockedReadSampledNanoseconds = 0;
+ std::uint64_t heightfieldSharedLockedReadSamples = 0;
+ bool l118FieldTagsCaptured = false;
std::array<std::uint64_t,12> passageSweepMissingLookupsByCaller{};
std::array<std::uint64_t,12> passageSweepExactRouteFallbacksByCaller{};
std::array<std::uint64_t,12> passageSweepFeatureBoundSkipsByCaller{};
@@ -1394,6 +1408,16 @@ namespace VoxelForgeCore
std::uint64_t sharedColumnHits = 0;
std::uint64_t columnEvaluations = 0;
double columnComputeMilliseconds = 0.0;
+ // L118 opt-in, one-in-128 sampled timings. Nanoseconds are accumulated only for
+ // sampled calls; report estimates multiply by 128.
+ std::uint64_t sampledPointNanoseconds = 0;
+ std::uint64_t sampledPointCalls = 0;
+ std::uint64_t sampledCachedColumnNanoseconds = 0;
+ std::uint64_t sampledCachedColumnCalls = 0;
+ std::uint64_t sampledSharedInitialReadNanoseconds = 0;
+ std::uint64_t sampledSharedInitialReads = 0;
+ std::uint64_t sampledSharedLockedReadNanoseconds = 0;
+ std::uint64_t sampledSharedLockedReads = 0;
bool captureCoordinates = false;
bool coordinateCaptureComplete = true;
std::vector<HeightfieldColumnEvaluation> evaluatedCoordinates;
diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h
index c266bd8..4782980 100644
--- a/Standalone/Core/src/ShapeWorldInternal.h
+++ b/Standalone/Core/src/ShapeWorldInternal.h
@@ -72,6 +72,14 @@ static bool VF_IsL98PassagePrepareTraceEnabled()
return bEnabled;
}
+static bool VF_IsL118TagTimingEnabled()
+{
+ static const bool bEnabled=VF_IsL88DiagnosticSwitchEnabled(
+ "VOXELFORGE_L118_TAG_TIMING");
+ return bEnabled;
+}
+static thread_local std::uint64_t VF_L118SmoothMinTimingSequence=0;
+
static double VF_L93PassageCarveReachMetres()
{
// The generated world uses a fixed bounded tube influence. An explicit diagnostic
@@ -289,6 +297,8 @@ public:
std::vector<FPassageSamples> passages;
std::atomic<std::uint64_t> lookups{0},cacheHits{0},baseInfluenceHits{0},
outOfInfluenceHits{0},missingSamples{0};
+ std::atomic<std::uint64_t> smoothMinCalls{0},smoothMinSampledNanoseconds{0},
+ smoothMinSamples{0};
std::array<std::atomic<std::uint64_t>,PassageDensityQueryCallerCount>
missingLookupsByCaller{},exactRouteFallbacksByCaller{},
featureBoundSkipsByCaller{},tileFreeSkipsByCaller{},
@@ -376,6 +386,17 @@ public:
const FPassageSweepSample* sweptSample(std::size_t passageIndex,
double qx,double qy,double qz) const;
bool hasSweptTubeBase() const noexcept{return passageSweepData_!=nullptr;}
+ void recordSmoothMinTiming(std::uint64_t nanoseconds,bool sampled) const noexcept
+ {
+ if(!profileStats_||!passageSweepData_)return;
+ passageSweepData_->smoothMinCalls.fetch_add(1,std::memory_order_relaxed);
+ if(sampled)
+ {
+ passageSweepData_->smoothMinSampledNanoseconds.fetch_add(
+ nanoseconds,std::memory_order_relaxed);
+ passageSweepData_->smoothMinSamples.fetch_add(1,std::memory_order_relaxed);
+ }
+ }
void copySweptTubeRuntimeCounts() const noexcept
{
if(!ownsPassageSweepDiagnostics_||!profileStats_||!passageSweepData_)return;
@@ -389,6 +410,13 @@ public:
passageSweepData_->outOfInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepMissingSamples=passageSweepData_->missingSamples.load(
std::memory_order_relaxed);
+ profileStats_->passageSmoothMinCalls=passageSweepData_->smoothMinCalls.load(
+ std::memory_order_relaxed);
+ profileStats_->passageSmoothMinSampledNanoseconds=
+ passageSweepData_->smoothMinSampledNanoseconds.load(
+ std::memory_order_relaxed);
+ profileStats_->passageSmoothMinSamples=passageSweepData_->smoothMinSamples.load(
+ std::memory_order_relaxed);
for(std::size_t caller=0;caller<PassageDensityQueryCallerCount;++caller)
{
profileStats_->passageSweepMissingLookupsByCaller[caller]=
@@ -1298,12 +1326,19 @@ public:
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double roundDistance=DeterministicSqrt(
ex*ex+ey*ey+ez*ez)-radius;
+ const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
+ const bool floorWins=floorCenterDistanceSq<bestFloorCenterDistanceSq
+ ||(floorCenterDistanceSq==bestFloorCenterDistanceSq
+ &&segmentIndex<nearest.floorSegmentIndex);
+ const bool roundWins=roundDistance<bestDistance
+ ||(roundDistance==bestDistance
+ &&(!nearest.valid||segmentIndex<nearest.segmentIndex));
+ if(!floorWins&&!roundWins)return;
const double floorT=segment.horizontalLengthSq>1.0e-12
?(((qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1])
/segment.horizontalLengthSq):t;
const double floorTClamped=std::clamp(floorT,0.0,1.0);
- const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorRadius=segment.radiusA
+floorTClamped*segment.radiusDelta;
const double floorDepth=PassageFlatFloorDepthSubvoxels(
@@ -1311,13 +1346,9 @@ public:
const double floorCenterZ=PassageFlatFloorCenterZSubvoxels(
constants,segments,segmentIndex,floorT,segment,
qx,qy,floorDepth,qPerMetre);
- const double flatDistance=PassageFlatBottomDistance(
- static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre);
// The profile uses the closest route projection's arc length. Tube
// radius selects the bore shape only; it never selects floor height.
- if(floorCenterDistanceSq<bestFloorCenterDistanceSq
- ||(floorCenterDistanceSq==bestFloorCenterDistanceSq
- &&segmentIndex<nearest.floorSegmentIndex))
+ if(floorWins)
{
bestFloorCenterDistanceSq=floorCenterDistanceSq;
nearest.floorCenterZ=floorCenterZ;
@@ -1329,10 +1360,11 @@ public:
// minimum of each segment's flattened distance lets a lower, downhill
// floor plane extend backward through the overlapping tube and makes
// the floor jump at the prepared-segment spacing.
- if(roundDistance<bestDistance
- ||(roundDistance==bestDistance
- &&(!nearest.valid||segmentIndex<nearest.segmentIndex)))
+ if(roundWins)
{
+ const double flatDistance=PassageFlatBottomDistance(
+ static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,
+ qPerMetre);
bestDistance=roundDistance;
nearest.signedDistance=flatDistance;
nearest.roundSignedDistance=roundDistance;
@@ -6929,12 +6961,31 @@ private:
static float SmoothMinDistance(float A,float B,float Width)
{
FScopedLegacySmoothMinTimer SmoothMinTimer;
+ const auto* tagScope=VF_IsL118TagTimingEnabled()
+ ?ActivePassageTileQueries:nullptr;
+ const bool tag=tagScope&&tagScope->profileStats()
+ &&tagScope->hasSweptTubeBase();
+ const bool sampled=tag&&((++VF_L118SmoothMinTimingSequence&127u)==0);
+ const auto tagStart=sampled?std::chrono::steady_clock::now()
+ :std::chrono::steady_clock::time_point{};
+ const auto recordTag=[&]()
+ {
+ if(!tag)return;
+ const std::uint64_t elapsed=sampled?static_cast<std::uint64_t>(std::max<std::int64_t>(0,
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now()-tagStart).count())):0;
+ tagScope->recordSmoothMinTiming(elapsed,sampled);
+ };
if(Width<=0.0f)
{
- return std::min(A,B);
+ const float result=std::min(A,B);
+ recordTag();
+ return result;
}
const float H=std::max(Width-std::abs(A-B),0.0f)/Width;
- return std::min(A,B)-(H*H*H)*(Width/6.0f);
+ const float result=std::min(A,B)-(H*H*H)*(Width/6.0f);
+ recordTag();
+ return result;
}
static float SmoothMaxDistance(float A,float B,float Width)
{return -SmoothMinDistance(-A,-B,Width);}
@@ -11464,6 +11515,9 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
const double wallReliefShell=0.41*qPerMetre;
const double floorDetailShell=1.0*qPerMetre;
const bool profile=profileStats_!=nullptr;
+ const bool tagTiming=profile&&VF_IsL118TagTimingEnabled();
+ const auto tagStart=tagTiming?std::chrono::steady_clock::now()
+ :std::chrono::steady_clock::time_point{};
if(profile)
{
profileStats_->passageSweepRouteSegmentCandidates=routeSegmentCandidateCount_;
@@ -11527,6 +11581,31 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
||qy>segment.sweepMaximumSubvoxels[1]
||qz<segment.sweepMinimumSubvoxels[2]
||qz>segment.sweepMaximumSubvoxels[2])continue;
+ const std::size_t sampleIndex=(static_cast<std::size_t>(gz)
+ *static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gy))
+ *static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gx);
+ auto& sample=passageSamples.samples[sampleIndex];
+ const double boundDx=std::max({std::min(segment.a[0],segment.b[0])-qx,
+ 0.0,qx-std::max(segment.a[0],segment.b[0])});
+ const double boundDy=std::max({std::min(segment.a[1],segment.b[1])-qy,
+ 0.0,qy-std::max(segment.a[1],segment.b[1])});
+ const double boundDz=std::max({std::min(segment.a[2],segment.b[2])-qz,
+ 0.0,qz-std::max(segment.a[2],segment.b[2])});
+ const double centerDistanceLowerBoundSq=boundDx*boundDx
+ +boundDy*boundDy+boundDz*boundDz;
+ const double roundDistanceLowerBound=FStandaloneShapeWorld::DeterministicSqrt(
+ centerDistanceLowerBoundSq)-std::max(segment.radiusA,segment.radiusB);
+ const bool couldImproveRound=!sample.valid
+ ||roundDistanceLowerBound<=sample.roundSignedDistance;
+ const bool couldImproveFloor=centerDistanceLowerBoundSq
+ <=sample.floorCenterDistanceSq;
+ const bool couldContributeShell=roundDistanceLowerBound<=shellThickness;
+ if(!couldImproveRound&&!couldImproveFloor&&!couldContributeShell)
+ {
+ if(profile)++profileStats_->passageSweepBoundPrunedPairs;
+ continue;
+ }
+ if(profile)++profileStats_->passageSweepSegmentSamplePairs;
const double projectionXY=(qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1];
const double t=std::clamp((projectionXY
@@ -11552,15 +11631,10 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
qx,qy,floorDepth,qPerMetre);
const double flatDistance=FStandaloneShapeWorld::PassageFlatBottomDistance(
static_cast<float>(signedDistance),floorCenterZ,qz,floorDepth,qPerMetre);
- if(profile)++profileStats_->passageSweepSegmentSamplePairs;
if(signedDistance>sweep->influenceSubvoxels)
{
if(profile)++profileStats_->passageSweepOutsideInfluencePairs;
}
- const std::size_t sampleIndex=(static_cast<std::size_t>(gz)
- *static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gy))
- *static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gx);
- auto& sample=passageSamples.samples[sampleIndex];
if(floorCenterDistanceSq<sample.floorCenterDistanceSq
||(floorCenterDistanceSq==sample.floorCenterDistanceSq
&&segmentIndex<sample.floorSegmentIndex))
@@ -11687,6 +11761,10 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
static_cast<std::uint64_t>(std::count(anyPassageInfluence.begin(),
anyPassageInfluence.end(),static_cast<std::uint8_t>(0)));
}
+ if(tagTiming)
+ profileStats_->passageSweepPrepareMilliseconds=
+ std::chrono::duration<double,std::milli>(
+ std::chrono::steady_clock::now()-tagStart).count();
passageSweepData_=std::move(sweep);
}
diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp
index b139ec4..a25049d 100644
--- a/Standalone/Core/src/VoxelForgeCore.cpp
+++ b/Standalone/Core/src/VoxelForgeCore.cpp
@@ -2444,6 +2444,28 @@ namespace VoxelForgeCore
FStandaloneShapeWorld::SetThreadDensityQueryProfileEnabled(options.instrumentTileDrain);
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(
options.captureColumnCoordinates);
+ const auto copyL118FieldTags=[&]()
+ {
+ if(!options.profilePassageComposer
+ ||!world_assembly_detail::L118FieldTagTimingEnabled())return;
+ const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
+ out.diagnostics.l118FieldTagsCaptured=true;
+ out.diagnostics.heightfieldPointSampledNanoseconds=
+ Columns.sampledPointNanoseconds;
+ out.diagnostics.heightfieldPointSamples=Columns.sampledPointCalls;
+ out.diagnostics.heightfieldCachedColumnSampledNanoseconds=
+ Columns.sampledCachedColumnNanoseconds;
+ out.diagnostics.heightfieldCachedColumnSamples=
+ Columns.sampledCachedColumnCalls;
+ out.diagnostics.heightfieldSharedInitialReadSampledNanoseconds=
+ Columns.sampledSharedInitialReadNanoseconds;
+ out.diagnostics.heightfieldSharedInitialReadSamples=
+ Columns.sampledSharedInitialReads;
+ out.diagnostics.heightfieldSharedLockedReadSampledNanoseconds=
+ Columns.sampledSharedLockedReadNanoseconds;
+ out.diagnostics.heightfieldSharedLockedReadSamples=
+ Columns.sampledSharedLockedReads;
+ };
if(options.captureColumnCoordinates)
{
constexpr std::int64_t SubvoxelsPerVoxel=WorldAssemblySubvoxelsPerVoxel;
@@ -2530,6 +2552,7 @@ namespace VoxelForgeCore
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
Counts.Milliseconds-classificationColumnComputeMilliseconds);
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
+ copyL118FieldTags();
#if defined(VF_L30_ENABLE_COST_COUNTERS)
PrintL30CostCounters(Counts);
#endif
@@ -3128,6 +3151,7 @@ namespace VoxelForgeCore
out.diagnostics.columnEvaluations=Columns.columnEvaluations;
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
+ copyL118FieldTags();
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
Counts.Milliseconds-classificationColumnComputeMilliseconds);
diff --git a/Standalone/Core/src/WorldAssemblerInternal.h b/Standalone/Core/src/WorldAssemblerInternal.h
index 07b3c9b..dd7c1d6 100644
--- a/Standalone/Core/src/WorldAssemblerInternal.h
+++ b/Standalone/Core/src/WorldAssemblerInternal.h
@@ -10,6 +10,7 @@
#include <charconv>
#include <chrono>
#include <cmath>
+#include <cstdlib>
#include <iomanip>
#include <limits>
#include <sstream>
@@ -1798,11 +1799,49 @@ namespace world_assembly_detail
inline thread_local VoxelForgeCoreThreadLocal::FLazy<
std::array<std::uint8_t, HeightfieldColumnCacheSetCount>> HeightfieldColumnCacheNextWay;
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
+ inline thread_local VoxelForgeCoreShared::FColumnCacheMetrics
+ HeightfieldSharedColumnCacheMetrics{};
+ inline thread_local std::uint64_t HeightfieldPointTimingSequence=0;
+ inline thread_local std::uint64_t HeightfieldCachedColumnTimingSequence=0;
inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false;
inline thread_local std::array<std::int64_t,4> HeightfieldColumnCaptureBounds{};
inline thread_local bool HeightfieldColumnCaptureBoundsValid=false;
inline std::atomic<std::uint64_t> HeightfieldColumnCaptureSequence{0};
+ inline bool L118FieldTagTimingEnabled() noexcept
+ {
+ static const bool enabled=[]
+ {
+ const char* value=std::getenv("VOXELFORGE_L118_TAG_TIMING");
+ return value&&value[0]=='1'&&value[1]=='\0';
+ }();
+ return enabled;
+ }
+
+ struct FScopedHeightfieldSampledTimer
+ {
+ FScopedHeightfieldSampledTimer(std::uint64_t& nanoseconds,
+ std::uint64_t& samples,std::uint64_t& sequence) noexcept
+ :Nanoseconds(nanoseconds),Samples(samples)
+ {
+ if(!L118FieldTagTimingEnabled()||((++sequence&127u)!=0))return;
+ Start=std::chrono::steady_clock::now();
+ bTimed=true;
+ }
+ ~FScopedHeightfieldSampledTimer()
+ {
+ if(!bTimed)return;
+ Nanoseconds+=static_cast<std::uint64_t>(std::max<std::int64_t>(0,
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now()-Start).count()));
+ ++Samples;
+ }
+ std::uint64_t& Nanoseconds;
+ std::uint64_t& Samples;
+ std::chrono::steady_clock::time_point Start{};
+ bool bTimed=false;
+ };
+
inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y) noexcept
{
@@ -1989,6 +2028,10 @@ namespace world_assembly_detail
inline HeightfieldPointSample CachedColumn(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
{
+ FScopedHeightfieldSampledTimer CachedColumnTimer(
+ HeightfieldColumnStats.sampledCachedColumnNanoseconds,
+ HeightfieldColumnStats.sampledCachedColumnCalls,
+ HeightfieldCachedColumnTimingSequence);
const std::int64_t InterpolationStride=HeightfieldColumnInterpolationStrideSubvoxels;
if(InterpolationStride>0)
{
@@ -2105,7 +2148,10 @@ namespace world_assembly_detail
static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),
plan.contentHash,x,y};
bool computed=false;
- column=shared->getOrCompute(key,compute,computed);
+ auto* metrics=L118FieldTagTimingEnabled()
+ ?&HeightfieldSharedColumnCacheMetrics:nullptr;
+ if(metrics)metrics->captureNanoseconds=true;
+ column=shared->getOrCompute(key,compute,computed,metrics);
if(!computed)++HeightfieldColumnStats.sharedColumnHits;
}
else column=compute();
@@ -2576,6 +2622,10 @@ WorldAssemblyFeatureTileListV1 Heightfield2D::buildFeatureTileList(
HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
std::int64_t z) const noexcept
{
+ world_assembly_detail::FScopedHeightfieldSampledTimer PointTimer(
+ world_assembly_detail::HeightfieldColumnStats.sampledPointNanoseconds,
+ world_assembly_detail::HeightfieldColumnStats.sampledPointCalls,
+ world_assembly_detail::HeightfieldPointTimingSequence);
HeightfieldPointSample out;
if (!isValid() || world_assembly_detail::OutsideRange(x)
|| world_assembly_detail::OutsideRange(y)
@@ -2635,6 +2685,17 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
Stats.sharedColumnHits=0;
Stats.columnEvaluations=0;
Stats.columnComputeMilliseconds=0.0;
+ Stats.sampledPointNanoseconds=0;
+ Stats.sampledPointCalls=0;
+ Stats.sampledCachedColumnNanoseconds=0;
+ Stats.sampledCachedColumnCalls=0;
+ Stats.sampledSharedInitialReadNanoseconds=0;
+ Stats.sampledSharedInitialReads=0;
+ Stats.sampledSharedLockedReadNanoseconds=0;
+ Stats.sampledSharedLockedReads=0;
+ world_assembly_detail::HeightfieldSharedColumnCacheMetrics={};
+ world_assembly_detail::HeightfieldPointTimingSequence=0;
+ world_assembly_detail::HeightfieldCachedColumnTimingSequence=0;
Stats.captureCoordinates=captureCoordinates;
Stats.coordinateCaptureComplete=true;
Stats.evaluatedCoordinates.clear();
@@ -2670,7 +2731,15 @@ std::int64_t Heightfield2D::threadColumnInterpolationStrideSubvoxels() noexcept
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
-{ return world_assembly_detail::HeightfieldColumnStats; }
+{
+ auto& Stats=world_assembly_detail::HeightfieldColumnStats;
+ const auto& Shared=world_assembly_detail::HeightfieldSharedColumnCacheMetrics;
+ Stats.sampledSharedInitialReadNanoseconds=Shared.sampledInitialReadNanoseconds;
+ Stats.sampledSharedInitialReads=Shared.sampledInitialReads;
+ Stats.sampledSharedLockedReadNanoseconds=Shared.sampledLockedReadNanoseconds;
+ Stats.sampledSharedLockedReads=Shared.sampledLockedReads;
+ return Stats;
+}
HeightfieldBoxClass Heightfield2D::classifyLatticeBox(std::int64_t minimumX,
std::int64_t minimumY,std::int64_t minimumZ,std::int64_t maximumX,
diff --git a/Standalone/Source/StreamingWorld.h b/Standalone/Source/StreamingWorld.h
index 2964e86..231362b 100644
--- a/Standalone/Source/StreamingWorld.h
+++ b/Standalone/Source/StreamingWorld.h
@@ -1580,7 +1580,7 @@ private:
static_cast<unsigned long long>(D.islandSharedCacheConflictReplacements),
static_cast<double>(D.islandSharedCacheLockWaitNanoseconds)/1000.0);
if(D.passageComposerProfiled)
- std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu carve_segment_visits=%llu relief_field_calls=%llu sweep_segments=%llu sweep_pairs=%llu sweep_outside_pairs=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n",
+ std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu carve_segment_visits=%llu relief_field_calls=%llu sweep_segments=%llu sweep_pairs=%llu sweep_bound_pruned=%llu sweep_outside_pairs=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n",
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
Event.WorkerMilliseconds,static_cast<unsigned long long>(Event.DensitySamples),
static_cast<unsigned long long>(D.fieldCalls),
@@ -1593,6 +1593,7 @@ private:
static_cast<unsigned long long>(D.passageReliefFieldCalls),
static_cast<unsigned long long>(D.passageSweepRouteSegmentCandidates),
static_cast<unsigned long long>(D.passageSweepSegmentSamplePairs),
+ static_cast<unsigned long long>(D.passageSweepBoundPrunedPairs),
static_cast<unsigned long long>(D.passageSweepOutsideInfluencePairs),
static_cast<unsigned long long>(D.passageSweepMinimumUpdates),
static_cast<unsigned long long>(D.passageSweepBaseInfluenceSamples),
@@ -1604,6 +1605,24 @@ private:
static_cast<unsigned long long>(D.passageSweepBaseInfluenceHits),
static_cast<unsigned long long>(D.passageSweepOutOfInfluenceHits),
static_cast<unsigned long long>(D.passageSweepMissingSamples));
+ if(D.l118FieldTagsCaptured)
+ std::printf("[L118FieldTags] key=(%d,%d,%d,lod%d) sweep_prepare_ms=%.3f smoothmin_calls=%llu smoothmin_samples=%llu smoothmin_ms_est=%.3f point_samples=%llu point_ms_est=%.3f cached_column_samples=%llu cached_column_ms_est=%.3f shared_initial_read_samples=%llu shared_initial_read_ms_est=%.3f shared_locked_read_samples=%llu shared_locked_read_ms_est=%.3f sample_period=128 overlap=(point_contains_cached_column,cached_column_contains_shared_reads)\n",
+ Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
+ D.passageSweepPrepareMilliseconds,
+ static_cast<unsigned long long>(D.passageSmoothMinCalls),
+ static_cast<unsigned long long>(D.passageSmoothMinSamples),
+ D.passageSmoothMinSamples
+ ?static_cast<double>(D.passageSmoothMinSampledNanoseconds)*128.0/1.0e6:0.0,
+ static_cast<unsigned long long>(D.heightfieldPointSamples),
+ static_cast<double>(D.heightfieldPointSampledNanoseconds)*128.0/1.0e6,
+ static_cast<unsigned long long>(D.heightfieldCachedColumnSamples),
+ static_cast<double>(D.heightfieldCachedColumnSampledNanoseconds)*128.0/1.0e6,
+ static_cast<unsigned long long>(D.heightfieldSharedInitialReadSamples),
+ static_cast<double>(D.heightfieldSharedInitialReadSampledNanoseconds)
+ *128.0/1.0e6,
+ static_cast<unsigned long long>(D.heightfieldSharedLockedReadSamples),
+ static_cast<double>(D.heightfieldSharedLockedReadSampledNanoseconds)
+ *128.0/1.0e6);
}
void RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType Type,
const FVoxelTileKey& Key,int32 WorkerId,size_t QueueDepth,size_t ActiveWorkers,