Files
2026-07-09 05:08:16 +08:00

158 lines
4.6 KiB
Python

from __future__ import annotations
import argparse
import json
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any
try:
from benchmarks.metadata import benchmark_metadata
except ModuleNotFoundError: # pragma: no cover - script execution fallback
from metadata import benchmark_metadata
ROOT = Path(__file__).resolve().parents[1]
def _run(cmd: list[str], *, cwd: Path = ROOT) -> None:
subprocess.run(cmd, cwd=cwd, check=True)
def _profile_variant(
*,
label: str,
maturin_args: list[str],
price_bars: int,
iv_bars: int,
window: int,
) -> dict[str, Any]:
_run([sys.executable, "-m", "maturin", "develop", "--release", *maturin_args])
with tempfile.TemporaryDirectory(prefix=f"ferro_ta_{label}_") as tmp_dir:
json_path = Path(tmp_dir) / "runtime_hotspots.json"
_run(
[
sys.executable,
"benchmarks/profile_runtime_hotspots.py",
"--price-bars",
str(price_bars),
"--iv-bars",
str(iv_bars),
"--window",
str(window),
"--json",
str(json_path),
]
)
payload = json.loads(json_path.read_text(encoding="utf-8"))
return payload
def run_simd_benchmark(
*,
price_bars: int = 20_000,
iv_bars: int = 50_000,
window: int = 252,
) -> dict[str, Any]:
# `simd` is a default feature, so a pure-scalar baseline must explicitly
# opt out via --no-default-features; otherwise both builds would be
# identical and every reported speedup would collapse to 1.0.
variants = [
("portable_release", ["--no-default-features"]),
("simd_release", ["--features", "simd"]),
]
reports = {
label: _profile_variant(
label=label,
maturin_args=args,
price_bars=price_bars,
iv_bars=iv_bars,
window=window,
)
for label, args in variants
}
portable_rows = {row["name"]: row for row in reports["portable_release"]["results"]}
simd_rows = {row["name"]: row for row in reports["simd_release"]["results"]}
comparison: list[dict[str, Any]] = []
for name in sorted(portable_rows):
portable = portable_rows[name]
simd = simd_rows.get(name)
if simd is None:
continue
portable_ms = float(portable["fast_ms"])
simd_ms = float(simd["fast_ms"])
comparison.append(
{
"name": name,
"category": portable["category"],
"portable_ms": round(portable_ms, 4),
"simd_ms": round(simd_ms, 4),
"speedup_simd_vs_portable": round(
portable_ms / simd_ms if simd_ms > 0.0 else float("inf"), 4
),
}
)
comparison.sort(
key=lambda row: float(row["speedup_simd_vs_portable"]), reverse=True
)
# Restore the default portable editable build so the workspace ends in the
# distributable configuration.
_run([sys.executable, "-m", "maturin", "develop", "--release"])
return {
"metadata": benchmark_metadata(
"simd",
extra={
"dataset": {
"price_bars": price_bars,
"iv_bars": iv_bars,
"window": window,
},
"variants": [label for label, _ in variants],
},
),
"results": comparison,
"reports": reports,
}
def main() -> int:
parser = argparse.ArgumentParser(
description="Benchmark portable vs SIMD-enabled ferro-ta builds."
)
parser.add_argument("--price-bars", type=int, default=20_000)
parser.add_argument("--iv-bars", type=int, default=50_000)
parser.add_argument("--window", type=int, default=252)
parser.add_argument("--json", dest="json_path")
args = parser.parse_args()
payload = run_simd_benchmark(
price_bars=args.price_bars,
iv_bars=args.iv_bars,
window=args.window,
)
print(f"{'Case':<20} {'Portable (ms)':>14} {'SIMD (ms)':>12} {'SIMD speedup':>14}")
print("-" * 64)
for row in payload["results"]:
print(
f"{row['name']:<20} {row['portable_ms']:14.4f} "
f"{row['simd_ms']:12.4f} {row['speedup_simd_vs_portable']:14.2f}x"
)
if args.json_path:
path = Path(args.json_path)
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
print(f"\nWrote JSON results to {path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())