diff --git a/CHANGELOG.md b/CHANGELOG.md index 981d95f..6107325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Added + +- Criterion benchmarks (`crates/eschalign/benches/`): `kernels` (score per + backend, mode and read length; row-major against reference-major; a 64-read + `map_reads` chunk) and `gpu` (`score_batch`, int against half2; needs + `--features gpu` and a device, skips without one). `criterion` is a + dev-dependency only; the library is still std-only. + ## 0.3.0 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index 0206ee8..3a7214a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,7 +111,10 @@ beside wall — the lessons of benchmarking on BeeGFS, where the first arm to touch a file pays to page it in. Output goes to node-local scratch and is deleted. The numbers carried over from escapepod-rs are in `benchmarks/README.md`. `crates/eschalign/examples/{long_read_probe,gpu_score_probe}.rs` are the -kernel-level probes; keep them re-runnable. Benchmark output belongs under +kernel-level probes; keep them re-runnable. `cargo bench -p eschalign --bench +kernels` (and `--bench gpu` with `--features gpu`) are the criterion versions, +for a before/after with a confidence interval (`benchmarks/README.md`). +Benchmark output belongs under `results/` or `~/scratch//`, never `~/.cache`. ```bash diff --git a/Cargo.lock b/Cargo.lock index 0c55ea7..f1e942a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "1.0.0" @@ -150,6 +156,12 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.6.0" @@ -183,6 +195,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.6.7" @@ -256,6 +295,40 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam-channel" version = "0.5.17" @@ -290,6 +363,12 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "cudarc" version = "0.19.10" @@ -331,6 +410,7 @@ dependencies = [ name = "eschalign" version = "0.3.0" dependencies = [ + "criterion", "cudarc", "serde_json", ] @@ -418,6 +498,17 @@ dependencies = [ "r-efi", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -430,6 +521,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -477,12 +574,32 @@ dependencies = [ "web-time", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -729,6 +846,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "owo-colors" version = "4.4.0" @@ -800,6 +923,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.18" @@ -836,6 +971,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.229" @@ -962,6 +1106,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -1093,6 +1247,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasm-bindgen" version = "0.2.129" @@ -1148,6 +1312,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -1225,6 +1398,26 @@ dependencies = [ "memchr", ] +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "zlib-rs" version = "0.6.8" diff --git a/benchmarks/README.md b/benchmarks/README.md index 0d681d9..f0baef4 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -4,6 +4,29 @@ Carved out of escapepod-rs 0.31.0. The sections below are that repository's `esc The harness is `benchmarks/benchmark_align.sh`; see its header for the flags. +Criterion benchmarks (`crates/eschalign/benches/`) give a confidence interval +for kernel changes on the same synthetic 164-reference panel the probes use. +Run them in an allocation, not on the login node; `[profile.bench]` inherits +release, so they match the shipped codegen. Criterion's own output +(`target/criterion`) stays in the build directory, so point `CARGO_TARGET_DIR` +under `~/scratch//` and delete it after. + +```bash +# CPU: score_all (backend x mode x read length), loop_order, map_reads (64-read chunk) +srun -p rna -c 32 --mem=32G -- pixi run -e dev cargo bench -p eschalign --bench kernels +cargo bench -p eschalign --bench kernels -- score_all/local/avx512 # a filter +# GPU: score_batch, int against half2, local and semi-global +srun -p gpu -A gpu_rbi -c 16 --gres=gpu:1 -- \ + pixi run -e dev-gpu cargo bench --features gpu -p eschalign --bench gpu +# compare against a baseline: cargo bench ... -- --save-baseline main, then --baseline main +``` + +First numbers (2026-10-17, v0.3.0, one thread, 130 nt, local): avx2 8.0e9 +cells/s and avx512 1.20e10 on rna; the A30 scores 2.66e11 on the int kernel and +5.68e11 on half2 (20,000-read batches). These agree with the probes below. +`examples/{long_read_probe,gpu_score_probe}.rs` remain the tools for real reads, +very long reads and register counts. + ## `eschalign --device gpu`: narrow tail stripe in the half2 kernel (2026-10-17, #13) The half2 kernel below walked a full, masked 16-row stripe for the end of diff --git a/crates/eschalign/CLAUDE.md b/crates/eschalign/CLAUDE.md index 20333e2..33733a7 100644 --- a/crates/eschalign/CLAUDE.md +++ b/crates/eschalign/CLAUDE.md @@ -36,6 +36,7 @@ pixi run -e dev-gpu cargo nextest run --features gpu -p eschalign --test gpu_par - `tests/gpu_parity.rs` (`gpu`) — `scalar::score` by equality, every check on **both kernels** (`set_half2`): the SIMD tests' random generator in both modes and five schemes (the usual three plus a positive mismatch and free gaps, which only the half2 kernel's end-of-reference masking survives), every read length 1–40 around the stripe, a 300-reference panel that splits into groups, the fixture reads against both fixture panels, a batch that straddles `fits_f16` (split between the kernels), and a panel over `MAX_REF_LEN_H2` (int kernel alone). Skips with a message where there is no device. - `examples/gpu_score_probe.rs` (`gpu`) — times `score_batch` alone on real reads, `auto` (half2 where it applies) or `int`, and prints each kernel's registers and local memory (spills); keep it re-runnable (A30, M1's reads: int 2.6e11 cells/s, ALU-bound; half2 5.5e11 local / 6.1e11 semi-global — `benchmarks/README.md`). - `examples/long_read_probe.rs` — both score-kernel loop orders, one thread, synthetic reads of 1 k–400 k nt (or `--reads FASTA`) against a synthetic 164-reference panel (or `--reference`); `--split` also times one thread per lane group. The measurement `TRANSPOSED_MIN_READ_LEN` was chosen from; keep it re-runnable (`benchmarks/README.md`). +- `benches/kernels.rs`, `benches/gpu.rs` (`gpu`) — criterion: score per backend/mode/length, the two loop orders, a 64-read `map_reads` chunk, and `score_batch` int against half2. The same synthetic panel as the probes; criterion is a dev-dependency only (the crate stays std-only). Run in an allocation with `CARGO_TARGET_DIR` under scratch (`benchmarks/README.md`). - `simd/mod.rs`'s own equality tests (`avx2_matches_scalar_random`, `avx512_matches_scalar_random`, `simd_matches_scalar_on_fixture_reads`, `traceback_kernels_match_scalar`, `transposed_matches_scalar_random`, `transposed_matches_row_major_on_fixture_reads`, `dispatch_picks_transposed_above_threshold`) sweep `Backend::available()`, not the dispatch's pick — run on `rna` (Cascade Lake) or the AVX-512 kernels never execute. The transposed random test runs the scalar oracle over 20–56 kb reads, which is why this crate builds at `opt-level = 2` in the dev profile (workspace `Cargo.toml`, `[profile.dev.package.eschalign]`). `mapper::tests::per_group_scores_equal_score_all` pins the per-group API to `score_all`. ## Invariants and traps diff --git a/crates/eschalign/Cargo.toml b/crates/eschalign/Cargo.toml index 96b036f..2e075f2 100644 --- a/crates/eschalign/Cargo.toml +++ b/crates/eschalign/Cargo.toml @@ -26,8 +26,19 @@ default = [] gpu = ["dep:cudarc"] [dev-dependencies] +# Benchmarks only; no plotting or rayon, so nothing extra to build. +criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] } serde_json = "1" +[[bench]] +name = "kernels" +harness = false + +[[bench]] +name = "gpu" +harness = false +required-features = ["gpu"] + [[example]] name = "gpu_score_probe" required-features = ["gpu"] diff --git a/crates/eschalign/benches/gpu.rs b/crates/eschalign/benches/gpu.rs new file mode 100644 index 0000000..22386fb --- /dev/null +++ b/crates/eschalign/benches/gpu.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT + +//! Criterion benchmark of the CUDA score kernels (`gpu` feature): one +//! `GpuScorer::score_batch` over a batch of reads against the synthetic +//! 164-reference panel, the int kernel against the half2 kernel (#9), local +//! and semi-global. Skips with a message where there is no device. +//! +//! ```text +//! srun -p gpu -A gpu_rbi -c 16 --gres=gpu:1 -- \ +//! pixi run -e dev-gpu cargo bench --features gpu -p eschalign --bench gpu +//! ``` +//! +//! `examples/gpu_score_probe.rs` stays the tool for real reads and register +//! counts; this is the one for a regression check with a confidence interval. + +use std::hint::black_box; + +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use eschalign::alphabet::encode; +use eschalign::cuda::{GpuScorer, device_visible}; +use eschalign::{Mode, Panel, Scoring}; + +const BATCH: usize = 20_000; + +fn xorshift(x: &mut u64) -> u64 { + *x ^= *x << 13; + *x ^= *x >> 7; + *x ^= *x << 17; + *x +} + +fn random_seq(x: &mut u64, len: usize) -> Vec { + (0..len) + .map(|_| b"ACGT"[(xorshift(x) % 4) as usize]) + .collect() +} + +fn gpu(c: &mut Criterion) { + if !device_visible() { + eprintln!("gpu bench skipped: no CUDA device visible"); + return; + } + let mut x = 0x0123_4567_89ab_cdefu64; + let panel = Panel::new((0..164).map(|k| { + let len = 138 + (xorshift(&mut x) % 63) as usize; + (format!("r{k}"), random_seq(&mut x, len)) + })) + .unwrap(); + let bases = panel.total_len() as u64; + for mode in [Mode::Local, Mode::SemiGlobal] { + let mut scorer = GpuScorer::new(&panel, Scoring::default(), mode).expect("GpuScorer"); + eprintln!("# {} ({} mode)", scorer.device_name(), mode.name()); + let mut g = c.benchmark_group(format!("gpu_score_batch/{}", mode.name())); + g.sample_size(20); + for len in [100usize, 130, 200] { + let reads: Vec> = (0..BATCH) + .map(|_| encode(&random_seq(&mut x, len))) + .collect(); + let queries: Vec<&[u8]> = reads.iter().map(Vec::as_slice).collect(); + g.throughput(Throughput::Elements((BATCH * len) as u64 * bases)); + for (name, half2) in [("int", false), ("half2", true)] { + scorer.set_half2(half2); + if half2 && !scorer.half2() { + continue; // pre-sm_80 device: no half2 kernel + } + g.bench_with_input(BenchmarkId::new(name, len), &queries, |b, q| { + b.iter(|| black_box(scorer.score_batch(black_box(q)).expect("score_batch"))); + }); + } + } + g.finish(); + } +} + +criterion_group!(benches, gpu); +criterion_main!(benches); diff --git a/crates/eschalign/benches/kernels.rs b/crates/eschalign/benches/kernels.rs new file mode 100644 index 0000000..c169649 --- /dev/null +++ b/crates/eschalign/benches/kernels.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT + +//! Criterion benchmarks of the CPU kernels, on the synthetic 164-reference +//! panel (138–200 nt, the shape of the sacCer3 dual-adapter panel) that +//! `examples/long_read_probe.rs` uses, so the numbers line up with +//! `benchmarks/README.md`. +//! +//! ```text +//! srun -p rna -c 32 --mem=32G -- pixi run -e dev cargo bench -p eschalign --bench kernels +//! cargo bench -p eschalign --bench kernels -- score_all/local/avx512 # a filter +//! ``` +//! +//! Groups: `score_all` (one read against the whole panel, per backend, mode +//! and read length, throughput in DP cells), `loop_order` (row-major against +//! reference-major around `TRANSPOSED_MIN_READ_LEN`), and `map_reads` (a +//! 64-read chunk through score, tie selection, traceback and `MD`/`NM`, the +//! unit the CLI hands a rayon worker). Every benchmark is one thread; the +//! backends are `Backend::available()`, so AVX-512 only appears on a node +//! that has it. `ESCHALIGN_BACKEND` is not read here. + +use std::hint::black_box; + +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use eschalign::alphabet::encode; +use eschalign::simd::Kernel; +use eschalign::{Aligner, Backend, MapOptions, Mode, Panel, Scoring}; + +fn xorshift(x: &mut u64) -> u64 { + *x ^= *x << 13; + *x ^= *x >> 7; + *x ^= *x << 17; + *x +} + +fn random_seq(x: &mut u64, len: usize) -> Vec { + (0..len) + .map(|_| b"ACGT"[(xorshift(x) % 4) as usize]) + .collect() +} + +fn panel() -> Panel { + let mut x = 0x0123_4567_89ab_cdefu64; + Panel::new((0..164).map(|k| { + let len = 138 + (xorshift(&mut x) % 63) as usize; + (format!("r{k}"), random_seq(&mut x, len)) + })) + .unwrap() +} + +/// A read of `len` bases taken from reference `k` of the panel (wrapping +/// around it for a long one) with a substitution about every 20 bases, so +/// the traceback has real mismatches to report. +fn read_like(panel: &Panel, x: &mut u64, k: usize, len: usize) -> Vec { + let src = &panel.get(k % panel.len()).seq; + (0..len) + .map(|i| { + let b = src[i % src.len()]; + if xorshift(x).is_multiple_of(20) { + b"ACGT"[(xorshift(x) % 4) as usize] + } else { + b + } + }) + .collect() +} + +const MODES: [Mode; 2] = [Mode::Local, Mode::SemiGlobal]; +const LENGTHS: [usize; 4] = [100, 130, 200, 1000]; + +fn backends() -> impl Iterator { + Backend::available() + .into_iter() + .filter(|&b| b != Backend::Scalar) +} + +fn score_all(c: &mut Criterion) { + let panel = panel(); + let bases = panel.total_len() as u64; + let mut x = 0xfeed_beef_u64; + for mode in MODES { + let mut g = c.benchmark_group(format!("score_all/{}", mode.name())); + for backend in backends() { + let a = + Aligner::with_backend(panel.clone(), Scoring::default(), mode, backend).unwrap(); + for len in LENGTHS { + let q = encode(&read_like(&panel, &mut x, len, len)); + g.throughput(Throughput::Elements(len as u64 * bases)); + g.bench_with_input(BenchmarkId::new(backend.name(), len), &q, |b, q| { + let mut out = Vec::new(); + b.iter(|| a.score_all(black_box(q), &mut out)); + }); + } + } + g.finish(); + } +} + +/// Row-major against reference-major at the lengths around +/// `TRANSPOSED_MIN_READ_LEN`; the measurement that threshold is set from +/// (`examples/long_read_probe.rs` is the one to re-run for the long end). +fn loop_order(c: &mut Criterion) { + let panel = panel(); + let bases = panel.total_len() as u64; + let mut x = 0xabad_1dea_u64; + let mut g = c.benchmark_group("loop_order/local"); + for backend in backends() { + let base = + Aligner::with_backend(panel.clone(), Scoring::default(), Mode::Local, backend).unwrap(); + for len in [130, 250, 300, 600, 1000] { + let q = encode(&read_like(&panel, &mut x, len, len)); + g.throughput(Throughput::Elements(len as u64 * bases)); + for (name, kernel) in [ + ("row_major", Kernel::RowMajor), + ("transposed", Kernel::Transposed), + ] { + let a = base.clone().with_transposed_min_len(match kernel { + Kernel::RowMajor => None, + Kernel::Transposed => Some(0), + }); + g.bench_with_input( + BenchmarkId::new(format!("{}/{name}", backend.name()), len), + &q, + |b, q| { + let mut out = Vec::new(); + b.iter(|| a.score_all(black_box(q), &mut out)); + }, + ); + } + } + } + g.finish(); +} + +/// A 64-read chunk end to end on the CPU: score every read against the +/// panel, pick the ties, trace the winners together, build `MD`/`NM`. +fn map_reads(c: &mut Criterion) { + let panel = panel(); + let mut x = 0x5eed_c0de_u64; + let reads: Vec> = (0..64) + .map(|k| { + let len = 100 + (xorshift(&mut x) % 100) as usize; + read_like(&panel, &mut x, k * 7, len) + }) + .collect(); + let refs: Vec<&[u8]> = reads.iter().map(Vec::as_slice).collect(); + let mut g = c.benchmark_group("map_reads/chunk64"); + g.throughput(Throughput::Elements(refs.len() as u64)); + for mode in MODES { + for backend in backends() { + let a = + Aligner::with_backend(panel.clone(), Scoring::default(), mode, backend).unwrap(); + g.bench_function(BenchmarkId::new(mode.name(), backend.name()), |b| { + b.iter(|| black_box(a.map_reads(black_box(&refs), &MapOptions::default()))); + }); + } + } + g.finish(); +} + +criterion_group!(benches, score_all, loop_order, map_reads); +criterion_main!(benches);