From 22ae6085d82531a6102d1c46e865ba92b96f0dc2 Mon Sep 17 00:00:00 2001 From: AndreaGuarracino Date: Wed, 1 Jan 2025 15:39:02 -0600 Subject: [PATCH 1/3] align SortedRanges to cache line boundary for improved memory access --- Cargo.lock | 4 ++-- src/impg.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40cac35..7c4313d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -434,9 +434,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.93" +version = "2.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" dependencies = [ "proc-macro2", "quote", diff --git a/src/impg.rs b/src/impg.rs index 0a0ea28..89340b4 100644 --- a/src/impg.rs +++ b/src/impg.rs @@ -112,6 +112,8 @@ pub struct SerializableInterval { metadata: QueryMetadata, } +// Align ranges to cache line boundary for better memory access +#[repr(align(64))] #[derive(Debug, Default, Clone)] pub struct SortedRanges { pub ranges: Vec<(i32, i32)> From 6c5a3b85c3d1d2e2c5718d35396cb64d703f246f Mon Sep 17 00:00:00 2001 From: AndreaGuarracino Date: Wed, 1 Jan 2025 20:27:01 -0600 Subject: [PATCH 2/3] keep the stack sorted and merged by key --- src/impg.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/impg.rs b/src/impg.rs index 89340b4..a318b3d 100644 --- a/src/impg.rs +++ b/src/impg.rs @@ -112,8 +112,6 @@ pub struct SerializableInterval { metadata: QueryMetadata, } -// Align ranges to cache line boundary for better memory access -#[repr(align(64))] #[derive(Debug, Default, Clone)] pub struct SortedRanges { pub ranges: Vec<(i32, i32)> @@ -142,22 +140,22 @@ impl SortedRanges { } else { (new_range.1, new_range.0) }; - + // Return regions that don't overlap with existing ranges let mut non_overlapping = Vec::new(); let mut current = start; - + // Find the first range that could overlap let mut i = match self.ranges.binary_search_by_key(&start, |&(s, _)| s) { Ok(pos) => pos, Err(pos) => pos, }; - + // Check previous range for overlap if i > 0 && self.ranges[i - 1].1 > start { i -= 1; } - + // Process all potentially overlapping ranges while i < self.ranges.len() && current < end { let (range_start, range_end) = self.ranges[i]; @@ -170,11 +168,11 @@ impl SortedRanges { current = max(current, range_end); i += 1; } - + if current < end { non_overlapping.push((current, end)); } - + // Now insert the range while maintaining sorted order and merging overlaps match self.ranges.binary_search_by_key(&start, |&(s, _)| s) { Ok(pos) | Err(pos) => { @@ -186,12 +184,12 @@ impl SortedRanges { self.ranges[pos].0 = min(start, self.ranges[pos].0); self.ranges[pos].1 = max(end, self.ranges[pos].1); self.merge_forward_from(pos); - } else { + } else { self.ranges.insert(pos, (start, end)); } } } - + non_overlapping } @@ -432,7 +430,21 @@ impl Impg { } } }); - } + + // Merge contiguous/overlapping ranges with same sequence_id + stack.sort_by_key(|(id, start, _)| (*id, *start)); + let mut write = 0; + for read in 1..stack.len() { + if stack[write].0 == stack[read].0 && // Same sequence_id + stack[write].2 >= stack[read].1 { // Overlapping or contiguous + stack[write].2 = stack[write].2.max(stack[read].2); + } else { + write += 1; + stack.swap(write, read); + } + } + stack.truncate(write + 1); + } } results From b14d1e2f33ec6770f4fe761dac3abdfa2a9fc3c9 Mon Sep 17 00:00:00 2001 From: AndreaGuarracino Date: Thu, 2 Jan 2025 18:41:20 -0600 Subject: [PATCH 3/3] performance tweak --- src/impg.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/impg.rs b/src/impg.rs index a318b3d..ec5770f 100644 --- a/src/impg.rs +++ b/src/impg.rs @@ -384,14 +384,14 @@ impl Impg { .map(|(&k, v)| (k, (*v).clone())) .collect() } else { - FxHashMap::default() + FxHashMap::with_capacity_and_hasher(self.seq_index.len(), Default::default()) }; // Initialize first visited range for target_id if not already present visited_ranges.entry(target_id) .or_default() .insert((range_start, range_end)); - while let Some((current_target_id, current_target_start, current_target_end)) = stack.pop() { + while let Some((current_target_id, current_target_start, current_target_end)) = stack.pop() { if let Some(tree) = self.trees.get(¤t_target_id) { tree.query(current_target_start, current_target_end, |interval| { let metadata = &interval.metadata;