From c4364c6bc13bf096bee6b03635dff3c27723d8fe Mon Sep 17 00:00:00 2001 From: Yanqin Li Date: Wed, 24 Jun 2026 23:19:47 +0800 Subject: [PATCH 1/2] anal(perf): add ipc diff comparison script --- perf/ipc_diff_pro.py | 153 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 perf/ipc_diff_pro.py diff --git a/perf/ipc_diff_pro.py b/perf/ipc_diff_pro.py new file mode 100644 index 00000000..13de8396 --- /dev/null +++ b/perf/ipc_diff_pro.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 + +''' +Author: Yanqin Li + +Usage: + python3 ipc_diff_pro.py checkpoint.json base=/path/to/spec_dir new=/path/to/spec_dir -o ipc-compare.csv -j $(nproc) + +Notes: + The first SPEC directory is treated as the baseline. + Additional SPEC directories are compared against that baseline. +''' + +import argparse +import csv +import json +import os +from multiprocessing import Process, Queue + +from perf import PerfCounters + + +def load_ckpt_list(json_path): + with open(json_path, "r") as f: + data = json.load(f) + ckpts = [] + for bench_name, info in data.items(): + for point, weight in info["points"].items(): + ckpts.append((bench_name, point, str(weight))) + return ckpts + + +def read_ipc(sim_err_path): + if not os.path.isfile(sim_err_path): + return None + counters = PerfCounters(sim_err_path) + cycle = counters["clock_cycle"] + instr = counters["commitInstr"] + if cycle is None or instr is None: + return None + try: + cycle = float(cycle) + instr = float(instr) + return instr / cycle if cycle != 0 else None + except Exception: + return None + + +def worker(task_q, result_q): + while not task_q.empty(): + ckpt, sim_err = task_q.get() + result_q.put((ckpt, read_ipc(sim_err))) + + +def collect_ipc(spec_dir, json_path, jobs): + ckpts = load_ckpt_list(json_path) + task_q = Queue() + result_q = Queue() + for bench_name, point, weight in ckpts: + ckpt = "_".join([bench_name, point, weight]) + sim_err = os.path.join(spec_dir, ckpt, "simulator_err.txt") + task_q.put((ckpt, sim_err)) + + procs = [] + for _ in range(jobs): + p = Process(target=worker, args=(task_q, result_q)) + p.start() + procs.append(p) + for p in procs: + p.join() + + ipc_map = {} + while not result_q.empty(): + ckpt, ipc = result_q.get() + ipc_map[ckpt] = ipc + return ipc_map + + +def parse_spec_dir_arg(arg): + parts = arg.split("=", 1) + if len(parts) != 2: + raise ValueError("SPEC_DIR must be in name=path format") + return parts[0], parts[1] + + +def compute_rates(row, base_idx=0): + base = row[base_idx] + rates = [] + for v in row[base_idx + 1:]: + if base is None or v is None or base == 0: + rates.append(None) + else: + rates.append(v / base - 1) + return rates + + +def main(): + parser = argparse.ArgumentParser(description="Compare IPC across SPEC dirs") + parser.add_argument("json_path", help="checkpoint json path") + parser.add_argument("spec_dirs", nargs="+", help="name=SPEC_DIR, multiple") + parser.add_argument("-o", "--output", default="ipc-compare.csv") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count() or 1) + args = parser.parse_args() + + print("out path of IPC compare:", os.path.abspath(args.output)) + named_dirs = [parse_spec_dir_arg(x) for x in args.spec_dirs] + ipc_by_dir = [] + for name, path in named_dirs: + ipc_by_dir.append((name, path, collect_ipc(path, args.json_path, args.jobs))) + + all_ckpts = sorted(set().union(*[set(m.keys()) for _, _, m in ipc_by_dir])) + + rows = [] + for ckpt in all_ckpts: + ipcs = [m.get(ckpt) for _, _, m in ipc_by_dir] + rates = compute_rates(ipcs, 0) + rows.append((ckpt, ipcs, rates)) + + def sort_key(item): + rates = item[2] + if not rates or rates[0] is None: + return -1 + return abs(rates[0]) + + rows.sort(key=sort_key, reverse=True) + + header = ["ckpt"] + [name for name, _, _ in ipc_by_dir] + if len(ipc_by_dir) > 1: + base_name = ipc_by_dir[0][0] + header += [f"pct {name}/{base_name}" for name, _, _ in ipc_by_dir[1:]] + + with open(args.output, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(header) + writer.writerow(["path"] + [path for _, path, _ in ipc_by_dir]) + for ckpt, ipcs, rates in rows: + row = [ckpt] + ipcs + if len(ipc_by_dir) > 1: + row += rates + writer.writerow(row) + + top_n = 3 if len(rows) >= 3 else len(rows) + if top_n > 0: + print("\nTop log files:") + for ckpt, _, _ in rows[:top_n]: + print(f"{ckpt}:") + for _, path, _ in ipc_by_dir: + print(os.path.join(path, ckpt, "simulator_err.txt")) + print() + + +if __name__ == "__main__": + main() From 70a7f41dee462a4f5d59754ad473ca83db4602d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Jun 2026 08:26:58 +0000 Subject: [PATCH 2/2] Fix ipc_diff_pro.py: import path, queue reliability, percent computation --- perf/ipc_diff_pro.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/perf/ipc_diff_pro.py b/perf/ipc_diff_pro.py index 13de8396..b038b549 100644 --- a/perf/ipc_diff_pro.py +++ b/perf/ipc_diff_pro.py @@ -15,8 +15,11 @@ import csv import json import os +import queue +import sys from multiprocessing import Process, Queue +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from perf import PerfCounters @@ -47,8 +50,11 @@ def read_ipc(sim_err_path): def worker(task_q, result_q): - while not task_q.empty(): - ckpt, sim_err = task_q.get() + while True: + try: + ckpt, sim_err = task_q.get_nowait() + except queue.Empty: + break result_q.put((ckpt, read_ipc(sim_err))) @@ -70,7 +76,7 @@ def collect_ipc(spec_dir, json_path, jobs): p.join() ipc_map = {} - while not result_q.empty(): + for _ in range(len(ckpts)): ckpt, ipc = result_q.get() ipc_map[ckpt] = ipc return ipc_map @@ -90,7 +96,7 @@ def compute_rates(row, base_idx=0): if base is None or v is None or base == 0: rates.append(None) else: - rates.append(v / base - 1) + rates.append((v / base - 1) * 100) return rates