# 100K batch benchmarks — 9 September 2026

These are fresh measurements of **100,000-contract BSM batches** at **4 and 10
Rayon worker threads**, using FerroRisk **0.38.4 source build
`301f5d0a4dadfcd00defebc805366afa66fd36cd`**. The source identity is retained even
if the release tag is published after this measurement.

- [summary.json](summary.json) contains displayed figures and source identities.
- [raw-results.json](raw-results.json) contains the four phases' original
  Criterion samples, estimates and workload records; before/after output
  checks; allocation probes; the measurement plan; and host observations.
- [website_batch_100k.rs](website_batch_100k.rs) is the exact compiled harness.
- [frozen-quotes.json](frozen-quotes.json) contains exact input and quote bits.

## Workload and timing boundary

Each batch alternates two fixed European Black-Scholes-Merton fixtures,
repeated 50,000 times each, in call/put order. These are the repository's BSM
batch fixtures, expanded to an actual vector of 100,000 contracts.

| Fixture | Type | Spot | Strike | Expiry (years) | Rate | Yield | Volatility |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| 0 | Call | 120 | 100 | 0.75 | 0.03 | 0.01 | 0.32 |
| 1 | Put | 95 | 100 | 1.25 | 0.01 | 0.02 | 0.19 |

Rates are continuously compounded decimal fractions. Volatility is a decimal
fraction. No discrete dividend schedule is supplied. The exact input bits and
model-generated quote bits are frozen before timing; these quotes are timing
inputs, not independent accuracy oracles.

**Full Greek sets:** `greeks_batch` consumes 100,000 `ResolvedGreekRequest`s.
Request construction and validation occur before timing. Each invocation
computes price and all ten Greeks for every contract and returns a vector of
100,000 results.

**Fused analytics:** `contract_analytics_batch` consumes 100,000 input/quote/model
tuples. Each invocation solves implied volatility, computes forward and all ten
Greeks for every contract. Fixture construction and initial quote generation
occur before timing. The IV solve and request validation performed by this
public API are included in the measurement.

Both timings include output-vector allocation and destruction. They exclude
transport, serialization, input construction, output verification and allocation
probes. The two workloads have different request boundaries; the fused timing
is not a sum of separately measured components.

The ten Greeks are delta, gamma, theta, vega, rho, vanna, volga, charm, veta and
color. Theta/charm/veta/color use calendar-day units; vega and rho are per unit
volatility/rate, not per volatility point or basis point. Price and forward use
the input monetary unit. All 100,000 results must succeed and agree with the
corresponding scalar calculation, before and after each timing phase.

## Measurement and host

Apple M1 Pro, 10 hardware cores, MacBookPro18,1, 16 GiB, macOS 26.6.2. Rust/Cargo
1.97.1, Cargo `bench` profile, engine default + SIMD features. These APIs use
Rayon over scalar kernels; enabling SIMD does not make this a measurement of
the separate `greeks_batch_simd` API.

One executable runs in **4 → 10 → 10 → 4** worker-thread order. Each phase uses
60 Criterion samples, a one-second warmup and at least three seconds of
measurement per workload. Batch workloads use flat sampling. Cards round the
arithmetic midpoint of the two phase medians for each thread count to one
decimal millisecond. The displayed range spans those two medians; it is not a
confidence interval. Contracts per second use **100,000 / seconds per batch**,
then convert to millions and round to one decimal.

Timing began immediately at the user's direction under normal host activity,
with AC power required. CPU, worker and thermal observations were retained;
the earlier CPU/worker admission screen and 90-second quiet window were not
enforced. The original CPU thresholds in the retained plan are diagnostic only.
This is an observed-host measurement, not an isolated-host run or a calibrated
release budget. Die temperature was not measured.

Across the 31 timing observations, peak unrelated per-process CPU reached
104.4% and aggregate unrelated CPU reached 225.7% (100% is one core). No
compiler/fuzz/reference-worker or thermal warning was recorded.

An independent arithmetic control must stay within 5% between repeated phases
at each thread count. All output checks remain required. A collector filename
error interrupted collection after the first phase; that completed phase was
preserved and collection resumed with the same executable and frozen inputs.

Allocation probes are separate from timing, after warming both operations and
the Rayon pool. Each thread count has three probes of one full batch per
operation. A 1,024-byte positive control checks that the allocator observer
records an allocation. The eight counters, in order, are allocation calls and
bytes, zeroed-allocation calls and bytes, reallocation calls and requested
bytes, and deallocation calls and bytes. The system-allocator forwarding code
is reused from the existing scalar measurement harness; it preserves the
allocator's pointer/layout contract and only updates atomic counters. Counters
are disabled during Criterion timing.

## Reproduction

Source access to the FerroRisk engine repository is required. In a clean
checkout of the revision above, copy the harness to
`crates/ferro-risk/benches/website_batch_100k.rs` and append to that package's
`Cargo.toml`:

```toml
[[bench]]
name = "website_batch_100k"
harness = false
```

Use the committed lockfile and pinned toolchain, with a fresh `CARGO_TARGET_DIR`
so existing evidence is preserved. Build once, before timing:

```sh
cargo rustc --locked -p ferro-risk --bench website_batch_100k \
  --profile bench --message-format=json-render-diagnostics > build.jsonl
```

Use the executable from the `website_batch_100k` compiler-artifact record.
Download the frozen quote file and set `FERRO_BATCH_QUOTES` to its absolute
path. Set `RAYON_NUM_THREADS=4` or `10` before every invocation. Run the
executable with `--preflight` and `--allocations` outside timing, retaining the
outputs. Its `--help` and `--version` require no fixture setup.

For each phase, use a new, exclusive `CRITERION_HOME` directory and run the
prebuilt executable:

```sh
FERRO_BATCH_RUN=1 /absolute/path/to/website_batch_100k \
  --bench --noplot --sample-size 60 --measurement-time 3 --warm-up-time 1
```

Repeat `--preflight` after each phase. Preserve build, host, output and
allocation records. Execute all four phases in the documented order. For a controlled-host
comparison, exclude concurrent builds, tests, fuzzing and other numerical work;
this published run used the user-directed host conditions described above. The website's
`npm run check:benchmarks` independently recomputes the medians and throughput,
checks 100,000 successful results and fixed thread counts, verifies the
arithmetic controls and checks the evidence hashes.
