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:
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,
|
||||||
Reference in new issue
Block a user