Skip to content

Commit 44e34cc

Browse files
authored
feat: perf small_union and small_symmetric_difference (#19069)
* perf: small_union and small_symmetric_difference functions were rewritten for in-place calculations to avoid frequent creation of HybridBitmap. * chore: codefmt * chore: codefmt
1 parent 13efc1f commit 44e34cc

File tree

2 files changed

+352
-85
lines changed

2 files changed

+352
-85
lines changed

src/common/io/src/bitmap.rs

Lines changed: 121 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use std::ops::BitAndAssign;
2020
use std::ops::BitOrAssign;
2121
use std::ops::BitXorAssign;
2222
use std::ops::SubAssign;
23+
use std::ptr;
2324

2425
use databend_common_exception::ErrorCode;
2526
use databend_common_exception::Result;
@@ -37,6 +38,10 @@ const HYBRID_HEADER_LEN: usize = 4;
3738

3839
type SmallBitmap = SmallVec<[u64; LARGE_THRESHOLD]>;
3940

41+
/// Perf Tips:
42+
/// - The deserialization performance of HybridBitmap significantly impacts the performance of Bitmap-related calculations.
43+
/// - Calculations may frequently create new Bitmaps; reusing them as much as possible can effectively improve performance.
44+
/// - do not use Box to construct HybridBitmap
4045
#[allow(clippy::large_enum_variant)]
4146
#[derive(Clone)]
4247
pub enum HybridBitmap {
@@ -241,8 +246,7 @@ impl std::ops::BitOrAssign for HybridBitmap {
241246
}
242247
}
243248
HybridBitmap::Small(lhs_set) => {
244-
let left = mem::take(lhs_set);
245-
*lhs_set = small_union(left, rhs_set.as_slice());
249+
small_union(lhs_set, rhs_set.as_slice());
246250
if self.len() >= LARGE_THRESHOLD as u64 {
247251
let _ = self.promote_to_tree();
248252
}
@@ -303,14 +307,14 @@ impl std::ops::BitXorAssign for HybridBitmap {
303307
self.try_demote();
304308
}
305309
HybridBitmap::Small(rhs_set) => match self {
310+
// Disjoint data in the bitmap can cause lhs_tree expansion during XOR, making this path a significant performance bottleneck.
306311
HybridBitmap::Large(lhs_tree) => {
307312
let rhs_tree = RoaringTreemap::from_iter(rhs_set.iter().copied());
308313
lhs_tree.bitxor_assign(rhs_tree);
309314
self.try_demote();
310315
}
311316
HybridBitmap::Small(lhs_set) => {
312-
let result = small_symmetric_difference(lhs_set.as_slice(), rhs_set.as_slice());
313-
*lhs_set = result;
317+
small_symmetric_difference(lhs_set, rhs_set.as_slice());
314318
if self.len() >= LARGE_THRESHOLD as u64 {
315319
let _ = self.promote_to_tree();
316320
}
@@ -541,12 +545,11 @@ fn decode_small_bitmap(payload: &[u8]) -> Result<HybridBitmap> {
541545
)));
542546
}
543547

544-
let mut data = [0u8; std::mem::size_of::<u64>()];
545548
let set: SmallBitmap = bytes
546-
.chunks_exact(data.len())
547-
.map(move |chunk| {
548-
data.copy_from_slice(chunk);
549-
u64::from_le_bytes(data)
549+
.chunks_exact(std::mem::size_of::<u64>())
550+
.map(|chunk| {
551+
let raw = unsafe { ptr::read_unaligned(chunk.as_ptr() as *const u64) };
552+
u64::from_le(raw)
550553
})
551554
.collect();
552555
Ok(HybridBitmap::Small(set))
@@ -562,37 +565,62 @@ fn small_insert(set: &mut SmallBitmap, value: u64) -> bool {
562565
}
563566
}
564567

565-
fn small_union(left: SmallBitmap, right: &[u64]) -> SmallBitmap {
566-
if right.is_empty() {
567-
return left;
568+
fn small_union(target: &mut SmallBitmap, other: &[u64]) {
569+
if other.is_empty() {
570+
return;
568571
}
569-
if left.is_empty() {
570-
return SmallBitmap::from_slice(right);
572+
if target.is_empty() {
573+
target.extend_from_slice(other);
574+
return;
571575
}
572576

573-
let left_slice = left.as_slice();
574-
let mut result = SmallBitmap::with_capacity(left_slice.len() + right.len());
575-
let mut i = 0;
576-
let mut j = 0;
577+
let lhs_len = target.len();
578+
let rhs_len = other.len();
579+
target.reserve(rhs_len);
580+
let mut write = lhs_len + rhs_len;
581+
target.resize(write, 0);
577582

578-
while i < left_slice.len() && j < right.len() {
579-
let lv = left_slice[i];
580-
let rv = right[j];
581-
if lv < rv {
582-
result.push(lv);
583-
i += 1;
584-
} else if rv < lv {
585-
result.push(rv);
586-
j += 1;
587-
} else {
588-
result.push(lv);
589-
i += 1;
590-
j += 1;
583+
let mut i = lhs_len;
584+
let mut j = rhs_len;
585+
586+
while i > 0 && j > 0 {
587+
let lv = target[i - 1];
588+
let rv = other[j - 1];
589+
write -= 1;
590+
match lv.cmp(&rv) {
591+
std::cmp::Ordering::Greater => {
592+
target[write] = lv;
593+
i -= 1;
594+
}
595+
std::cmp::Ordering::Less => {
596+
target[write] = rv;
597+
j -= 1;
598+
}
599+
std::cmp::Ordering::Equal => {
600+
target[write] = lv;
601+
i -= 1;
602+
j -= 1;
603+
}
591604
}
592605
}
593-
result.extend_from_slice(&left_slice[i..]);
594-
result.extend_from_slice(&right[j..]);
595-
result
606+
607+
while i > 0 {
608+
write -= 1;
609+
target[write] = target[i - 1];
610+
i -= 1;
611+
}
612+
613+
while j > 0 {
614+
write -= 1;
615+
target[write] = other[j - 1];
616+
j -= 1;
617+
}
618+
619+
if write > 0 {
620+
let len = target.len();
621+
target.copy_within(write..len, 0);
622+
target.truncate(len - write);
623+
}
596624
}
597625

598626
fn small_intersection(lhs: &mut SmallBitmap, rhs: &mut SmallBitmap) {
@@ -673,28 +701,62 @@ fn small_difference(lhs: &[u64], rhs: &[u64]) -> SmallBitmap {
673701
result
674702
}
675703

676-
fn small_symmetric_difference(lhs: &[u64], rhs: &[u64]) -> SmallBitmap {
677-
let mut result = SmallBitmap::with_capacity(lhs.len() + rhs.len());
678-
let mut i = 0;
679-
let mut j = 0;
704+
fn small_symmetric_difference(target: &mut SmallBitmap, other: &[u64]) {
705+
if other.is_empty() {
706+
return;
707+
}
708+
if target.is_empty() {
709+
target.extend_from_slice(other);
710+
return;
711+
}
680712

681-
while i < lhs.len() && j < rhs.len() {
682-
let lv = lhs[i];
683-
let rv = rhs[j];
684-
if lv < rv {
685-
result.push(lv);
686-
i += 1;
687-
} else if rv < lv {
688-
result.push(rv);
689-
j += 1;
690-
} else {
691-
i += 1;
692-
j += 1;
713+
let lhs_len = target.len();
714+
let rhs_len = other.len();
715+
target.reserve(rhs_len);
716+
let mut write = lhs_len + rhs_len;
717+
target.resize(write, 0);
718+
719+
let mut i = lhs_len;
720+
let mut j = rhs_len;
721+
722+
while i > 0 && j > 0 {
723+
let lv = target[i - 1];
724+
let rv = other[j - 1];
725+
match lv.cmp(&rv) {
726+
std::cmp::Ordering::Greater => {
727+
write -= 1;
728+
target[write] = lv;
729+
i -= 1;
730+
}
731+
std::cmp::Ordering::Less => {
732+
write -= 1;
733+
target[write] = rv;
734+
j -= 1;
735+
}
736+
std::cmp::Ordering::Equal => {
737+
i -= 1;
738+
j -= 1;
739+
}
693740
}
694741
}
695-
result.extend_from_slice(&lhs[i..]);
696-
result.extend_from_slice(&rhs[j..]);
697-
result
742+
743+
while i > 0 {
744+
write -= 1;
745+
target[write] = target[i - 1];
746+
i -= 1;
747+
}
748+
749+
while j > 0 {
750+
write -= 1;
751+
target[write] = other[j - 1];
752+
j -= 1;
753+
}
754+
755+
if write > 0 {
756+
let len = target.len();
757+
target.copy_within(write..len, 0);
758+
target.truncate(len - write);
759+
}
698760
}
699761

700762
fn small_is_superset(lhs: &SmallBitmap, rhs: &SmallBitmap) -> bool {
@@ -759,10 +821,10 @@ mod tests {
759821

760822
#[test]
761823
fn small_union_merges_and_deduplicates() {
762-
let left: SmallBitmap = smallvec![1_u64, 3, 5];
824+
let mut left: SmallBitmap = smallvec![1_u64, 3, 5];
763825
let right = [0_u64, 3, 4, 7];
764-
let result = small_union(left, &right);
765-
assert_eq!(result.as_slice(), &[0, 1, 3, 4, 5, 7]);
826+
small_union(&mut left, &right);
827+
assert_eq!(left.as_slice(), &[0, 1, 3, 4, 5, 7]);
766828
}
767829

768830
#[test]
@@ -798,10 +860,10 @@ mod tests {
798860

799861
#[test]
800862
fn small_symmetric_difference_handles_overlap() {
801-
let lhs = [1_u64, 2, 4];
863+
let mut lhs: SmallBitmap = smallvec![1_u64, 2, 4];
802864
let rhs = [2_u64, 3, 5];
803-
let result = small_symmetric_difference(&lhs, &rhs);
804-
assert_eq!(result.as_slice(), &[1, 3, 4, 5]);
865+
small_symmetric_difference(&mut lhs, &rhs);
866+
assert_eq!(lhs.as_slice(), &[1, 3, 4, 5]);
805867
}
806868

807869
#[test]

0 commit comments

Comments
 (0)