Buckets:
| #!/usr/bin/env python | |
| """Parallel driver for the 24-PDR x 70-instance sweep (Claim 3 heuristic side). | |
| Resumes from outputs/pdr_makespans.json: only (instance, rule) pairs not already | |
| present are recomputed. Parallelism is at the (instance, rule) task granularity | |
| (up to 24 x 70 = 1680 tasks) so heavy "large" instances do not serialize a whole | |
| worker. Each task calls rollout() from run_pdrs.py (the faithful in-process | |
| replica of `dsbx-agent run`). Results are checkpointed to disk after every | |
| completion, so a broken pool can simply be re-run to continue. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import sys | |
| import time | |
| from concurrent.futures import ProcessPoolExecutor, as_completed | |
| from concurrent.futures.process import BrokenProcessPool | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).parent)) | |
| from run_pdrs import ( # noqa: E402 | |
| rollout, | |
| instance_dirs, | |
| OP_RULES, | |
| MACHINE_RULES, | |
| SUBSET, | |
| OUT, | |
| ) | |
| MAX_WORKERS = int(os.environ.get("PDR_WORKERS", "48")) | |
| RULES = [f"{op}:{mac}" for op in OP_RULES for mac in MACHINE_RULES] | |
| def one_task(dir_str: str, op: str, mac: str): | |
| return dir_str, f"{op}:{mac}", rollout(Path(dir_str), op, mac) | |
| def _instance_weight(d: Path) -> int: | |
| """Cheap size proxy: number of event lines (drives rollout time & memory).""" | |
| ev = d / "events.jsonl" | |
| try: | |
| with ev.open("rb") as fh: | |
| return sum(1 for _ in fh) | |
| except OSError: | |
| return 0 | |
| def _remaining_tasks(results, dirs, key_of): | |
| # Smallest instances first: gives fast early progress (confirms pool | |
| # stability quickly) and defers the memory-heavy large instances so fewer | |
| # of them are ever resident at once. | |
| ordered = sorted(dirs, key=_instance_weight) | |
| tasks = [] | |
| for d in ordered: | |
| key = key_of[str(d)] | |
| for op in OP_RULES: | |
| for mac in MACHINE_RULES: | |
| if f"{op}:{mac}" not in results[key]: | |
| tasks.append((str(d), op, mac)) | |
| return tasks | |
| def _run_pool(tasks, results, key_of, t0): | |
| """One executor attempt. Returns (#done, broke) and checkpoints as it goes. | |
| If the pool dies abruptly (BrokenProcessPool), the completed tasks are | |
| already on disk; we report broke=True so the caller can rebuild a fresh | |
| executor and continue from the checkpoint. | |
| """ | |
| done = 0 | |
| broke = False | |
| with ProcessPoolExecutor(max_workers=MAX_WORKERS) as ex: | |
| futs = {ex.submit(one_task, *t): t for t in tasks} | |
| try: | |
| for fut in as_completed(futs): | |
| src = futs[fut] | |
| try: | |
| dir_str, rule, cmax = fut.result() | |
| except BrokenProcessPool: | |
| raise | |
| except Exception as e: # noqa: BLE001 - single bad task | |
| print( | |
| f" TASK ERROR {src[0]} {src[1]}:{src[2]} -> {e!r}", flush=True | |
| ) | |
| continue | |
| results[key_of[dir_str]][rule] = cmax | |
| done += 1 | |
| OUT.parent.mkdir(parents=True, exist_ok=True) | |
| OUT.write_text(json.dumps(results, indent=2)) | |
| if done % 10 == 0: | |
| el = time.time() - t0 | |
| print(f" +{done} this attempt, {el:.0f}s elapsed", flush=True) | |
| except BrokenProcessPool: | |
| broke = True | |
| print(" POOL BROKE; will rebuild and continue from checkpoint", flush=True) | |
| return done, broke | |
| def main(): | |
| dirs = instance_dirs() | |
| assert len(dirs) == 70, f"expected 70, got {len(dirs)}" | |
| results = {} | |
| if OUT.exists(): | |
| results = json.loads(OUT.read_text()) | |
| key_of = {str(d): str(d.relative_to(SUBSET)) for d in dirs} | |
| for d in dirs: | |
| results.setdefault(key_of[str(d)], {}) | |
| total_needed = 70 * 24 | |
| t0 = time.time() | |
| attempt = 0 | |
| while True: | |
| tasks = _remaining_tasks(results, dirs, key_of) | |
| have = total_needed - len(tasks) | |
| if not tasks: | |
| print(f"All {total_needed} rollouts present.", flush=True) | |
| break | |
| attempt += 1 | |
| print( | |
| f"[attempt {attempt}] {have}/{total_needed} done; " | |
| f"{len(tasks)} remaining on {MAX_WORKERS} workers", | |
| flush=True, | |
| ) | |
| done, broke = _run_pool(tasks, results, key_of, t0) | |
| if not broke and done == 0 and tasks: | |
| print( | |
| "No progress and pool did not break; aborting to avoid a loop.", | |
| flush=True, | |
| ) | |
| break | |
| if attempt > 40: | |
| print("Too many attempts; aborting.", flush=True) | |
| break | |
| print(f"DONE in {time.time()-t0:.0f}s -> {OUT}", flush=True) | |
| _verify(results) | |
| def _verify(results): | |
| n_full = sum(1 for k in results if set(results[k].keys()) >= set(RULES)) | |
| print(f"Instances with all 24 rules: {n_full}/{len(results)}", flush=True) | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 4.99 kB
- Xet hash:
- b90134badc702a547f495705e16b26d66a8c7f7bf60c7f212c6e1caec6256a3e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.