aacb9280f1
## Summary An honest, tiered cross-library benchmark — and the optimization pass it triggered. ### Performance (wickra-core, outputs unchanged) Profiling against the other Rust TA crates exposed real inefficiencies. Each benchmarked indicator is now **5–79% faster** in both streaming and batch: - **SMA, Bollinger**: flat `Box<[f64]>` ring buffers replace `VecDeque` (−69…79%). - **RSI**: `100·ag/(ag+al)` collapses three divisions into one; Wilder smoothing hoists `1/period` out of the hot path (−46%). - **ATR**: reciprocal hoisted (−42%). - **EMA/RSI/ATR**: per-tick `Option<f64>` hot state → bare `f64` + ready flag. Net result vs `kand`: Wickra now wins **RSI, Bollinger and ATR** (streaming), and ties `ta-rs` on SMA — up from losing every indicator 1.5–6× before. ### Benchmark harness New `crates/wickra-bench` (publish=false): a Criterion benchmark comparing Wickra against `kand`, `ta-rs` and `yata` on an identical BTCUSDT candle series, in streaming and batch modes. Peer APIs were verified against their source, not guessed. Wired into the nightly `cross-library-bench` workflow as a separate job. ### Honest README The benchmark section is rewritten into three layered tables (Rust core vs Rust crates; Python vs the Python ecosystem) that **show the losses as well as the wins**. The "only library that combines…" claim is gone; the new framing is breadth + multi-language reach + the deliberate safety trade-off that costs raw speed. Added an origin/why-slower rationale and a star CTA. ### Python benchmark Added `tulipy` runners and expanded per-tick streaming coverage to SMA/EMA/RSI/ MACD/Bollinger. `bench.in`/`bench.txt` now lock `TA-Lib` + `tulipy` (hash-pinned); `pandas-ta` stays out (it requires Python ≥ 3.12, the bench runs on 3.11). ### Notes - TA-Lib/tulipy numbers in the README Python table are marked ⧗ — they are produced by the CI Linux job (C extensions don't build cleanly on every desktop), not measured locally. - The matching `wickra-docs` prose update is committed separately and will be pushed with the release, per the docs-don't-lead-the-registries rule. Verified locally: `cargo fmt`, `cargo test --workspace --all-features` (3413 core + bindings), `cargo clippy --workspace --all-targets --all-features -D warnings`, Node build + 498 tests, and pytest all green.
146 lines
5.8 KiB
YAML
146 lines
5.8 KiB
YAML
name: Cross-library benchmark
|
||
|
||
# Audit finding R10: previously the cross-library benchmark ran on every push
|
||
# and every pull-request to `main`, adding 5–10 minutes of build + bench time
|
||
# per CI run with no consumer of the resulting artefact. The benchmark is now
|
||
# scheduled (nightly at 03:00 UTC) and on-demand via `workflow_dispatch`. The
|
||
# CI pipeline proper (.github/workflows/ci.yml) still verifies build / tests /
|
||
# lints on every push and pull-request.
|
||
on:
|
||
schedule:
|
||
# Nightly at 03:00 UTC. Pick a slot well away from common European /
|
||
# American working-hours pushes to keep this off the critical path.
|
||
- cron: "0 3 * * *"
|
||
workflow_dispatch:
|
||
inputs:
|
||
size:
|
||
description: "Number of bars per indicator (default 20000)"
|
||
required: false
|
||
default: "20000"
|
||
iterations:
|
||
description: "Batch iterations per indicator (default 10)"
|
||
required: false
|
||
default: "10"
|
||
|
||
# Least-privilege default for the auto-injected GITHUB_TOKEN. The single job
|
||
# only builds and uploads an artifact (upload-artifact uses the artifact
|
||
# storage API, not the contents scope), so it never needs repo write (OpenSSF
|
||
# Scorecard: Token-Permissions).
|
||
permissions:
|
||
contents: read
|
||
|
||
env:
|
||
CARGO_TERM_COLOR: always
|
||
# Network-flake resilience: retry transient registry/DNS failures at the tool
|
||
# level so a blip fetching crates.io / PyPI inside any build step (cargo,
|
||
# maturin, pip) retries automatically instead of failing the job. Cargo treats
|
||
# "couldn't resolve host" / connect / timeout as spurious and retries with
|
||
# backoff; 10 attempts ride out a transient DNS blip on a runner.
|
||
CARGO_NET_RETRY: "10"
|
||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||
npm_config_fetch_retries: "5"
|
||
npm_config_fetch_retry_maxtimeout: "120000"
|
||
PIP_RETRIES: "5"
|
||
PIP_DEFAULT_TIMEOUT: "120"
|
||
|
||
jobs:
|
||
cross-library-bench:
|
||
name: Cross-library benchmark report
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||
|
||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||
timeout-minutes: 6
|
||
|
||
- name: Set up Python
|
||
id: setup_python
|
||
continue-on-error: true
|
||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.11"
|
||
cache: pip
|
||
cache-dependency-path: .github/requirements/bench.txt
|
||
|
||
- name: Wait before Python retry
|
||
if: steps.setup_python.outcome == 'failure'
|
||
shell: bash
|
||
run: |
|
||
echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..."
|
||
sleep 30
|
||
|
||
- name: Set up Python (retry)
|
||
if: steps.setup_python.outcome == 'failure'
|
||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.11"
|
||
cache: pip
|
||
cache-dependency-path: .github/requirements/bench.txt
|
||
|
||
- name: Install Python deps + peer libs
|
||
run: |
|
||
python -m pip install --upgrade pip
|
||
# Hash-locked deps (OpenSSF Scorecard PinnedDependencies). bench.yml
|
||
# runs on a single Python version (3.11), so one lock file suffices.
|
||
python -m pip install --require-hashes -r .github/requirements/bench.txt
|
||
|
||
- name: Build Wickra wheel
|
||
working-directory: bindings/python
|
||
run: maturin build --release --out dist
|
||
|
||
- name: Install Wickra wheel
|
||
working-directory: bindings/python
|
||
run: python -m pip install --find-links dist --force-reinstall wickra
|
||
|
||
- name: Run cross-library benchmark
|
||
working-directory: bindings/python
|
||
# workflow_dispatch inputs are untrusted; pass them through the
|
||
# environment and quote them rather than interpolating into the shell
|
||
# command (OpenSSF Scorecard: Dangerous-Workflow).
|
||
env:
|
||
BENCH_SIZE: ${{ github.event.inputs.size || '20000' }}
|
||
BENCH_ITERATIONS: ${{ github.event.inputs.iterations || '10' }}
|
||
run: |
|
||
python -m benchmarks.compare_libraries \
|
||
--size "$BENCH_SIZE" \
|
||
--iterations "$BENCH_ITERATIONS" \
|
||
--streaming-window 5000 --streaming-iterations 2 \
|
||
| tee benchmark.txt
|
||
|
||
- name: Upload report
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: cross-library-bench
|
||
path: bindings/python/benchmark.txt
|
||
|
||
rust-cross-bench:
|
||
name: Rust cross-library benchmark report
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||
|
||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||
timeout-minutes: 6
|
||
|
||
# Wickra vs the other Rust TA crates (kand, ta-rs, yata) on an identical
|
||
# candle series — the like-for-like engine comparison with no binding
|
||
# overhead. Streaming + batch, in crates/wickra-bench/benches/cross_lib.rs.
|
||
- name: Run Rust cross-library benchmark
|
||
run: cargo bench -p wickra-bench --bench cross_lib | tee rust_cross_bench.txt
|
||
|
||
- name: Upload Rust report
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: rust-cross-bench
|
||
path: rust_cross_bench.txt
|