mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-13 02:41:02 +00:00
build_game.py --pgo gen builds an instrumented totk.exe (llvm-mingw: MSYS2's clang has no profile runtime) that adds its counts to <out>/pgo/*.profraw every minute and at exit; plat_exit ends the process with ExitProcess, which skips compiler-rt's atexit write. tools/pgo.py merges them into build/pgo/pgo-<ver>.profdata with a record of the recomp/ tree it fits. build_game.py --pgo FILE compiles the generated code at -O2 and the runtime with that profile. make_release.py ships build/pgo's profiles in data/ and builds the runtime libraries with them; the installer passes the update's profile to build_game.py, and builds at -O1 without one. Profiles name lifted functions by address and hold branch counts and control-flow hashes of the generated C, no bytes of the game. 1.4.3 on machine A, mean frame by world clock: the fight2 bench 11.49 -> 10.06 ms (-12.5%), a fight outside the training 13.00 -> 12.24 ms (-5.8%); every window CPU-bound. The profile was trained on scripted runs and 24 minutes of play. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
4.1 KiB
Python
85 lines
4.1 KiB
Python
"""Merge the raw profiles of an instrumented build into the .profdata that build_game.py --pgo FILE reads.
|
|
|
|
python tools/pgo.py merge build/v143-pgogen --version v143 [--out build/pgo/pgo-v143.profdata]
|
|
|
|
An instrumented build (build_game.py --pgo gen) writes <build>/pgo/default_<id>.profraw while totk.exe runs (every
|
|
minute and at exit); successive runs of the same exe add to that file. llvm-profdata comes from the llvm-mingw toolchain
|
|
the instrumented build used: its raw format is tied to the compiler version.
|
|
|
|
Beside the profile goes <out>.json, the training record: the game version and the git tree of recomp/ at the merge.
|
|
The profile matches the generated C of that generator; tools/make_release.py ships it only while recomp/ is unchanged,
|
|
since llvm-profdata matches functions by name and control-flow hash and a regenerated function silently loses its
|
|
counts. Merge right after the training runs, from the commit they were built at.
|
|
"""
|
|
import argparse
|
|
import datetime
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
EXE = '.exe' if sys.platform == 'win32' else ''
|
|
DEFAULT_DIR = ROOT / 'build' / 'pgo' # tools/make_release.py takes pgo-<version>.profdata from here
|
|
|
|
|
|
def llvm_profdata(toolchain):
|
|
if toolchain:
|
|
tool = toolchain / 'bin' / f'llvm-profdata{EXE}'
|
|
else:
|
|
found = sorted((ROOT / 'toolchain').glob(f'llvm-mingw-*/llvm-mingw-*/bin/llvm-profdata{EXE}'))
|
|
tool = found[-1] if found else Path(f'llvm-profdata{EXE}')
|
|
if not tool.is_file():
|
|
sys.exit(f'{tool} not found: pass --toolchain')
|
|
return tool
|
|
|
|
|
|
def recomp_tree():
|
|
"""The git tree id of recomp/ at HEAD; None when recomp/ has uncommitted changes."""
|
|
dirty = subprocess.run(['git', '-C', str(ROOT), 'status', '--porcelain', '--', 'recomp'], capture_output=True,
|
|
text=True).stdout.strip()
|
|
if dirty:
|
|
return None
|
|
return subprocess.run(['git', '-C', str(ROOT), 'rev-parse', 'HEAD:recomp'], capture_output=True,
|
|
text=True).stdout.strip() or None
|
|
|
|
|
|
def merge(build, out, version, toolchain):
|
|
raw = sorted((build / 'pgo').glob('*.profraw'))
|
|
if not raw:
|
|
sys.exit(f'no .profraw in {build / "pgo"}: run the instrumented totk.exe first')
|
|
tree = recomp_tree()
|
|
if not tree:
|
|
sys.exit('recomp/ has uncommitted changes: the record could not name the generator the profile matches')
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp = out.with_suffix('.profdata.tmp')
|
|
cmd = [str(llvm_profdata(toolchain)), 'merge', '--failure-mode=all', '-o', str(tmp), *map(str, raw)]
|
|
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
if p.returncode:
|
|
tmp.unlink(missing_ok=True)
|
|
sys.exit(f'llvm-profdata merge failed: {(p.stdout + p.stderr).strip()[-2000:]}')
|
|
tmp.replace(out)
|
|
record = {'version': version, 'recomp_tree': tree, 'merged': datetime.date.today().isoformat(),
|
|
'raw_files': len(raw)}
|
|
out.with_suffix('.json').write_text(json.dumps(record, indent=1) + '\n', encoding='utf-8')
|
|
size = sum(f.stat().st_size for f in raw)
|
|
print(f'{out}: {len(raw)} raw files ({size / 2**20:.1f} MiB) -> {out.stat().st_size / 2**20:.1f} MiB; '
|
|
f'recomp tree {tree[:12]}')
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
sub = ap.add_subparsers(dest='cmd', required=True)
|
|
m = sub.add_parser('merge', help='merge <build>/pgo/*.profraw into one .profdata and its training record')
|
|
m.add_argument('build', type=Path, help='the instrumented build directory')
|
|
m.add_argument('--version', required=True, help='game version the build was made for (v121, v143)')
|
|
m.add_argument('--out', type=Path, help=f'default {DEFAULT_DIR.relative_to(ROOT).as_posix()}/pgo-<version>.profdata')
|
|
m.add_argument('--toolchain', type=Path, help='llvm-mingw directory (default: the newest under toolchain/)')
|
|
a = ap.parse_args()
|
|
out = (a.out or DEFAULT_DIR / f'pgo-{a.version}.profdata').resolve()
|
|
merge(a.build.resolve(), out, a.version, a.toolchain)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|