From 418cd756b372885067035358643f6b08b8028066 Mon Sep 17 00:00:00 2001 From: Seth Stadick Date: Mon, 15 Jun 2026 10:21:59 -0400 Subject: [PATCH 1/3] chore: prepare sort_unstable feature release --- .github/workflows/ci.yaml | 12 ++--- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 1 + benches/lapper_benchmark.rs | 95 ++++++++++++++++++++++++++++++++++--- rust-toolchain.toml | 4 ++ src/lib.rs | 6 +-- 7 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f15f940..247d974 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,11 +13,11 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - - name: Install stable toolchain + - name: Install pinned toolchain uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.95.0 override: true - name: Cache dependencies @@ -36,11 +36,11 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - - name: Install stable toolchain + - name: Install pinned toolchain uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.95.0 override: true components: rustfmt, clippy @@ -69,11 +69,11 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - - name: Install stable toolchain + - name: Install pinned toolchain uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.95.0 override: true - name: Cache dependencies diff --git a/Cargo.lock b/Cargo.lock index 9c322df..802d636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,7 +448,7 @@ checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "rust-lapper" -version = "1.2.0" +version = "1.3.0" dependencies = [ "bincode", "cpu-time", diff --git a/Cargo.toml b/Cargo.toml index fcd0481..fbf5d16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-lapper" -version = "1.2.0" +version = "1.3.0" authors = ["Seth Stadick "] edition = "2018" license = "MIT" diff --git a/README.md b/README.md index 71e6591..41d0765 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,7 @@ fn main() { ## Release Notes +- `1.3.0`: Add the `sort_unstable` feature flag for allocation-sensitive sorting. - `1.1.0`: Added insert functionality thanks to @zaporter - `0.4.0`: Addition of the BITS count algorithm. - `0.4.2`: Bugfix in to update starts/stops vectors when overlaps merged diff --git a/benches/lapper_benchmark.rs b/benches/lapper_benchmark.rs index df8dace..a52a872 100644 --- a/benches/lapper_benchmark.rs +++ b/benches/lapper_benchmark.rs @@ -3,14 +3,12 @@ extern crate criterion; extern crate rand; extern crate rust_lapper; -use cpu_time::ProcessTime; use criterion::black_box; -use criterion::Criterion; -use rand::prelude::*; +use criterion::{BatchSize, BenchmarkId, Criterion, Throughput}; +use rand::rngs::StdRng; use rand::Rng; +use rand::SeedableRng; use rust_lapper::{Interval, Lapper}; -use std::ops::Range; -use std::time::Duration; type Iv = Interval; @@ -21,7 +19,7 @@ fn randomi(imin: u32, imax: u32) -> u32 { fn make_random(n: usize, range_max: u32, size_min: u32, size_max: u32) -> Vec { let mut result = Vec::with_capacity(n); - for i in 0..n { + for _ in 0..n { let s = randomi(0, range_max); let e = s + randomi(size_min, size_max); result.push(Interval { @@ -44,6 +42,41 @@ fn make_interval_set() -> (Vec, Vec) { (intervals, other_intervals) } +fn make_random_seeded( + n: usize, + range_max: u32, + size_min: u32, + size_max: u32, + seed: u64, +) -> Vec { + let mut rng = StdRng::seed_from_u64(seed); + let mut result = Vec::with_capacity(n); + for _ in 0..n { + let s = rng.gen_range(0, range_max); + let e = s + rng.gen_range(size_min, size_max); + result.push(Interval { + start: s, + stop: e, + val: false, + }); + } + result +} + +fn make_non_overlapping(n: usize) -> Vec { + (0..n) + .rev() + .map(|i| { + let start = (i as u32) * 10; + Interval { + start, + stop: start + 5, + val: false, + } + }) + .collect() +} + pub fn query(c: &mut Criterion) { let (intervals, other_intervals) = make_interval_set(); // Make Lapper intervals @@ -145,5 +178,53 @@ pub fn query(c: &mut Criterion) { comparison_group.finish(); } -criterion_group!(benches, query); +pub fn construction(c: &mut Criterion) { + let sizes = [1_000usize, 10_000, 100_000]; + let chrom_size = 100_000_000; + let min_interval_size = 500; + let max_interval_size = 80000; + + let mut construction_group = c.benchmark_group("Construction"); + for &n in sizes.iter() { + construction_group.throughput(Throughput::Elements(n as u64)); + let intervals = make_random_seeded( + n, + chrom_size, + min_interval_size, + max_interval_size, + n as u64, + ); + let non_overlapping_intervals = make_non_overlapping(n); + + construction_group.bench_with_input( + BenchmarkId::new("Lapper::new from unsorted intervals", n), + &intervals, + |b, intervals| { + b.iter_batched( + || intervals.clone(), + |intervals| black_box(Lapper::new(black_box(intervals))), + BatchSize::LargeInput, + ); + }, + ); + + construction_group.bench_with_input( + BenchmarkId::new("merge_overlaps non-overlapping intervals", n), + &non_overlapping_intervals, + |b, intervals| { + b.iter_batched( + || Lapper::new(intervals.clone()), + |mut lapper| { + lapper.merge_overlaps(); + black_box(lapper) + }, + BatchSize::LargeInput, + ); + }, + ); + } + construction_group.finish(); +} + +criterion_group!(benches, query, construction); criterion_main!(benches); diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..12dd525 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.95.0" +profile = "minimal" +components = ["rustfmt", "clippy"] diff --git a/src/lib.rs b/src/lib.rs index c950d9b..f09c4d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -351,7 +351,7 @@ where /// Return an iterator over the intervals in Lapper #[inline] - pub fn iter(&self) -> IterLapper { + pub fn iter(&self) -> IterLapper<'_, I, T> { IterLapper { inner: self, pos: 0, @@ -574,7 +574,7 @@ where /// Interval { start: 20, stop: 25, val: 1 }]); /// ``` #[inline] - pub fn depth(&self) -> IterDepth { + pub fn depth(&self) -> IterDepth<'_, I, T> { let mut merged_lapper = Lapper::new( self.intervals .iter() @@ -626,7 +626,7 @@ where /// assert_eq!(lapper.find(5, 11).count(), 2); /// ``` #[inline] - pub fn find(&self, start: I, stop: I) -> IterFind { + pub fn find(&self, start: I, stop: I) -> IterFind<'_, I, T> { IterFind { inner: self, off: Self::lower_bound( From 0c3868262a76f24bb004c63e21e77a464117fbaa Mon Sep 17 00:00:00 2001 From: Seth Stadick Date: Mon, 15 Jun 2026 10:24:46 -0400 Subject: [PATCH 2/3] test: gate serde roundtrip by feature --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index f09c4d6..0fcc7b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1384,6 +1384,7 @@ mod tests { assert_eq!(lapper.count(28974798, 33141355), 1); } + #[cfg(feature = "with_serde")] #[test] fn serde_test() { let data = vec![ From ccfa394eee06299336941fa18e33ab319ae2c9ad Mon Sep 17 00:00:00 2001 From: Seth Stadick Date: Mon, 15 Jun 2026 10:29:02 -0400 Subject: [PATCH 3/3] docs: credit sort_unstable contributor --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41d0765..92f5480 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ fn main() { ## Release Notes -- `1.3.0`: Add the `sort_unstable` feature flag for allocation-sensitive sorting. +- `1.3.0`: Add the `sort_unstable` feature flag for allocation-sensitive sorting thanks to @jameslkingsley. - `1.1.0`: Added insert functionality thanks to @zaporter - `0.4.0`: Addition of the BITS count algorithm. - `0.4.2`: Bugfix in to update starts/stops vectors when overlaps merged