From 45d66380997d5e91131e48aa59d8b1692145e43b Mon Sep 17 00:00:00 2001 From: human9000 Date: Mon, 17 Aug 2026 13:55:31 +0200 Subject: [PATCH] bench: install mimalloc in every harness, not just two MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `#[global_allocator]` lived in exactly two places — `apps/bench/src/main.rs` and `benchmarks/onnx-models/src/main.rs`. Bench, example, and every `src/bin/*.rs` target is its own crate root, so a declaration in a package's `main.rs` never reaches its siblings. `benchmarks/onnx-models` was therefore split three ways against itself: the bin ran mimalloc while `benches/onnx_models.rs` and `examples/bench_yolo.rs` ran the system allocator, the latter being exactly what `scripts/bench-all-competitors.sh` measures for the published "yscv CPU" number. All 17 criterion benches behind the CI regression gates were on system malloc, as were the `apps/llm-bench` harnesses — `compute_gap` and `bench_tracker` — that produced the single-op and Siamese tracker tables in `docs/performance-benchmarks.md`, whose methodology section nonetheless claimed a mimalloc global allocator. The gate that decides whether a hot-path change regressed and the harness whose numbers go in the commit message did not agree on the allocator. `benchmarks/rust-competitors` had no declaration either, so `run-suite.sh` compared yscv-on-mimalloc against tract-on-system-malloc. Hoist mimalloc into `[workspace.dependencies]` so the version is stated once, and declare it in all 39 measurement entry points: the 17 criterion benches, `fpv-latency`, the 7 llm-bench bins, `bench_yolo`, the 9 `bench_*` examples, and the tract harness. The library crates keep no declaration on purpose — a `#[global_allocator]` is a whole-program singleton, so a library that sets one forces it on every consumer and hard-fails the link against any binary that sets its own; it would also put libmimalloc-sys's C toolchain in the dependency path of the published crates and of the RISC-V/WASM fallback targets. README and `docs/architecture.md` relabel the `Allocator` row in the SIMD matrix to `Bench-harness allocator`, since sitting among per-platform kernel rows it read as a framework-wide property. The existing tables in `docs/performance-benchmarks.md` predate this change and are flagged in-place as needing a re-run before they can be compared against post-unification measurements. Validation: `scripts/check-ci-local.sh` default path clean; `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean; `cargo test --workspace` 53 targets, 0 failures. Criterion gates re-run for yscv-tensor, yscv-imgproc and yscv-kernels: 43/43 PASS against the existing absolute `max_upper_us` ceilings, which the allocator switch cannot silently invalidate since they are bounds rather than deltas. `nm` confirms 3 mimalloc symbols in the freshly built bench binaries against 0 in the pre-change hashes. `cargo +nightly miri test -p yscv-recognize` still builds and runs, since the allocator sits in the bench crate root and not the lib test target. --- Cargo.lock | 14 ++++++++++++++ Cargo.toml | 5 +++++ README.md | 12 +++++++++++- apps/bench/Cargo.toml | 2 +- apps/bench/src/fpv_latency.rs | 3 +++ apps/camera-face-tool/Cargo.toml | 1 + .../benches/camera_face_runtime_ops.rs | 3 +++ apps/llm-bench/Cargo.toml | 1 + apps/llm-bench/src/bin/bench_tracker.rs | 3 +++ apps/llm-bench/src/bin/calib_accuracy.rs | 3 +++ apps/llm-bench/src/bin/compute_gap.rs | 3 +++ apps/llm-bench/src/bin/inspect.rs | 3 +++ apps/llm-bench/src/bin/kernel_bench.rs | 3 +++ apps/llm-bench/src/bin/nchwc_coverage.rs | 3 +++ apps/llm-bench/src/bin/quantize_tracker.rs | 3 +++ apps/llm-bench/src/main.rs | 3 +++ benchmarks/onnx-models/benches/onnx_models.rs | 3 +++ benchmarks/onnx-models/examples/bench_yolo.rs | 3 +++ benchmarks/rust-competitors/Cargo.lock | 19 +++++++++++++++++++ benchmarks/rust-competitors/Cargo.toml | 3 +++ benchmarks/rust-competitors/src/main.rs | 3 +++ crates/yscv-autograd/Cargo.toml | 1 + .../benches/autograd_graph_ops.rs | 3 +++ crates/yscv-cli/Cargo.toml | 1 + crates/yscv-cli/benches/cli_runtime_ops.rs | 3 +++ crates/yscv-detect/Cargo.toml | 1 + .../benches/detect_heatmap_scratch.rs | 3 +++ crates/yscv-eval/Cargo.toml | 1 + crates/yscv-eval/benches/eval_metrics_ops.rs | 3 +++ crates/yscv-imgproc/Cargo.toml | 1 + crates/yscv-imgproc/benches/imgproc_morph.rs | 3 +++ crates/yscv-imgproc/benches/imgproc_ops.rs | 3 +++ crates/yscv-imgproc/benches/imgproc_u8.rs | 3 +++ crates/yscv-kernels/Cargo.toml | 1 + .../benches/int8_fused_dw_pw_3x3.rs | 3 +++ .../benches/int8_fused_pw_dw_3x3.rs | 3 +++ .../yscv-kernels/benches/kernels_cpu_ops.rs | 3 +++ crates/yscv-model/Cargo.toml | 1 + .../yscv-model/benches/model_runtime_ops.rs | 3 +++ crates/yscv-recognize/Cargo.toml | 1 + .../benches/recognize_slice_vs_tensor.rs | 3 +++ crates/yscv-tensor/Cargo.toml | 1 + .../benches/tensor_elementwise_modes.rs | 3 +++ crates/yscv-track/Cargo.toml | 1 + crates/yscv-track/benches/tracker_update.rs | 3 +++ crates/yscv-video/Cargo.toml | 1 + crates/yscv-video/benches/normalize_rgb8.rs | 3 +++ docs/architecture.md | 14 ++++++++++++-- docs/performance-benchmarks.md | 10 +++++++++- examples/Cargo.toml | 1 + examples/src/bench_gpu.rs | 3 +++ examples/src/bench_metal_conv.rs | 3 +++ examples/src/bench_metal_vball.rs | 3 +++ examples/src/bench_metal_yolo.rs | 3 +++ examples/src/bench_mps_gemm.rs | 3 +++ examples/src/bench_mpsgraph.rs | 3 +++ examples/src/bench_mpsgraph_only.rs | 3 +++ examples/src/bench_vball_cpu.rs | 3 +++ examples/src/bench_video_decode.rs | 3 +++ 59 files changed, 199 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46d7020d..17418b8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,7 @@ name = "camera-face-tool" version = "0.1.11" dependencies = [ "criterion", + "mimalloc", "serde_json", "thiserror", "yscv-detect", @@ -2745,6 +2746,7 @@ name = "yscv-autograd" version = "0.1.11" dependencies = [ "criterion", + "mimalloc", "thiserror", "yscv-kernels", "yscv-tensor", @@ -2770,6 +2772,7 @@ name = "yscv-cli" version = "0.1.11" dependencies = [ "criterion", + "mimalloc", "serde_json", "thiserror", "yscv-detect", @@ -2789,6 +2792,7 @@ name = "yscv-detect" version = "0.1.11" dependencies = [ "criterion", + "mimalloc", "proptest", "rustc-hash 2.1.3", "thiserror", @@ -2803,6 +2807,7 @@ version = "0.1.11" dependencies = [ "criterion", "csv", + "mimalloc", "roxmltree", "rustc-hash 2.1.3", "serde", @@ -2816,6 +2821,7 @@ dependencies = [ name = "yscv-examples" version = "0.1.11" dependencies = [ + "mimalloc", "rustc-hash 2.1.3", "thiserror", "yscv-autograd", @@ -2838,6 +2844,7 @@ version = "0.1.11" dependencies = [ "criterion", "image", + "mimalloc", "proptest", "rayon", "rustc-hash 2.1.3", @@ -2858,6 +2865,7 @@ dependencies = [ "foreign-types", "libc", "metal", + "mimalloc", "objc", "pollster", "proptest", @@ -2875,6 +2883,7 @@ dependencies = [ name = "yscv-llm-bench" version = "0.1.11" dependencies = [ + "mimalloc", "rustc-hash 2.1.3", "serde", "serde_json", @@ -2890,6 +2899,7 @@ version = "0.1.11" dependencies = [ "criterion", "image", + "mimalloc", "rustc-hash 2.1.3", "serde", "serde_json", @@ -2963,6 +2973,7 @@ name = "yscv-recognize" version = "0.1.11" dependencies = [ "criterion", + "mimalloc", "serde", "serde_json", "thiserror", @@ -2974,6 +2985,7 @@ name = "yscv-tensor" version = "0.1.11" dependencies = [ "criterion", + "mimalloc", "proptest", "rayon", "thiserror", @@ -2994,6 +3006,7 @@ name = "yscv-track" version = "0.1.11" dependencies = [ "criterion", + "mimalloc", "rustc-hash 2.1.3", "thiserror", "yscv-detect", @@ -3008,6 +3021,7 @@ dependencies = [ "bytes", "criterion", "libc", + "mimalloc", "nokhwa", "proptest", "rayon", diff --git a/Cargo.toml b/Cargo.toml index dae0b6f4..2eb5b418 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,11 @@ keywords = ["computer-vision", "deep-learning", "image-processing", "onnx", "vid [workspace.dependencies] thiserror = "2" rustc-hash = "2.1.3" +# Every benchmark and latency harness in the workspace installs this as its +# global allocator, so measurements are allocator-comparable across targets. +# Library crates deliberately do not — a library must leave that choice to the +# binary at the top of the dependency graph. +mimalloc = { version = "0.1", default-features = false } [workspace.lints.rust] unsafe_code = "deny" diff --git a/README.md b/README.md index e2885d1a..c09297b7 100644 --- a/README.md +++ b/README.md @@ -214,10 +214,20 @@ All hot paths have hand-tuned SIMD for three architectures with runtime CPU dete | **Threading** | GCD dispatch_apply (~0.3µs) | std::thread::scope (~1µs) | std::thread::scope (~1µs) | | **GPU inference** | MPSGraph | wgpu/Vulkan | wgpu/Vulkan | | **Softmax** | Fused NEON | Fused AVX/SSE | Fused NEON | -| **Allocator** | mimalloc | mimalloc | mimalloc | +| **Bench-harness allocator** | mimalloc | mimalloc | mimalloc | SIMD dispatch is automatic at runtime — no need for `-C target-cpu` flags (though they help: `-C target-cpu=apple-m1` or `-C target-cpu=native` for best codegen). The framework detects CPU features once through `yscv-cpu` and routes kernels through cached `host_cpu().features` gates. `yscv_kernels::runtime_dispatch_report()` exposes the typed CPU/kernel selection snapshot, while `runtime_config_report()` records active `YSCV_*` A/B overrides for reproducible benchmark logs. 570 `#[target_feature]`-gated functions total, all with scalar fallback for WASM/RISC-V/Miri. +The allocator row applies to the benchmark and latency harnesses, which all +install mimalloc so their numbers stay comparable. The library crates set no +global allocator — that is a whole-program choice, so it belongs to your +binary. If you want mimalloc in your own build, add the two lines yourself: + +```rust +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; +``` + ### Recommended release profile For best performance, the workspace already includes an optimized release profile in `Cargo.toml`: diff --git a/apps/bench/Cargo.toml b/apps/bench/Cargo.toml index bc4f0bda..90b4f97a 100644 --- a/apps/bench/Cargo.toml +++ b/apps/bench/Cargo.toml @@ -20,7 +20,7 @@ yscv-video = { path = "../../crates/yscv-video" } yscv-detect = { path = "../../crates/yscv-detect" } yscv-track = { path = "../../crates/yscv-track" } yscv-optim = { path = "../../crates/yscv-optim" } -mimalloc = { version = "0.1", default-features = false } +mimalloc.workspace = true [[bin]] name = "yscv-bench" diff --git a/apps/bench/src/fpv_latency.rs b/apps/bench/src/fpv_latency.rs index 0c47975e..650dfd4e 100644 --- a/apps/bench/src/fpv_latency.rs +++ b/apps/bench/src/fpv_latency.rs @@ -13,6 +13,9 @@ //! can model a real board's timing distribution without requiring that //! HW to be physically attached. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::{Duration, Instant}; diff --git a/apps/camera-face-tool/Cargo.toml b/apps/camera-face-tool/Cargo.toml index 0f853549..c7d7e486 100644 --- a/apps/camera-face-tool/Cargo.toml +++ b/apps/camera-face-tool/Cargo.toml @@ -22,6 +22,7 @@ thiserror.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true [[bench]] name = "camera_face_runtime_ops" diff --git a/apps/camera-face-tool/benches/camera_face_runtime_ops.rs b/apps/camera-face-tool/benches/camera_face_runtime_ops.rs index 088047c2..4759481e 100644 --- a/apps/camera-face-tool/benches/camera_face_runtime_ops.rs +++ b/apps/camera-face-tool/benches/camera_face_runtime_ops.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use serde_json::{Value, json}; use yscv_detect::{ diff --git a/apps/llm-bench/Cargo.toml b/apps/llm-bench/Cargo.toml index afd0a727..edf4915f 100644 --- a/apps/llm-bench/Cargo.toml +++ b/apps/llm-bench/Cargo.toml @@ -47,6 +47,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror.workspace = true rustc-hash.workspace = true +mimalloc.workspace = true [lints] workspace = true diff --git a/apps/llm-bench/src/bin/bench_tracker.rs b/apps/llm-bench/src/bin/bench_tracker.rs index 28d16a93..fbfcf54a 100644 --- a/apps/llm-bench/src/bin/bench_tracker.rs +++ b/apps/llm-bench/src/bin/bench_tracker.rs @@ -1,5 +1,8 @@ //! Minimal yscv tracker benchmark that uses the current workspace crates. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::path::Path; use std::process::ExitCode; use std::time::Instant; diff --git a/apps/llm-bench/src/bin/calib_accuracy.rs b/apps/llm-bench/src/bin/calib_accuracy.rs index 935e9f79..55cc8ca8 100644 --- a/apps/llm-bench/src/bin/calib_accuracy.rs +++ b/apps/llm-bench/src/bin/calib_accuracy.rs @@ -23,6 +23,9 @@ //! cargo run --release --bin calib_accuracy -p yscv-llm-bench //! ``` +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use yscv_onnx::quantize::{ CalibrationCollector, derive::{QuantTarget, derive_mse_optimal, derive_percentile, derive_symmetric}, diff --git a/apps/llm-bench/src/bin/compute_gap.rs b/apps/llm-bench/src/bin/compute_gap.rs index b80bd2d9..334f2a9a 100644 --- a/apps/llm-bench/src/bin/compute_gap.rs +++ b/apps/llm-bench/src/bin/compute_gap.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::time::Instant; use yscv_kernels::{ diff --git a/apps/llm-bench/src/bin/inspect.rs b/apps/llm-bench/src/bin/inspect.rs index af2c7730..5503b06b 100644 --- a/apps/llm-bench/src/bin/inspect.rs +++ b/apps/llm-bench/src/bin/inspect.rs @@ -2,6 +2,9 @@ //! of an ONNX model. Used to detect cached-decoder protocols //! (past_key_values.{i}.{key,value}) before wiring up the bench harness. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use yscv_onnx::load_onnx_model_from_file; fn main() { diff --git a/apps/llm-bench/src/bin/kernel_bench.rs b/apps/llm-bench/src/bin/kernel_bench.rs index 22ea0c2d..a2b31901 100644 --- a/apps/llm-bench/src/bin/kernel_bench.rs +++ b/apps/llm-bench/src/bin/kernel_bench.rs @@ -11,6 +11,9 @@ //! cargo run --release --bin kernel_bench -p yscv-llm-bench //! ``` +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::time::Instant; use yscv_kernels::{ diff --git a/apps/llm-bench/src/bin/nchwc_coverage.rs b/apps/llm-bench/src/bin/nchwc_coverage.rs index 43e0a86d..f5ac64f0 100644 --- a/apps/llm-bench/src/bin/nchwc_coverage.rs +++ b/apps/llm-bench/src/bin/nchwc_coverage.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use rustc_hash::FxHashMap; /// NCHWc layout coverage probe — Step 0 of NCHWc-everywhere plan. /// diff --git a/apps/llm-bench/src/bin/quantize_tracker.rs b/apps/llm-bench/src/bin/quantize_tracker.rs index f0bfcf3c..56808630 100644 --- a/apps/llm-bench/src/bin/quantize_tracker.rs +++ b/apps/llm-bench/src/bin/quantize_tracker.rs @@ -31,6 +31,9 @@ //! `calib_accuracy --model`. Without `--output` the QDQ save+reload step //! is skipped — useful when iterating on the calibration count alone. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::path::{Path, PathBuf}; use std::process::ExitCode; diff --git a/apps/llm-bench/src/main.rs b/apps/llm-bench/src/main.rs index f14b97b1..1781bf42 100644 --- a/apps/llm-bench/src/main.rs +++ b/apps/llm-bench/src/main.rs @@ -31,6 +31,9 @@ //! See `apps/llm-bench/README.md` for end-to-end download + quantize + //! bench instructions for TinyLlama-1.1B. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::path::PathBuf; use std::process::ExitCode; use std::time::Instant; diff --git a/benchmarks/onnx-models/benches/onnx_models.rs b/benchmarks/onnx-models/benches/onnx_models.rs index 59f6a26b..b7d9b449 100644 --- a/benchmarks/onnx-models/benches/onnx_models.rs +++ b/benchmarks/onnx-models/benches/onnx_models.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; use yscv_onnx::{OnnxModel, OnnxRunner, load_onnx_model_from_file, optimize_onnx_graph}; use yscv_onnx_model_bench::{asset_dir, download_assets, make_inputs, model_cases}; diff --git a/benchmarks/onnx-models/examples/bench_yolo.rs b/benchmarks/onnx-models/examples/bench_yolo.rs index 366ac2ee..b61d82da 100644 --- a/benchmarks/onnx-models/examples/bench_yolo.rs +++ b/benchmarks/onnx-models/examples/bench_yolo.rs @@ -3,6 +3,9 @@ //! Set BENCH_COOLDOWN= to insert a cooldown pause between benchmarks. //! The default is 0; callers that need thermal control must opt in. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use rustc_hash::FxHashMap; use yscv_onnx::load_onnx_model_from_file; use yscv_onnx_model_bench::{asset_dir, download_assets, make_inputs, model_cases}; diff --git a/benchmarks/rust-competitors/Cargo.lock b/benchmarks/rust-competitors/Cargo.lock index 296b2c2b..7608bddd 100644 --- a/benchmarks/rust-competitors/Cargo.lock +++ b/benchmarks/rust-competitors/Cargo.lock @@ -57,6 +57,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" name = "bench-competitors" version = "0.1.0" dependencies = [ + "mimalloc", "tract-onnx", ] @@ -351,6 +352,15 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "libmimalloc-sys" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" +dependencies = [ + "cc", +] + [[package]] name = "libredox" version = "0.1.15" @@ -472,6 +482,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mimalloc" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" +dependencies = [ + "libmimalloc-sys", +] + [[package]] name = "minimal-lexical" version = "0.2.1" diff --git a/benchmarks/rust-competitors/Cargo.toml b/benchmarks/rust-competitors/Cargo.toml index 9c4b04f1..4adb83b5 100644 --- a/benchmarks/rust-competitors/Cargo.toml +++ b/benchmarks/rust-competitors/Cargo.toml @@ -6,6 +6,9 @@ publish = false [dependencies] tract-onnx = { version = "0.21", optional = true } +# Matches the allocator the yscv harnesses use, so competitor numbers are not +# a yscv-on-mimalloc vs tract-on-system-malloc comparison. +mimalloc = { version = "0.1", default-features = false } [features] default = ["tract"] diff --git a/benchmarks/rust-competitors/src/main.rs b/benchmarks/rust-competitors/src/main.rs index 56c06898..26bd276c 100644 --- a/benchmarks/rust-competitors/src/main.rs +++ b/benchmarks/rust-competitors/src/main.rs @@ -1,6 +1,9 @@ //! Benchmark competitor ONNX runtimes (tract, candle) against yscv. //! Outputs JSON timing results for automated comparison. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::time::Instant; fn main() { diff --git a/crates/yscv-autograd/Cargo.toml b/crates/yscv-autograd/Cargo.toml index 77e9e5fa..43256281 100644 --- a/crates/yscv-autograd/Cargo.toml +++ b/crates/yscv-autograd/Cargo.toml @@ -17,6 +17,7 @@ thiserror.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true [[bench]] name = "autograd_graph_ops" diff --git a/crates/yscv-autograd/benches/autograd_graph_ops.rs b/crates/yscv-autograd/benches/autograd_graph_ops.rs index 7a0a33b7..9301ecbc 100644 --- a/crates/yscv-autograd/benches/autograd_graph_ops.rs +++ b/crates/yscv-autograd/benches/autograd_graph_ops.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_autograd::Graph; use yscv_tensor::Tensor; diff --git a/crates/yscv-cli/Cargo.toml b/crates/yscv-cli/Cargo.toml index 56ee034e..faeea84e 100644 --- a/crates/yscv-cli/Cargo.toml +++ b/crates/yscv-cli/Cargo.toml @@ -26,6 +26,7 @@ thiserror.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true [[bench]] name = "cli_runtime_ops" diff --git a/crates/yscv-cli/benches/cli_runtime_ops.rs b/crates/yscv-cli/benches/cli_runtime_ops.rs index c14bb009..a3163c41 100644 --- a/crates/yscv-cli/benches/cli_runtime_ops.rs +++ b/crates/yscv-cli/benches/cli_runtime_ops.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_detect::{ BoundingBox, CLASS_ID_FACE, CLASS_ID_PERSON, Rgb8FaceDetectScratch, Rgb8PeopleDetectScratch, diff --git a/crates/yscv-detect/Cargo.toml b/crates/yscv-detect/Cargo.toml index b33f3cad..6ba6fafb 100644 --- a/crates/yscv-detect/Cargo.toml +++ b/crates/yscv-detect/Cargo.toml @@ -22,6 +22,7 @@ rustc-hash.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true proptest = "1" [[bench]] diff --git a/crates/yscv-detect/benches/detect_heatmap_scratch.rs b/crates/yscv-detect/benches/detect_heatmap_scratch.rs index f0a16b81..8f5dbc8e 100644 --- a/crates/yscv-detect/benches/detect_heatmap_scratch.rs +++ b/crates/yscv-detect/benches/detect_heatmap_scratch.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_detect::{ FrameFaceDetectScratch, FramePeopleDetectScratch, HeatmapDetectScratch, diff --git a/crates/yscv-eval/Cargo.toml b/crates/yscv-eval/Cargo.toml index f47df32a..612cff86 100644 --- a/crates/yscv-eval/Cargo.toml +++ b/crates/yscv-eval/Cargo.toml @@ -22,6 +22,7 @@ rustc-hash.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true [[bench]] name = "eval_metrics_ops" diff --git a/crates/yscv-eval/benches/eval_metrics_ops.rs b/crates/yscv-eval/benches/eval_metrics_ops.rs index 88d97544..03eac74d 100644 --- a/crates/yscv-eval/benches/eval_metrics_ops.rs +++ b/crates/yscv-eval/benches/eval_metrics_ops.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::time::Duration; use criterion::{Criterion, black_box, criterion_group, criterion_main}; diff --git a/crates/yscv-imgproc/Cargo.toml b/crates/yscv-imgproc/Cargo.toml index 974de596..3e4711d6 100644 --- a/crates/yscv-imgproc/Cargo.toml +++ b/crates/yscv-imgproc/Cargo.toml @@ -20,6 +20,7 @@ rayon = "1.11" [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true proptest = "1" [[bench]] diff --git a/crates/yscv-imgproc/benches/imgproc_morph.rs b/crates/yscv-imgproc/benches/imgproc_morph.rs index b5d9e0ab..1a933878 100644 --- a/crates/yscv-imgproc/benches/imgproc_morph.rs +++ b/crates/yscv-imgproc/benches/imgproc_morph.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_imgproc::{ closing_3x3, dilate_3x3, erode_3x3, morph_gradient_3x3, opening_3x3, sobel_3x3_gradients, diff --git a/crates/yscv-imgproc/benches/imgproc_ops.rs b/crates/yscv-imgproc/benches/imgproc_ops.rs index 9672ce46..15128f0e 100644 --- a/crates/yscv-imgproc/benches/imgproc_ops.rs +++ b/crates/yscv-imgproc/benches/imgproc_ops.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{BatchSize, Criterion, black_box, criterion_group, criterion_main}; use yscv_imgproc::{ box_blur_3x3, flip_horizontal, flip_vertical, normalize, resize_nearest, rgb_to_grayscale, diff --git a/crates/yscv-imgproc/benches/imgproc_u8.rs b/crates/yscv-imgproc/benches/imgproc_u8.rs index e87a4b19..5443f0be 100644 --- a/crates/yscv-imgproc/benches/imgproc_u8.rs +++ b/crates/yscv-imgproc/benches/imgproc_u8.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_imgproc::{ ImageU8, box_blur_3x3_u8, dilate_3x3_u8, erode_3x3_u8, grayscale_u8, resize_bilinear_u8, diff --git a/crates/yscv-kernels/Cargo.toml b/crates/yscv-kernels/Cargo.toml index 45f39d6a..4541ab2b 100644 --- a/crates/yscv-kernels/Cargo.toml +++ b/crates/yscv-kernels/Cargo.toml @@ -54,6 +54,7 @@ cc = "1.0" [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true proptest = "1" [[bench]] diff --git a/crates/yscv-kernels/benches/int8_fused_dw_pw_3x3.rs b/crates/yscv-kernels/benches/int8_fused_dw_pw_3x3.rs index f9a3980c..c29b71f2 100644 --- a/crates/yscv-kernels/benches/int8_fused_dw_pw_3x3.rs +++ b/crates/yscv-kernels/benches/int8_fused_dw_pw_3x3.rs @@ -6,6 +6,9 @@ //! so the per-iter savings of the fused kernel are visible on top of the //! same SIMD primitives. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_kernels::{ DepthwiseI8Params, Int8FusedDwPwParams, depthwise_i8_i32_nhwc_dispatch, diff --git a/crates/yscv-kernels/benches/int8_fused_pw_dw_3x3.rs b/crates/yscv-kernels/benches/int8_fused_pw_dw_3x3.rs index abb3d525..87783b3b 100644 --- a/crates/yscv-kernels/benches/int8_fused_pw_dw_3x3.rs +++ b/crates/yscv-kernels/benches/int8_fused_pw_dw_3x3.rs @@ -8,6 +8,9 @@ //! the per-iter savings of the fused kernel are visible on top of the //! same SIMD primitives. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_kernels::{ DepthwiseI8Params, Int8FusedPwDwParams, depthwise_i8_i32_nhwc_dispatch, diff --git a/crates/yscv-kernels/benches/kernels_cpu_ops.rs b/crates/yscv-kernels/benches/kernels_cpu_ops.rs index 300b3ecf..4370e445 100644 --- a/crates/yscv-kernels/benches/kernels_cpu_ops.rs +++ b/crates/yscv-kernels/benches/kernels_cpu_ops.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::num::NonZeroUsize; use criterion::{Criterion, black_box, criterion_group, criterion_main}; diff --git a/crates/yscv-model/Cargo.toml b/crates/yscv-model/Cargo.toml index 19219f1d..8b20cc73 100644 --- a/crates/yscv-model/Cargo.toml +++ b/crates/yscv-model/Cargo.toml @@ -25,6 +25,7 @@ rustc-hash.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true [[bench]] name = "model_runtime_ops" diff --git a/crates/yscv-model/benches/model_runtime_ops.rs b/crates/yscv-model/benches/model_runtime_ops.rs index fbfba357..b9e3866e 100644 --- a/crates/yscv-model/benches/model_runtime_ops.rs +++ b/crates/yscv-model/benches/model_runtime_ops.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_autograd::Graph; use yscv_model::{SequentialModel, SupervisedLoss, train_step_sgd, train_step_sgd_with_loss}; diff --git a/crates/yscv-recognize/Cargo.toml b/crates/yscv-recognize/Cargo.toml index 8dd99653..63ca3905 100644 --- a/crates/yscv-recognize/Cargo.toml +++ b/crates/yscv-recognize/Cargo.toml @@ -18,6 +18,7 @@ thiserror.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true [[bench]] name = "recognize_slice_vs_tensor" diff --git a/crates/yscv-recognize/benches/recognize_slice_vs_tensor.rs b/crates/yscv-recognize/benches/recognize_slice_vs_tensor.rs index a230c645..dd703c69 100644 --- a/crates/yscv-recognize/benches/recognize_slice_vs_tensor.rs +++ b/crates/yscv-recognize/benches/recognize_slice_vs_tensor.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_recognize::Recognizer; use yscv_tensor::Tensor; diff --git a/crates/yscv-tensor/Cargo.toml b/crates/yscv-tensor/Cargo.toml index 8bad9564..e608760f 100644 --- a/crates/yscv-tensor/Cargo.toml +++ b/crates/yscv-tensor/Cargo.toml @@ -17,6 +17,7 @@ yscv-cpu = { version = "0.1", path = "../yscv-cpu" } [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true proptest = "1" [[bench]] diff --git a/crates/yscv-tensor/benches/tensor_elementwise_modes.rs b/crates/yscv-tensor/benches/tensor_elementwise_modes.rs index 6956f5c5..8adba3d5 100644 --- a/crates/yscv-tensor/benches/tensor_elementwise_modes.rs +++ b/crates/yscv-tensor/benches/tensor_elementwise_modes.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_tensor::Tensor; diff --git a/crates/yscv-track/Cargo.toml b/crates/yscv-track/Cargo.toml index 4c845efb..6ae1afbd 100644 --- a/crates/yscv-track/Cargo.toml +++ b/crates/yscv-track/Cargo.toml @@ -18,6 +18,7 @@ rustc-hash.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true [[bench]] name = "tracker_update" diff --git a/crates/yscv-track/benches/tracker_update.rs b/crates/yscv-track/benches/tracker_update.rs index 598f8f38..268cada1 100644 --- a/crates/yscv-track/benches/tracker_update.rs +++ b/crates/yscv-track/benches/tracker_update.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{BatchSize, Criterion, black_box, criterion_group, criterion_main}; use yscv_detect::{BoundingBox, CLASS_ID_PERSON, Detection}; use yscv_track::{Tracker, TrackerConfig}; diff --git a/crates/yscv-video/Cargo.toml b/crates/yscv-video/Cargo.toml index 2cb7be43..b6447f74 100644 --- a/crates/yscv-video/Cargo.toml +++ b/crates/yscv-video/Cargo.toml @@ -41,6 +41,7 @@ rustc-hash.workspace = true [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } +mimalloc.workspace = true proptest = "1" [[bench]] diff --git a/crates/yscv-video/benches/normalize_rgb8.rs b/crates/yscv-video/benches/normalize_rgb8.rs index e7692051..a8124dd7 100644 --- a/crates/yscv-video/benches/normalize_rgb8.rs +++ b/crates/yscv-video/benches/normalize_rgb8.rs @@ -1,3 +1,6 @@ +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use criterion::{Criterion, black_box, criterion_group, criterion_main}; use yscv_video::normalize_rgb8_to_f32_inplace; diff --git a/docs/architecture.md b/docs/architecture.md index 1d03a8c9..0cf21b99 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -83,7 +83,13 @@ Rayon is the cross-platform parallel backend. It provides work-stealing thread p ## Memory patterns -- **mimalloc** global allocator in benchmark harness for faster large allocations. +- **mimalloc** global allocator in every benchmark and latency harness — the + criterion benches, `apps/bench`, `apps/llm-bench`, `benchmarks/onnx-models`, + the `bench_*` examples, and the tract competitor harness — so numbers are + allocator-comparable no matter which entry point produced them. The library + crates deliberately declare no allocator: `#[global_allocator]` is a + whole-program singleton, so choosing one is the calling binary's decision, + not a library's. - `AlignedVec` in yscv-tensor (32-byte aligned for AVX) with `uninitialized(len)` to skip zeroing output buffers. - **Ring buffers** for streaming row processing (canny magnitude/direction, morph separable passes). - **Thread-local scratch** via rayon's per-task closures (not explicit thread-local storage). @@ -123,7 +129,11 @@ If you need to change something, these are the most important files: | MatMul BLAS | Accelerate cblas (always on) | OpenBLAS (opt-in `blas`) | OpenBLAS (opt-in `blas`) | | Softmax | Fused NEON | Fused NEON | Fused AVX/SSE | | Median u8 | NEON sort network | NEON sort network | SSE2 sort network | -| Allocator | mimalloc | mimalloc | mimalloc | +| Bench-harness allocator | mimalloc | mimalloc | mimalloc | + +The allocator row covers the benchmark and latency harnesses only; the library +crates leave the global allocator to the calling binary. See +[Memory patterns](#memory-patterns). All SIMD dispatch paths include scalar fallback for architectures without runtime detection support (e.g., RISC-V, WASM) and for Miri testing. diff --git a/docs/performance-benchmarks.md b/docs/performance-benchmarks.md index 07d863f8..51ce01ac 100644 --- a/docs/performance-benchmarks.md +++ b/docs/performance-benchmarks.md @@ -259,6 +259,13 @@ NEON polynomial approximations trading a float-tolerance error for speed. - **Single-op:** p50 of 1000 iterations after 200 warmup (300 on the A53), each op isolated in its own process to avoid cross-op cache/allocator contamination. - **Ratios** are `competitor / yscv`; >1.0 means yscv is faster. +- **Allocator caveat:** the tables above were captured while only `apps/bench` + and `benchmarks/onnx-models`' own binary installed mimalloc; the `compute_gap` + and `bench_tracker` harnesses that produced the single-op and tracker numbers + still ran on the system allocator, as did the tract competitor harness. All + harnesses now install mimalloc uniformly, so these numbers need a re-run + before they can be compared against post-unification measurements. + This needs to be removed once re-measured. - All landings in the kernel path are bitwise-identical or 1-ULP-close to the reference; the suite builds clean on x86_64 + aarch64 and passes with and without BLAS. @@ -750,7 +757,8 @@ for the full when-to-enable / when-to-disable BLAS checklist. - **AlignedVec::uninitialized** — skip output zeroing in hot paths - **ImageU8/ImageF32** — zero-overhead wrappers bypass Tensor allocation - **GCD dispatch_apply** — macOS near-zero threading (~0.3µs) -- **mimalloc** — thread-local arena pools +- **mimalloc** — thread-local arena pools, installed by the bench harnesses (not + by the library crates; see [architecture.md](architecture.md#memory-patterns)) - **Fused kernels** — single-pass softmax, sigmoid, attention - **im2col + BLAS** — Accelerate/OpenBLAS for matmul/conv2d/conv3d - **Flash Attention** — tiled O(Br×Bc) memory, online softmax diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 53732a96..7c4cb0f7 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -26,6 +26,7 @@ yscv-onnx = { path = "../crates/yscv-onnx" } yscv-pipeline = { path = "../crates/yscv-pipeline" } thiserror.workspace = true rustc-hash.workspace = true +mimalloc.workspace = true [[example]] name = "classify_image" diff --git a/examples/src/bench_gpu.rs b/examples/src/bench_gpu.rs index 515f2f40..24c4d71f 100644 --- a/examples/src/bench_gpu.rs +++ b/examples/src/bench_gpu.rs @@ -3,6 +3,9 @@ //! Usage: //! cargo run --release --example bench_gpu --features gpu -- +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use rustc_hash::FxHashMap; use yscv_kernels::GpuBackend; use yscv_onnx::{ diff --git a/examples/src/bench_metal_conv.rs b/examples/src/bench_metal_conv.rs index 1918ed10..c9e035d8 100644 --- a/examples/src/bench_metal_conv.rs +++ b/examples/src/bench_metal_conv.rs @@ -1,6 +1,9 @@ //! Benchmark: Metal simdgroup_matrix vs Metal basic (=same algo as WGSL) vs CPU. //! Isolates: (1) naga overhead, (2) simdgroup_matrix benefit. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + #[cfg(not(all(target_os = "macos", feature = "metal-backend")))] fn main() { eprintln!("bench_metal_conv requires macOS with the metal-backend feature"); diff --git a/examples/src/bench_metal_vball.rs b/examples/src/bench_metal_vball.rs index 860dc12c..a03ad879 100644 --- a/examples/src/bench_metal_vball.rs +++ b/examples/src/bench_metal_vball.rs @@ -3,6 +3,9 @@ //! Usage: //! cargo run --release --example bench_metal_vball --features metal-backend -- /path/to/model.onnx +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + #[cfg(not(all(target_os = "macos", feature = "metal-backend")))] fn main() { eprintln!("bench_metal_vball requires macOS with the metal-backend feature"); diff --git a/examples/src/bench_metal_yolo.rs b/examples/src/bench_metal_yolo.rs index 38ffdadf..ba4a67c2 100644 --- a/examples/src/bench_metal_yolo.rs +++ b/examples/src/bench_metal_yolo.rs @@ -1,6 +1,9 @@ //! Benchmark: Metal-native ONNX inference for YOLO models. //! Compares Metal vs CPU (and GPU/wgpu if enabled). +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + #[cfg(not(all(target_os = "macos", feature = "metal-backend")))] fn main() { eprintln!("bench_metal_yolo requires macOS with the metal-backend feature"); diff --git a/examples/src/bench_mps_gemm.rs b/examples/src/bench_mps_gemm.rs index 7666a677..05050ca3 100644 --- a/examples/src/bench_mps_gemm.rs +++ b/examples/src/bench_mps_gemm.rs @@ -1,6 +1,9 @@ //! Benchmark: MPS GEMM vs custom Metal matmul/conv kernel on representative YOLOv8n sizes. //! Tests pure GEMM (1x1 conv equivalent) performance. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + #[cfg(not(all(target_os = "macos", feature = "metal-backend")))] fn main() { eprintln!("bench_mps_gemm requires macOS with the metal-backend feature"); diff --git a/examples/src/bench_mpsgraph.rs b/examples/src/bench_mpsgraph.rs index e4a0c5ed..ba396ba2 100644 --- a/examples/src/bench_mpsgraph.rs +++ b/examples/src/bench_mpsgraph.rs @@ -5,6 +5,9 @@ //! //! Also supports YOLO models from slowwork/ when no arg is given. +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + #[cfg(not(all(target_os = "macos", feature = "metal-backend")))] fn main() { eprintln!("bench_mpsgraph requires macOS with the metal-backend feature"); diff --git a/examples/src/bench_mpsgraph_only.rs b/examples/src/bench_mpsgraph_only.rs index ab4c0ba0..e59d285d 100644 --- a/examples/src/bench_mpsgraph_only.rs +++ b/examples/src/bench_mpsgraph_only.rs @@ -3,6 +3,9 @@ //! Usage: //! cargo run --release --example bench_mpsgraph_only --features metal-backend -- model.onnx [iters] +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + #[cfg(not(all(target_os = "macos", feature = "metal-backend")))] fn main() { eprintln!("bench_mpsgraph_only requires macOS with the metal-backend feature"); diff --git a/examples/src/bench_vball_cpu.rs b/examples/src/bench_vball_cpu.rs index 9d02585c..83a9da23 100644 --- a/examples/src/bench_vball_cpu.rs +++ b/examples/src/bench_vball_cpu.rs @@ -3,6 +3,9 @@ //! Usage: //! cargo run --release --example bench_vball_cpu -- /path/to/model.onnx [iterations] +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::time::Instant; use rustc_hash::FxHashMap; diff --git a/examples/src/bench_video_decode.rs b/examples/src/bench_video_decode.rs index ca1f2447..12ee1ed6 100644 --- a/examples/src/bench_video_decode.rs +++ b/examples/src/bench_video_decode.rs @@ -5,6 +5,9 @@ //! `--luma-only` skips YUV-to-RGB for fair comparison with ffmpeg `-f null`. //! `--hw` uses hardware decode (VideoToolbox/VAAPI/NVDEC/MediaFoundation). +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + use std::path::Path; use std::time::Instant;