fix(benchmark): 简化显示纯耗时, 修复 pipefail/BOA源/JerryScript编译/录入值

- 输出移除 X.Xx 倍率, 只显示耗时对比
- set -o pipefail 避免 cargo 编译失败被 pipe 吞掉
- BOA 改用 boa/ dev 源码 (兼容 Rust 1.96+)
- build.sh 修复 IFS='|' 分隔, 解决多词参数被拆碎
- 加入 pkg-config/cmake/nodejs 依赖, 支持 BOA/JerryScript/V8
- test262 各引擎全部改为录入值, 仅 OxideJS 现场跑

(cherry picked from commit d64c6577e768195687b182a3ccb6ef36d318e905)
This commit is contained in:
爱特每羊队
2026-06-29 15:54:07 +08:00
parent c61442dbf0
commit 0d926d6ac2
2 changed files with 38 additions and 89 deletions
+17 -17
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# OxideJS Benchmark Suite — 一键构建
# 兼容: Debian/Ubuntu, RHEL/Fedora, Arch, Alpine, openSUSE
set -e
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
@@ -13,23 +13,23 @@ step() { echo -e "\n${BOLD}${CYAN}═══ $* ═══${NC}"; }
# ── 包管理器检测 ─────────────────────────────────────────────────────
detect_pm() {
if command -v apt-get &>/dev/null; then echo "apt" "apt-get install -y" "apt-get update -qq"
elif command -v dnf &>/dev/null; then echo "dnf" "dnf install -y" "dnf check-update 2>/dev/null || true"
elif command -v yum &>/dev/null; then echo "yum" "yum install -y" "yum check-update 2>/dev/null || true"
elif command -v pacman &>/dev/null; then echo "pacman" "pacman -S --noconfirm" "pacman -Sy --noconfirm"
elif command -v apk &>/dev/null; then echo "apk" "apk add --no-cache" "apk update"
elif command -v zypper &>/dev/null; then echo "zypper" "zypper install -y" "zypper refresh"
else echo "unknown" "" ""
if command -v apt-get &>/dev/null; then echo "apt|apt-get install -y|apt-get update -qq"
elif command -v dnf &>/dev/null; then echo "dnf|dnf install -y|dnf check-update 2>/dev/null || true"
elif command -v yum &>/dev/null; then echo "yum|yum install -y|yum check-update 2>/dev/null || true"
elif command -v pacman &>/dev/null; then echo "pacman|pacman -S --noconfirm|pacman -Sy --noconfirm"
elif command -v apk &>/dev/null; then echo "apk|apk add --no-cache|apk update"
elif command -v zypper &>/dev/null; then echo "zypper|zypper install -y|zypper refresh"
else echo "unknown||"
fi
}
map_pkgs() {
case "$1" in
apt) echo "gcc make git python3 python3-pip curl pkg-config cmake";;
dnf|yum) echo "gcc make git python3 python3-pip curl pkgconfig cmake";;
pacman) echo "gcc make git python python-pip curl pkgconf cmake";;
apk) echo "gcc make git python3 py3-pip curl pkgconfig cmake";;
zypper) echo "gcc make git python3 python3-pip curl pkg-config cmake";;
apt) echo "gcc make git python3 python3-pip curl pkg-config cmake nodejs";;
dnf|yum) echo "gcc make git python3 python3-pip curl pkgconfig cmake nodejs";;
pacman) echo "gcc make git python python-pip curl pkgconf cmake nodejs";;
apk) echo "gcc make git python3 py3-pip curl pkgconfig cmake nodejs";;
zypper) echo "gcc make git python3 python3-pip curl pkg-config cmake nodejs";;
*) echo "gcc make git python3 curl";;
esac
}
@@ -103,10 +103,10 @@ build_quickjs() {
# ── BOA ──────────────────────────────────────────────────────────────────
build_boa() {
step "BOA (baseline v0.21)"
local boa_dir="$REPO_ROOT/baseline-boa"
step "BOA (dev)"
local boa_dir="$REPO_ROOT/boa"
if [ ! -d "$boa_dir" ]; then
warn "baseline-boa/ 不存在,跳过"
warn "boa/ 不存在,跳过"
return 0
fi
if [ -f "$boa_dir/target/release/boa" ]; then
@@ -188,7 +188,7 @@ echo -e "${BOLD}${CYAN}╚══════════════════
step "检测环境"
echo " OS: $(uname -a 2>/dev/null | cut -d' ' -f1-3)"
echo " CPU: $(nproc 2>/dev/null || echo '?') cores"
read pm icmd ucmd <<< $(detect_pm)
IFS='|' read pm icmd ucmd <<< $(detect_pm)
info "包管理器: $pm"
install_deps "$pm" "$icmd" "$ucmd" $(map_pkgs "$pm")
+21 -72
View File
@@ -31,18 +31,12 @@ RESULTS_DIR = REPO_ROOT / "benchmark" / "results"
class Engine:
"""A JS engine available for benchmarking."""
def __init__(self, key, name, exe, run_cmd, *,
test262_exe=None, test262_rate=None,
test262_runner_is_conf=False, test262_conf=None, test262_cwd=None):
def __init__(self, key, name, exe, run_cmd, *, test262_rate=None):
self.key = key
self.name = name
self.exe = Path(exe) if isinstance(exe, str) else exe
self._run_cmd = run_cmd # list of str, {exe} and {file} placeholders
self.test262_exe = Path(test262_exe) if test262_exe else None
self.test262_rate = test262_rate # pre-recorded pass rate or None
self.test262_runner_is_conf = test262_runner_is_conf
self.test262_conf = test262_conf
self.test262_cwd = test262_cwd
@property
def available(self):
@@ -58,7 +52,7 @@ class Engine:
f.write(js); tmp = f.name
try:
if self.key == "v8":
rc, out, err, t = run(["node", "-p", js], timeout=timeout)
rc, out, err, t = run(["node", tmp], timeout=timeout)
else:
cmd = [str(self.exe) if p == "{exe}" else (tmp if p == "{file}" else p) for p in self._run_cmd]
rc, out, err, t = run(cmd, timeout=timeout)
@@ -73,65 +67,35 @@ class Engine:
cmd = [str(self.exe) if p == "{exe}" else (str(file_path) if p == "{file}" else p) for p in self._run_cmd]
return run(cmd, timeout=timeout)
def run_test262(self, timeout=7200):
"""Run test262 suite. Returns (pass, fail, skip, total, elapsed_sec) or None."""
if self.test262_rate is not None:
return None # pre-recorded, no runner
if not self.test262_exe or not self.test262_exe.exists():
return None
if self.test262_runner_is_conf:
cwd = self.test262_cwd or self.exe.parent
rc, out, err, _ = run([str(self.test262_exe), "-c", str(self.test262_conf)],
timeout=timeout, cwd=cwd)
else:
rc, out, err, _ = run([str(self.test262_exe), "--supervise", str(TEST262_DIR)],
timeout=timeout)
summary = {"pass": 0, "fail": 0, "skip": 0, "total": 0}
for line in (out + err).split('\n'):
for k in ("pass", "fail", "skip", "total"):
m = re.search(rf'{k}\s*:\s*(\d+)', line, re.IGNORECASE)
if m: summary[k] = max(summary[k], int(m.group(1)))
if summary["total"] == 0:
return None
return summary
# ── Engine registry ─────────────────────────────────────────────────────
# QuickJS (全平台, make 编译)
QJS_DIR = REPO_ROOT / "baseline-quickjs"
# QuickJS (全平台, make 编译) — test262 98.8% (ES2020 full, minus Annex B)
QUICKJS = Engine("quickjs", "QuickJS",
exe=QJS_DIR / "qjs",
exe=REPO_ROOT / "baseline-quickjs" / "qjs",
run_cmd=["{exe}", "{file}"],
test262_exe=QJS_DIR / "run-test262",
test262_runner_is_conf=True,
test262_conf="test262.conf",
test262_cwd=QJS_DIR,
test262_rate=94.5)
# BOA (全平台, cargo build)
BOA_DIR = REPO_ROOT / "baseline-boa"
# BOA (全平台, cargo build) — test262 ~85% (ES2020)
BOA = Engine("boa", "BOA",
exe=BOA_DIR / "target" / "release" / "boa",
exe=REPO_ROOT / "boa" / "target" / "release" / "boa",
run_cmd=["{exe}", "--strict", "{file}"],
test262_rate=85.0)
# JerryScript (全平台, cmake + make)
JERRY_DIR = REPO_ROOT / "baseline-jerryscript"
# JerryScript (全平台, cmake+make) — test262 ~98% (ES5.1 + partial ES6)
JERRY = Engine("jerry", "JerryScript",
exe=JERRY_DIR / "build" / "bin" / "MinSizeRel" / "jerry",
exe=REPO_ROOT / "baseline-jerryscript" / "build" / "bin" / "MinSizeRel" / "jerry",
run_cmd=["{exe}", "{file}"],
test262_rate=98.0)
# V8 via node (主流平台可选)
# V8 via node (主流平台可选) — test262 ~99.9%
V8 = Engine("v8", "V8(node)",
exe=Path("node"), # resolved via shutil_which
exe=Path("node"),
run_cmd=["node", "-p"],
test262_rate=99.9)
# Hermes (主流平台可选)
# Hermes (主流平台可选) — test262 ~90%
HERMES = Engine("hermes", "Hermes",
exe=Path("hermes"), # resolved via shutil_which
exe=Path("hermes"),
run_cmd=["{exe}", "{file}"],
test262_rate=90.0)
@@ -219,7 +183,7 @@ def bench_timing():
if avg and avg != float('inf'):
ratio = ox_avg / avg
row[f"{eng.key}_ratio"] = round(ratio, 1)
parts.append(f" {eng.name} {avg:.3f}ms {ratio:.1f}x")
parts.append(f" {eng.name} {avg:.3f}ms")
else:
row[f"{eng.key}_ratio"] = 0
parts.append(f" {eng.name} N/A")
@@ -255,7 +219,7 @@ def bench_stress():
if val and val != float('inf'):
ratio = ox / val
row[f"{eng.key}_ratio"] = round(ratio, 1)
parts.append(f" {eng.name} {val:.1f}ms {ratio:.1f}x")
parts.append(f" {eng.name} {val:.1f}ms")
else:
row[f"{eng.key}_ratio"] = 0
parts.append(f" {eng.name} N/A")
@@ -296,28 +260,11 @@ def bench_test262_oxide():
return summary
def bench_test262_baselines():
"""各引擎 test262 成绩汇总 — 运行 runner 或使用已知通过率"""
print("\n" + "=" * 60)
print(" 3b. Test262 — 各引擎")
print("=" * 60)
"""各引擎 test262 成绩 — 全部使用已知通过率,不实际跑"""
results = {}
for eng in ALL_ENGINES:
if eng.test262_rate is not None:
results[eng.key] = {"rate": eng.test262_rate, "note": f"{eng.name} 录入值"}
print(f" {eng.name:15s} {eng.test262_rate:.1f}% (录入值)")
elif eng.available:
s = eng.run_test262()
if s:
ran = s['pass'] + s['fail']
rate = s['pass'] / ran * 100 if ran else 0
results[eng.key] = {"rate": round(rate, 1), "raw": s}
print(f" {eng.name:15s} {rate:.1f}%")
else:
results[eng.key] = {"rate": 0, "note": "runner 不可用"}
print(f" {eng.name:15s} 无法运行")
else:
results[eng.key] = {"rate": 0, "note": "引擎不可用"}
print(f" {eng.name:15s} N/A")
return results
def parse_jsonl(path):
@@ -504,20 +451,22 @@ def report(all_data):
timing = all_data.get("timing", [])
if timing:
w("## Single JS Execution\n")
w("| Scenario | OxideJS | QuickJS | Ratio |")
w("| Scenario | OxideJS | QuickJS ")
w("|------|---------|---------|------|")
for r in timing:
w(f"| {r['name']} | {r['ox_ms']:.3f}ms | {r['qjs_ms']:.3f}ms | {r['ratio']:.1f}x |")
qjs_v = r.get('quickjs_ms', 0); qjs_r = r.get('quickjs_ratio', 0)
w(f"| {r['name']} | {r['ox_ms']:.3f}ms | {qjs_v:.3f}ms |")
w("")
# Stress
stress = all_data.get("stress", [])
if stress:
w("## Stress Benchmarks\n")
w("| Test | OxideJS | QuickJS | Ratio |")
w("| Test | OxideJS | QuickJS ")
w("|------|---------|---------|------|")
for r in stress:
w(f"| {r['test']} | {r['ox_ms']:.1f}ms | {r['qjs_ms']:.1f}ms | {r['ratio']:.1f}x |")
qjs_v = r.get('quickjs_ms', 0); qjs_r = r.get('quickjs_ratio', 0)
w(f"| {r['test']} | {r['ox_ms']:.1f}ms | {qjs_v:.1f}ms |")
w("")
# Startup