Identical builds differ <=0.6% median; residual near-ready vs assembler/world is real: Surface World +2.9..+3.6%, Vertical Shafts +4.0..+4.3%, Tunnel Network -3.6/+6.8%. Profile points at column-cache reads and the sweep lookup. Removed an unused sample field and per-lookup shared_ptr refcounting. 16/16 seeds, fast tier, Clang/GCC green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
121 lines
4.9 KiB
Python
121 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Symbolize L137 L77 samples and compare inclusive worker functions."""
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[4]
|
|
OUT = ROOT / "Saved/Linux/l137/profile"
|
|
BINARIES = {
|
|
"assembler-world": ROOT / "Saved/Linux/l137/profile-bin/VoxelForgeCoreHeadless-l137-profile-base",
|
|
"head": ROOT / "Saved/Linux/l137/profile-bin/VoxelForgeCoreHeadless-l137-profile-head",
|
|
}
|
|
SAMPLE_RE = re.compile(
|
|
r"^\[L77Sample\] pc=(0x\w+) lod=(\d+) stage=(\S+) route=(\S+) "
|
|
r"phase=(\S+) halo=(\d+) weight=(\d+)$", re.M)
|
|
|
|
|
|
def samples(version: str) -> tuple[list[dict], set[str]]:
|
|
rows, pcs = [], set()
|
|
for replicate in (1, 2, 3):
|
|
path = OUT / f"sw-{version}-r{replicate}.log"
|
|
for pc, lod, stage, route, phase, halo, weight in SAMPLE_RE.findall(
|
|
path.read_text(encoding="utf-8", errors="replace")):
|
|
lod_value = int(lod)
|
|
if not 0 <= lod_value <= 8:
|
|
continue
|
|
pcs.add(pc)
|
|
rows.append({"version": version, "replicate": replicate,
|
|
"pc": pc, "lod": lod_value, "stage": stage,
|
|
"route": route, "phase": phase, "halo": int(halo),
|
|
"weight": int(weight)})
|
|
return rows, pcs
|
|
|
|
|
|
def symbolize(version: str, pcs: set[str]) -> dict[str, str]:
|
|
command = ["llvm-symbolizer-19", "--demangle", "--inlines", "--output-style=JSON",
|
|
f"--obj={BINARIES[version]}"]
|
|
result = subprocess.run(command, input="\n".join(sorted(pcs)) + "\n",
|
|
capture_output=True, text=True, check=True)
|
|
decoder = json.JSONDecoder()
|
|
offset, output = 0, {}
|
|
while offset < len(result.stdout):
|
|
while offset < len(result.stdout) and result.stdout[offset].isspace():
|
|
offset += 1
|
|
if offset == len(result.stdout):
|
|
break
|
|
item, offset = decoder.raw_decode(result.stdout, offset)
|
|
frames = item.get("Symbol", [])
|
|
output[item["Address"]] = (
|
|
frames[0].get("FunctionName", "?") if frames else "?")
|
|
if output.keys() != pcs:
|
|
raise RuntimeError(f"{version}: symbolizer resolved {len(output)}/{len(pcs)} PCs")
|
|
return output
|
|
|
|
|
|
def main() -> int:
|
|
totals: dict[str, Counter] = {}
|
|
lod_totals: dict[str, Counter] = {}
|
|
detail_rows = []
|
|
for version in BINARIES:
|
|
rows, pcs = samples(version)
|
|
symbols = symbolize(version, pcs)
|
|
totals[version] = Counter()
|
|
lod_totals[version] = Counter()
|
|
for row in rows:
|
|
function = symbols[row["pc"]]
|
|
weight = row["weight"]
|
|
totals[version][function] += weight
|
|
lod_totals[version][row["lod"]] += weight
|
|
detail_rows.append({**row, "function": function})
|
|
total_by_version = {key: sum(counter.values()) for key, counter in totals.items()}
|
|
functions = set(totals["assembler-world"]) | set(totals["head"])
|
|
result = []
|
|
for function in functions:
|
|
base_ticks = totals["assembler-world"][function]
|
|
head_ticks = totals["head"][function]
|
|
base_pct = 100.0 * base_ticks / total_by_version["assembler-world"]
|
|
head_pct = 100.0 * head_ticks / total_by_version["head"]
|
|
result.append({
|
|
"function": function,
|
|
"assembler_world_ticks": base_ticks,
|
|
"assembler_world_share_pct": round(base_pct, 3),
|
|
"head_ticks": head_ticks,
|
|
"head_share_pct": round(head_pct, 3),
|
|
"share_delta_pp": round(head_pct - base_pct, 3),
|
|
})
|
|
result.sort(key=lambda row: abs(row["share_delta_pp"]), reverse=True)
|
|
with (OUT / "function-diff.csv").open("w", encoding="utf-8", newline="") as stream:
|
|
writer = csv.DictWriter(stream, fieldnames=list(result[0]))
|
|
writer.writeheader()
|
|
writer.writerows(result)
|
|
with (OUT / "symbolized-samples.csv").open(
|
|
"w", encoding="utf-8", newline="") as stream:
|
|
writer = csv.DictWriter(stream, fieldnames=list(detail_rows[0]))
|
|
writer.writeheader()
|
|
writer.writerows(detail_rows)
|
|
summary = {
|
|
"scope": "worker samples LOD0-8, all LODs included; lod=255 main-thread samples excluded",
|
|
"runs_per_version": 3,
|
|
"weighted_ticks": total_by_version,
|
|
"lod_weighted_ticks": {version: dict(sorted(values.items()))
|
|
for version, values in lod_totals.items()},
|
|
"function_diff": result,
|
|
}
|
|
(OUT / "function-diff.json").write_text(
|
|
json.dumps(summary, indent=2) + "\n", encoding="utf-8")
|
|
print("[L137ProfileDiff] " + json.dumps({
|
|
"scope": summary["scope"], "weighted_ticks": total_by_version,
|
|
"lod_weighted_ticks": summary["lod_weighted_ticks"],
|
|
"largest_share_deltas": result[:20]}, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|