212 lines
6.6 KiB
Python
212 lines
6.6 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import numpy as np
|
|
|
|
try:
|
|
from benchmarks.bench_batch import run_batch_benchmark
|
|
from benchmarks.bench_simd import run_simd_benchmark
|
|
from benchmarks.bench_streaming import run_streaming_benchmark
|
|
from benchmarks.bench_vs_talib import run_comparison
|
|
from benchmarks.metadata import benchmark_metadata, file_info
|
|
from benchmarks.profile_runtime_hotspots import build_hotspot_report
|
|
from benchmarks.test_benchmark_suite import (
|
|
FIXTURE_PATH,
|
|
INDICATOR_SUITE,
|
|
_run_indicator,
|
|
)
|
|
except ModuleNotFoundError: # pragma: no cover - script execution fallback
|
|
from bench_batch import run_batch_benchmark
|
|
from bench_simd import run_simd_benchmark
|
|
from bench_streaming import run_streaming_benchmark
|
|
from bench_vs_talib import run_comparison
|
|
from metadata import benchmark_metadata, file_info
|
|
from profile_runtime_hotspots import build_hotspot_report
|
|
from test_benchmark_suite import FIXTURE_PATH, INDICATOR_SUITE, _run_indicator
|
|
|
|
|
|
def _time_min(fn, rounds: int = 5) -> float:
|
|
fn()
|
|
samples: list[float] = []
|
|
for _ in range(rounds):
|
|
t0 = time.perf_counter()
|
|
fn()
|
|
samples.append(time.perf_counter() - t0)
|
|
return min(samples) * 1000.0
|
|
|
|
|
|
def build_indicator_latency_report(*, rounds: int = 5) -> dict[str, Any]:
|
|
if not FIXTURE_PATH.exists():
|
|
raise FileNotFoundError(
|
|
f"Canonical fixture not found: {FIXTURE_PATH}. "
|
|
"Run benchmarks/fixtures/generate_canonical.py first."
|
|
)
|
|
|
|
fixture = np.load(FIXTURE_PATH)
|
|
ohlcv = {key: fixture[key] for key in fixture.files}
|
|
|
|
rows: list[dict[str, Any]] = []
|
|
for entry in INDICATOR_SUITE:
|
|
elapsed_ms = _time_min(lambda entry=entry: _run_indicator(entry, ohlcv), rounds=rounds)
|
|
rows.append(
|
|
{
|
|
"name": entry["name"],
|
|
"inputs": entry["inputs"],
|
|
"kwargs": entry["kwargs"],
|
|
"elapsed_ms": round(elapsed_ms, 4),
|
|
}
|
|
)
|
|
|
|
rows.sort(key=lambda row: float(row["elapsed_ms"]), reverse=True)
|
|
return {
|
|
"metadata": benchmark_metadata(
|
|
"indicator_latency",
|
|
fixtures=[FIXTURE_PATH],
|
|
extra={
|
|
"dataset": {
|
|
"fixture": str(FIXTURE_PATH),
|
|
"bars": len(ohlcv["close"]),
|
|
"rounds": rounds,
|
|
}
|
|
},
|
|
),
|
|
"results": rows,
|
|
}
|
|
|
|
|
|
def _write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate reproducible performance baseline artifacts."
|
|
)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
default="benchmarks/artifacts/latest",
|
|
help="Directory where benchmark JSON artifacts are written",
|
|
)
|
|
parser.add_argument("--indicator-rounds", type=int, default=5)
|
|
parser.add_argument("--batch-samples", type=int, default=100_000)
|
|
parser.add_argument("--batch-series", type=int, default=100)
|
|
parser.add_argument("--batch-seed", type=int, default=42)
|
|
parser.add_argument("--streaming-bars", type=int, default=100_000)
|
|
parser.add_argument("--streaming-seed", type=int, default=2026)
|
|
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(
|
|
"--skip-simd",
|
|
action="store_true",
|
|
help="Skip portable-vs-SIMD comparison",
|
|
)
|
|
parser.add_argument(
|
|
"--talib-sizes",
|
|
type=int,
|
|
nargs="+",
|
|
default=[10_000, 100_000],
|
|
help="Bar counts used for the TA-Lib comparison suite",
|
|
)
|
|
parser.add_argument(
|
|
"--skip-talib",
|
|
action="store_true",
|
|
help="Skip the TA-Lib comparison artifact",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
output_dir = Path(args.output_dir)
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
artifacts: dict[str, str] = {}
|
|
|
|
indicator_path = output_dir / "indicator_latency.json"
|
|
_write_json(
|
|
indicator_path,
|
|
build_indicator_latency_report(rounds=args.indicator_rounds),
|
|
)
|
|
artifacts["indicator_latency"] = str(indicator_path)
|
|
|
|
batch_path = output_dir / "batch.json"
|
|
_write_json(
|
|
batch_path,
|
|
run_batch_benchmark(
|
|
n_samples=args.batch_samples,
|
|
n_series=args.batch_series,
|
|
seed=args.batch_seed,
|
|
),
|
|
)
|
|
artifacts["batch"] = str(batch_path)
|
|
|
|
streaming_path = output_dir / "streaming.json"
|
|
_write_json(
|
|
streaming_path,
|
|
run_streaming_benchmark(
|
|
n_bars=args.streaming_bars,
|
|
seed=args.streaming_seed,
|
|
),
|
|
)
|
|
artifacts["streaming"] = str(streaming_path)
|
|
|
|
hotspot_path = output_dir / "runtime_hotspots.json"
|
|
_write_json(
|
|
hotspot_path,
|
|
build_hotspot_report(
|
|
price_bars=args.price_bars,
|
|
iv_bars=args.iv_bars,
|
|
window=args.window,
|
|
),
|
|
)
|
|
artifacts["runtime_hotspots"] = str(hotspot_path)
|
|
|
|
if not args.skip_simd:
|
|
simd_path = output_dir / "simd.json"
|
|
_write_json(
|
|
simd_path,
|
|
run_simd_benchmark(
|
|
price_bars=args.price_bars,
|
|
iv_bars=args.iv_bars,
|
|
window=args.window,
|
|
),
|
|
)
|
|
artifacts["simd"] = str(simd_path)
|
|
|
|
if not args.skip_talib:
|
|
talib_path = output_dir / "benchmark_vs_talib.json"
|
|
run_comparison(args.talib_sizes, str(talib_path))
|
|
artifacts["benchmark_vs_talib"] = str(talib_path)
|
|
|
|
wasm_path = output_dir / "wasm.json"
|
|
if wasm_path.exists():
|
|
artifacts["wasm"] = str(wasm_path)
|
|
|
|
manifest = {
|
|
"metadata": benchmark_metadata(
|
|
"perf_contract",
|
|
fixtures=[FIXTURE_PATH],
|
|
extra={"output_dir": str(output_dir)},
|
|
),
|
|
"artifacts": {
|
|
name: file_info(path)
|
|
for name, path in artifacts.items()
|
|
},
|
|
}
|
|
manifest_path = output_dir / "manifest.json"
|
|
_write_json(manifest_path, manifest)
|
|
|
|
print(f"Generated performance contract artifacts in {output_dir}")
|
|
for name, path in artifacts.items():
|
|
print(f" - {name}: {path}")
|
|
print(f" - manifest: {manifest_path}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|