chore: 初始化 ViperApps 仓库
纳入 11 个示例工程(不含 ViperOS,另行开专题)的源码:已完成新语法迁移(R1-R16 机械规则与 R1/R12 改写)。.gitignore 排除 ViperOS/、各工程编译产物(.tpv_build/.native_build/output/temp)、二进制与 ViperMNIST/data 数据集。
This commit is contained in:
commit
df8e301720
54 files changed
+12112
No files matched your search
@@ -0,0 +1,511 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
plot_bench.py - 读 run_bench.py 导出的 CSV, 生成 4 张对比图(无需重跑基准)
|
||||
|
||||
用法:
|
||||
python plot_bench.py # 自动取最新的 bench_*.csv
|
||||
python plot_bench.py bench_20260910_2031.csv
|
||||
python plot_bench.py bench_xxx.csv --metric cpu
|
||||
python plot_bench.py bench_xxx.csv --out-dir figs --prefix run1
|
||||
|
||||
产出(4 张 PNG, 默认与 CSV 同目录, 前缀 = CSV 文件名):
|
||||
<前缀>_1_simd_vs_scalar.png 左: ADD/MUL/SQRT 的 原生 vs SIMD 延迟对比(柱状)
|
||||
右: 各操作"比原生快多少倍"(柱状 + xN.NN 标注)
|
||||
<前缀>_2_exec_time.png 各实现 Execution Time 折线(叠加每次 run 的实测点)
|
||||
<前缀>_3_overall_mean.png 各实现 Overall Mean Execution Time 对比
|
||||
<前缀>_4_gflops.png 各实现 Measured GFLOPS 吞吐对比
|
||||
|
||||
统计口径与 run_bench.py 报告一致: CSV 里 trimmed=1 的样本(被离群裁剪剔除)不计入。
|
||||
图1 需要 CSV 里同时有 ADD/MUL/SQRT 的 原生+SIMD 成对数据, 例如:
|
||||
python run_bench.py --impl viper_add,viper_scalar_add,viper_mul,viper_scalar_mul,viper_sqrt,viper_scalar_sqrt
|
||||
缺哪一对就少画哪一根, 全缺则整张图跳过并提示。
|
||||
"""
|
||||
import argparse
|
||||
import csv
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg") # 无 GUI 后端, 只存文件
|
||||
import matplotlib.pyplot as plt # noqa: E402
|
||||
import numpy as np # noqa: E402
|
||||
|
||||
# 图表标题/坐标轴都是中文: 必须显式指定带 CJK 字形的字体, 否则全是方块
|
||||
matplotlib.rcParams["font.sans-serif"] = [
|
||||
"Microsoft YaHei", "SimHei", "Microsoft JhengHei", "DejaVu Sans"]
|
||||
matplotlib.rcParams["axes.unicode_minus"] = False
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
|
||||
# 实现名 -> (显示名, 颜色), 固定顺序便于多次运行配色一致
|
||||
# 显示名刻意短: 图内文字统一英文, 图例太长会把绘图区挤扁
|
||||
IMPL_STYLE = [
|
||||
("viper", "Viper SIMD", "#d62728"),
|
||||
("viper_scalar", "Viper Scalar", "#ff7f0e"),
|
||||
("c", "C (-O2 -mavx2 -mfma)", "#1f77b4"),
|
||||
("cpp", "C++ (std::vector)", "#2ca02c"),
|
||||
("py", "Python + numpy", "#9467bd"),
|
||||
]
|
||||
|
||||
# 图1: 三种操作 -> (显示名, SIMD 实现名, 原生实现名)。
|
||||
# 每一对都是同一个 app.exe 的两个 mode, 除"是否向量化"外无任何差异
|
||||
# (见 App/main.vp 的 KERNEL_* 与 run_bench.py 的 VIPER_KERNELS)。
|
||||
KERNEL_PAIRS = [
|
||||
("ADD", "viper_add", "viper_scalar_add"),
|
||||
("MUL", "viper_mul", "viper_scalar_mul"),
|
||||
("SQRT", "viper_sqrt", "viper_scalar_sqrt"),
|
||||
]
|
||||
|
||||
# 图1 配色: 原生(标量) vs SIMD
|
||||
COL_NATIVE = "#8c8c8c"
|
||||
COL_SIMD = "#d62728"
|
||||
|
||||
|
||||
def style_for(impl):
|
||||
"""实现名 -> (显示名, 颜色); 未登记的实现给灰色, 不静默改名。"""
|
||||
for name, disp, color in IMPL_STYLE:
|
||||
if name == impl:
|
||||
return disp, color
|
||||
return impl, "#7f7f7f"
|
||||
|
||||
|
||||
def metric_txt(metric):
|
||||
"""耗时口径 -> 英文短名(图内文字一律用英文, 避免中文长句撑爆标题)。"""
|
||||
return "wall clock" if metric == "wall" else "thread CPU"
|
||||
|
||||
|
||||
def human_kib(size):
|
||||
"""LEN(元素数, double) -> 工作集描述(四份缓冲共 LEN*32 字节)"""
|
||||
kib = size * 32 / 1024.0
|
||||
if kib >= 1024:
|
||||
return f"{size}\n({kib / 1024:.0f} MiB)"
|
||||
return f"{size}\n({kib:.0f} KiB)"
|
||||
|
||||
|
||||
def load_csv(path):
|
||||
"""读 CSV -> {(impl, len): {"wall":[...], "cpu":[...], "iters":n, "trimmed":n,
|
||||
"groups": {组号: {"wall":[...], "cpu":[...]}}}}
|
||||
|
||||
注意 iters 要留着: 每个尺寸的 LEN×ITERS×2 = 该尺寸下单 run 的总运算量,
|
||||
是 GFLOPS 换算的唯一依据(py 纯解释器回退会降规模, iters 与其它实现不同)。
|
||||
groups 供"只有一个 LEN 时改用组号当横轴"用(见 fig_exec_time)。
|
||||
"""
|
||||
data = {}
|
||||
with open(path, "r", newline="", encoding="utf-8-sig") as f:
|
||||
for row in csv.DictReader(f):
|
||||
key = (row["impl"], int(row["len"]))
|
||||
rec = data.setdefault(key, {"wall": [], "cpu": [], "iters": int(row["iters"]),
|
||||
"trimmed": 0, "groups": {}})
|
||||
if int(row["trimmed"]) == 1:
|
||||
rec["trimmed"] += 1
|
||||
continue
|
||||
wv = float(row["wall_us"])
|
||||
cv = float(row["cpu_us"])
|
||||
rec["wall"].append(wv)
|
||||
rec["cpu"].append(cv)
|
||||
grp = rec["groups"].setdefault(int(row["group"]), {"wall": [], "cpu": []})
|
||||
grp["wall"].append(wv)
|
||||
grp["cpu"].append(cv)
|
||||
if int(row["iters"]) != rec["iters"]:
|
||||
rec["iters"] = int(row["iters"]) # 防御: 同名 key 的 iters 应一致
|
||||
return data
|
||||
|
||||
|
||||
def series(rec, metric):
|
||||
"""取某实现某尺寸在指定口径下的有效样本(计时值必须 > 0)。"""
|
||||
v = np.asarray(rec[metric], dtype=float)
|
||||
return v[np.isfinite(v) & (v > 0.0)]
|
||||
|
||||
|
||||
def annotate_bars(ax, xs, ys, spec=".1f", dy_ratio=0.02, fontsize=9, color="black"):
|
||||
"""在柱顶标数值(xs/ys 为等长序列)。
|
||||
|
||||
spec 用 format() 的格式说明符(如 ".1f" / ",.0f"), 不用 % 风格 ——
|
||||
Python 的 % 格式化不支持千分位(%,.0f 会直接抛 ValueError)。
|
||||
"""
|
||||
span = (max(ys) - min(ys)) if len(ys) > 1 else (abs(ys[0]) if ys else 1.0)
|
||||
dy = (span if span > 0 else 1.0) * dy_ratio
|
||||
for x, y in zip(xs, ys):
|
||||
ax.text(x, y + dy, format(y, spec), ha="center", va="bottom",
|
||||
fontsize=fontsize, color=color)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 图1: ViperSIMD - ADD / MUL / SQRT 的 原生 vs SIMD(左右双面板)
|
||||
# ------------------------------------------------------------
|
||||
def fig_simd_vs_scalar(data, sizes, args, metric, out_path):
|
||||
"""左面板 = 每种操作(ADD/MUL/SQRT)的 原生 vs SIMD 延迟柱状对比;
|
||||
右面板 = 对应的 SIMD 加速比(原生延迟 / SIMD 延迟)。
|
||||
|
||||
只取一个 LEN(CSV 里最小的那个): 加速比是"指令级/微架构"特性, 在能驻留私有
|
||||
L2 的小尺寸上测得最干净; 想看跨尺寸的访存行为请用图2。
|
||||
"""
|
||||
if not sizes:
|
||||
print("[图1] 跳过: CSV 里没有样本")
|
||||
return False
|
||||
use_size = sizes[0]
|
||||
rows = [] # (op, native_us, simd_us)
|
||||
for op, simd_impl, native_impl in KERNEL_PAIRS:
|
||||
rs = data.get((simd_impl, use_size))
|
||||
rn = data.get((native_impl, use_size))
|
||||
if not rs or not rn:
|
||||
continue
|
||||
vs = series(rs, metric)
|
||||
vn = series(rn, metric)
|
||||
if vs.size == 0 or vn.size == 0:
|
||||
continue
|
||||
rows.append((op, float(vn.mean()), float(vs.mean())))
|
||||
if not rows:
|
||||
need = ",".join(f"{a},{b}" for _, a, b in KERNEL_PAIRS)
|
||||
print("[图1] 跳过: CSV 里没有 ADD/MUL/SQRT 的 原生+SIMD 成对数据")
|
||||
print(f" 该 CSV 实际包含: {sorted({k[0] for k in data})}")
|
||||
print(" 直接重跑一次即可(run_bench.py 默认已含这些内核变体): python run_bench.py")
|
||||
print(f" 或只补跑内核变体: --impl {need}")
|
||||
return False
|
||||
|
||||
ops = [r[0] for r in rows]
|
||||
native_us = [r[1] for r in rows]
|
||||
simd_us = [r[2] for r in rows]
|
||||
speedup = [(n / s if s > 0 else float("nan")) for n, s in zip(native_us, simd_us)]
|
||||
best = max(speedup)
|
||||
|
||||
fig, (axL, axR) = plt.subplots(1, 2, figsize=(13.5, 6.4))
|
||||
x = np.arange(len(ops), dtype=float)
|
||||
w = 0.36
|
||||
|
||||
# ---- 左: 延迟对比(每组内 左=原生 / 右=SIMD) ----
|
||||
axL.bar(x - w / 2, native_us, w, color=COL_NATIVE, alpha=0.9,
|
||||
label="Native (scalar)")
|
||||
axL.bar(x + w / 2, simd_us, w, color=COL_SIMD, alpha=0.9,
|
||||
label="ViperSIMD (AVX2 x4)")
|
||||
axL.set_xticks(x)
|
||||
axL.set_xticklabels(ops, fontsize=11)
|
||||
axL.set_xlabel("Operation", fontsize=10)
|
||||
axL.set_ylabel(f"Latency per Run (us, {metric_txt(metric)})", fontsize=10)
|
||||
axL.set_title(f"Latency: Native vs ViperSIMD\nLEN={use_size}", fontsize=12)
|
||||
axL.grid(alpha=0.3, axis="y")
|
||||
axL.legend(fontsize=10)
|
||||
hi = max(native_us + simd_us)
|
||||
for xi, y in zip(x - w / 2, native_us):
|
||||
axL.text(xi, y + hi * 0.01, f"{y:,.0f}", ha="center", va="bottom", fontsize=9)
|
||||
for xi, y in zip(x + w / 2, simd_us):
|
||||
axL.text(xi, y + hi * 0.01, f"{y:,.0f}", ha="center", va="bottom", fontsize=9)
|
||||
|
||||
# ---- 右: 比原生快多少倍 ----
|
||||
axR.bar(x, speedup, 0.5, color=COL_SIMD, alpha=0.9)
|
||||
axR.axhline(1.0, color="#555555", lw=1.2, ls="--")
|
||||
axR.text(len(ops) - 0.55, 1.0, "x1 = no gain", va="bottom", ha="right",
|
||||
fontsize=9, color="#555555")
|
||||
axR.set_xticks(x)
|
||||
axR.set_xticklabels(ops, fontsize=11)
|
||||
axR.set_xlabel("Operation", fontsize=10)
|
||||
axR.set_ylabel("Speed-up (native latency / ViperSIMD latency)", fontsize=10)
|
||||
axR.set_title("SIMD Speed-up over Native", fontsize=12)
|
||||
axR.grid(alpha=0.3, axis="y")
|
||||
axR.set_ylim(0, best * 1.30 if best > 0 else 2.0)
|
||||
for xi, sp in zip(x, speedup):
|
||||
axR.text(xi, sp + best * 0.02, f"x{sp:.2f}", ha="center", va="bottom",
|
||||
fontsize=13, fontweight="bold")
|
||||
|
||||
fig.suptitle(f"{args.csv_name}\n"
|
||||
f"bar = mean of all runs · metric = {metric_txt(metric)}"
|
||||
f" · trimmed outliers excluded"
|
||||
f" · latency: lower is better", fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.90))
|
||||
fig.savefig(out_path, dpi=args.dpi)
|
||||
plt.close(fig)
|
||||
print(f"[图1] Native vs SIMD 已保存: {out_path}")
|
||||
return True
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 图2: 各实现 Execution Time 折线 + 每次 run 的实测点
|
||||
# ------------------------------------------------------------
|
||||
def fig_exec_time(data, sizes, impls, args, metric, out_path):
|
||||
# 只有一个 LEN 时横轴没东西可展开(折线会退化成孤点), 改用"组号"当横轴:
|
||||
# 每组 = 一次进程内多次 run, 于是单尺寸下依然能看到折线与组间离散。
|
||||
single = len(sizes) == 1
|
||||
if single:
|
||||
xkeys = sorted({g for rec in data.values() for g in rec["groups"]})
|
||||
xlabel = f"Group Index (LEN={sizes[0]}; one group = one process run)"
|
||||
xticklabels = [str(g) for g in xkeys]
|
||||
note = ("line = group mean, dots = individual runs\n"
|
||||
f"single LEN={sizes[0]}: X axis = group index")
|
||||
else:
|
||||
xkeys = list(sizes)
|
||||
xlabel = "Working Set a/b/c/out (LEN)"
|
||||
xticklabels = [human_kib(s) for s in sizes]
|
||||
note = "line = per-size mean, dots = individual runs"
|
||||
|
||||
def samples_at(rec, xi):
|
||||
"""第 xi 个横轴位置上的有效样本(计时值必须 > 0)。"""
|
||||
raw = rec["groups"].get(xkeys[xi]) if single else rec
|
||||
if not raw:
|
||||
return np.array([])
|
||||
v = np.asarray(raw[metric], dtype=float)
|
||||
return v[np.isfinite(v) & (v > 0.0)]
|
||||
|
||||
def rec_of(impl, xi):
|
||||
return data.get((impl, sizes[0])) if single else data.get((impl, xkeys[xi]))
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12.5, 6.8))
|
||||
x = np.arange(len(xkeys), dtype=float)
|
||||
n = max(1, len(impls))
|
||||
allv = []
|
||||
|
||||
for k, impl in enumerate(impls):
|
||||
disp, color = style_for(impl)
|
||||
off = (k - (n - 1) / 2.0) * 0.11 # 各实现的散点横向错开, 避免互相压盖
|
||||
xs, ys = [], []
|
||||
for i in range(len(xkeys)):
|
||||
rec = rec_of(impl, i)
|
||||
if not rec:
|
||||
continue
|
||||
v = samples_at(rec, i)
|
||||
if v.size == 0:
|
||||
continue
|
||||
xs.append(x[i])
|
||||
ys.append(float(v.mean()))
|
||||
ax.scatter(np.full(v.shape, x[i] + off), v, s=16, color=color,
|
||||
alpha=0.35, linewidths=0, zorder=2)
|
||||
allv.extend(v.tolist())
|
||||
if xs:
|
||||
ax.plot(xs, ys, marker="o", ms=8, lw=2.2, color=color,
|
||||
label=f"{disp} (mean)", zorder=3)
|
||||
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(xticklabels, fontsize=9)
|
||||
ax.set_xlabel(xlabel, fontsize=10)
|
||||
ax.set_ylabel(f"Execution Time per Run (us, {metric_txt(metric)})", fontsize=10)
|
||||
ax.set_title("Execution Time Comparison\n" + note, fontsize=12)
|
||||
|
||||
# 动态范围很大(如 numpy 与 SIMD 差百倍)时自动取对数, 否则小值被压成一条线
|
||||
use_log = args.y_scale == "log"
|
||||
if args.y_scale == "auto" and allv:
|
||||
lo, hi = min(allv), max(allv)
|
||||
use_log = lo > 0 and (hi / lo) > 30.0
|
||||
if use_log:
|
||||
ax.set_yscale("log")
|
||||
ax.set_axisbelow(True)
|
||||
ax.grid(alpha=0.3, which="both")
|
||||
|
||||
if ax.get_legend_handles_labels()[0]:
|
||||
ax.legend(fontsize=9.5, ncol=2)
|
||||
fig.suptitle(f"{args.csv_name} · y-axis: {'log' if use_log else 'linear'}"
|
||||
f" · trimmed outliers excluded"
|
||||
f" · implementations offset horizontally", fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.94))
|
||||
fig.savefig(out_path, dpi=args.dpi)
|
||||
plt.close(fig)
|
||||
print(f"[图2] Execution Time 折线已保存: {out_path}")
|
||||
return True
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 图3: Overall Mean Execution Time 对比
|
||||
# ------------------------------------------------------------
|
||||
def fig_overall_mean(data, sizes, impls, args, metric, out_path):
|
||||
labels, means, stds, colors = [], [], [], []
|
||||
for impl in impls:
|
||||
vals = [series(data[(impl, s)], metric) for s in sizes if (impl, s) in data]
|
||||
vals = [v for v in vals if v.size]
|
||||
if not vals:
|
||||
continue
|
||||
v = np.concatenate(vals)
|
||||
disp, color = style_for(impl)
|
||||
labels.append(disp)
|
||||
means.append(float(v.mean()))
|
||||
stds.append(float(v.std(ddof=1)) if v.size > 1 else 0.0)
|
||||
colors.append(color)
|
||||
if not labels:
|
||||
print("[图3] 跳过: 没有有效样本")
|
||||
return False
|
||||
|
||||
fig, ax = plt.subplots(figsize=(11.0, 6.4))
|
||||
xs = np.arange(len(labels), dtype=float)
|
||||
ax.bar(xs, means, 0.6, color=colors, alpha=0.9,
|
||||
yerr=stds, capsize=6, ecolor="#333333")
|
||||
ax.set_xticks(xs)
|
||||
ax.set_xticklabels(labels, fontsize=10)
|
||||
ax.set_ylabel(f"Mean Execution Time per Run (us, {metric_txt(metric)})", fontsize=10)
|
||||
ax.set_title("Overall Mean Execution Time\n"
|
||||
"all valid samples pooled · error bar = std dev"
|
||||
" · in-bar xN.NN = relative to fastest", fontsize=12)
|
||||
ax.grid(alpha=0.3, axis="y")
|
||||
annotate_bars(ax, xs, means, spec=",.0f" if max(means) > 10000 else ".1f")
|
||||
|
||||
# 柱身只标"相对最快者的倍数"(越小越快); 最快的那根直接标"最快"
|
||||
fastest = min(means)
|
||||
for x, y in zip(xs, means):
|
||||
mult = y / fastest
|
||||
ax.text(x, y * 0.55, "fastest" if mult <= 1.0001 else f"x{mult:.2f}",
|
||||
ha="center", va="center", fontsize=9.5, color="white", fontweight="bold")
|
||||
fig.suptitle(f"{args.csv_name}\n"
|
||||
f"per-run work is constant (LEN×ITERS×2),"
|
||||
f" so absolute times are comparable", fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.90))
|
||||
fig.savefig(out_path, dpi=args.dpi)
|
||||
plt.close(fig)
|
||||
print(f"[图3] Overall Mean Execution Time 已保存: {out_path}")
|
||||
return True
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 图4: Measured GFLOPS 吞吐对比
|
||||
# ------------------------------------------------------------
|
||||
def fig_gflops(data, sizes, impls, args, metric, out_path):
|
||||
labels, means, stds, colors = [], [], [], []
|
||||
for impl in impls:
|
||||
per_run = []
|
||||
for s in sizes:
|
||||
rec = data.get((impl, s))
|
||||
if not rec:
|
||||
continue
|
||||
v = series(rec, metric)
|
||||
if v.size == 0:
|
||||
continue
|
||||
# 该尺寸单 run 的总运算量 = LEN × ITERS × 2 (读 CSV 的 iters, 兼容降规模)
|
||||
flops_run = float(s) * float(rec["iters"]) * 2.0
|
||||
per_run.append(flops_run / (v * 1e-6) / 1e9) # 每次 run 的 GFLOPS
|
||||
if not per_run:
|
||||
continue
|
||||
v = np.concatenate(per_run)
|
||||
disp, color = style_for(impl)
|
||||
labels.append(disp)
|
||||
means.append(float(v.mean()))
|
||||
stds.append(float(v.std(ddof=1)) if v.size > 1 else 0.0)
|
||||
colors.append(color)
|
||||
if not labels:
|
||||
print("[图4] 跳过: 没有有效样本")
|
||||
return False
|
||||
|
||||
fig, ax = plt.subplots(figsize=(11.0, 6.4))
|
||||
xs = np.arange(len(labels), dtype=float)
|
||||
ax.bar(xs, means, 0.6, color=colors, alpha=0.9,
|
||||
yerr=stds, capsize=6, ecolor="#333333")
|
||||
ax.set_xticks(xs)
|
||||
ax.set_xticklabels(labels, fontsize=10)
|
||||
ax.set_ylabel(f"Throughput (GFLOPS, {metric_txt(metric)})", fontsize=10)
|
||||
ax.set_title("Measured GFLOPS Throughput\n"
|
||||
"GFLOPS = LEN×ITERS×2 / per-run seconds · error bar = std dev"
|
||||
" · in-bar xN.NN = relative to fastest", fontsize=12)
|
||||
ax.grid(alpha=0.3, axis="y")
|
||||
annotate_bars(ax, xs, means, spec=".2f")
|
||||
|
||||
# 柱身只标"相对最快者的倍数"(越大越好); 最快的那根直接标"最快"
|
||||
fastest = max(means)
|
||||
for x, y in zip(xs, means):
|
||||
mult = y / fastest
|
||||
ax.text(x, y * 0.55, "fastest" if mult >= 0.9999 else f"x{mult:.2f}",
|
||||
ha="center", va="center", fontsize=9.5, color="white", fontweight="bold")
|
||||
fig.suptitle(f"{args.csv_name}\n"
|
||||
f"higher is better · GFLOPS computed from each run's own CFG",
|
||||
fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.90))
|
||||
fig.savefig(out_path, dpi=args.dpi)
|
||||
plt.close(fig)
|
||||
print(f"[图4] GFLOPS 吞吐对比已保存: {out_path}")
|
||||
return True
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
p = argparse.ArgumentParser(description="把 run_bench.py 的 CSV 画成 4 张对比图")
|
||||
p.add_argument("csv", nargs="?", default=None, help="CSV 路径(默认取最新 bench_*.csv)")
|
||||
p.add_argument("--metric", default="wall", choices=["wall", "cpu"],
|
||||
help="耗时口径(默认 wall=QPC 墙钟; cpu=QueryThreadCycleTime)")
|
||||
p.add_argument("--out-dir", default=None, help="输出目录(默认与 CSV 同目录)")
|
||||
p.add_argument("--prefix", default=None, help="输出文件名前缀(默认 CSV 文件名)")
|
||||
p.add_argument("--out", default=None,
|
||||
help="兼容旧用法: 传 .png 路径时, 取其所在目录与文件名作为输出目录/前缀")
|
||||
p.add_argument("--y-scale", default="auto", choices=["auto", "log", "linear"],
|
||||
help="图2 纵轴刻度(默认 auto: 动态范围 >30 倍自动取对数)")
|
||||
p.add_argument("--dpi", type=int, default=130, help="输出 PNG 分辨率(默认 130)")
|
||||
args = p.parse_args(argv)
|
||||
|
||||
# ---- 定位 CSV ----
|
||||
if args.csv:
|
||||
csv_path = Path(args.csv)
|
||||
if not csv_path.is_absolute():
|
||||
csv_path = ROOT / csv_path
|
||||
else:
|
||||
cands = sorted(ROOT.glob("bench_*.csv"), key=lambda q: q.stat().st_mtime)
|
||||
if not cands:
|
||||
print("找不到 bench_*.csv; 请先跑 run_bench.py(默认会自动导出 CSV)",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
csv_path = cands[-1]
|
||||
if not csv_path.exists():
|
||||
print(f"CSV 不存在: {csv_path}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# ---- 定位输出目录 / 文件名前缀 ----
|
||||
out_dir = csv_path.parent
|
||||
prefix = csv_path.stem
|
||||
if args.out: # 旧用法: --out some.png
|
||||
op = Path(args.out)
|
||||
out_dir = op.parent if str(op.parent) not in ("", ".") else ROOT
|
||||
if op.suffix.lower() == ".png":
|
||||
prefix = op.stem
|
||||
if args.out_dir:
|
||||
out_dir = Path(args.out_dir)
|
||||
if not out_dir.is_absolute():
|
||||
out_dir = ROOT / out_dir
|
||||
if args.prefix:
|
||||
prefix = args.prefix
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
args.csv_name = csv_path.name
|
||||
|
||||
data = load_csv(csv_path)
|
||||
if not data:
|
||||
print(f"CSV 里没有有效样本: {csv_path}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
sizes = sorted({k[1] for k in data})
|
||||
# ADD/MUL/SQRT 的内核变体只在图1 出现 —— 混进图2/3/4 会把曲线糊成一团
|
||||
kernel_impls = {n for _, simd_n, native_n in KERNEL_PAIRS for n in (simd_n, native_n)}
|
||||
known = [name for name, _, _ in IMPL_STYLE]
|
||||
impls = [n for n in known if any(k[0] == n for k in data)]
|
||||
impls += sorted({k[0] for k in data} - set(known) - kernel_impls) # 未登记的实现也不丢
|
||||
|
||||
print(f"CSV : {csv_path}")
|
||||
print(f"尺寸 : {sizes}")
|
||||
print(f"实现 : {impls}")
|
||||
print(f"口径 : {args.metric} 输出目录: {out_dir} 前缀: {prefix}")
|
||||
|
||||
made = []
|
||||
if fig_simd_vs_scalar(data, sizes, args, args.metric,
|
||||
out_dir / f"{prefix}_1_simd_vs_scalar.png"):
|
||||
made.append(1)
|
||||
if fig_exec_time(data, sizes, impls, args, args.metric,
|
||||
out_dir / f"{prefix}_2_exec_time.png"):
|
||||
made.append(2)
|
||||
if fig_overall_mean(data, sizes, impls, args, args.metric,
|
||||
out_dir / f"{prefix}_3_overall_mean.png"):
|
||||
made.append(3)
|
||||
if fig_gflops(data, sizes, impls, args, args.metric,
|
||||
out_dir / f"{prefix}_4_gflops.png"):
|
||||
made.append(4)
|
||||
|
||||
# ---- 控制台再来一份文字小结, 免得每次都要开图 ----
|
||||
print(f"\n{'impl':<13} {'LEN':>9} {'工作集KiB':>10} {'每run us':>11} "
|
||||
f"{'GFLOPS':>8} {'CPU/墙钟':>9} {'trimmed':>8}")
|
||||
for s in sizes:
|
||||
for impl in impls:
|
||||
rec = data.get((impl, s))
|
||||
if not rec:
|
||||
continue
|
||||
wv = series(rec, "wall")
|
||||
cv = series(rec, "cpu")
|
||||
if wv.size == 0:
|
||||
continue
|
||||
flops_run = float(s) * float(rec["iters"]) * 2.0
|
||||
g = flops_run / (wv * 1e-6) / 1e9
|
||||
ratio = float(np.median(cv / wv[:cv.size])) if cv.size == wv.size else float("nan")
|
||||
print(f"{impl:<13} {s:>9} {s * 32 / 1024:>10.1f} {np.median(wv):>11.1f} "
|
||||
f"{np.mean(g):>8.2f} {ratio:>9.4f} {rec['trimmed']:>8}")
|
||||
print(f"\n共生成 {len(made)} 张图: {made}")
|
||||
return 0 if made else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user