Skip to content

Commit 4ba28b9

Browse files
committed
perf: add fast-path bitmap_has_all that avoids full deserialization
For large hybrid bitmaps, bitmap_has_all now checks superset relationships directly on serialized buffers using container-level comparison instead of deserializing both bitmaps. This reduces bitmap_has_all_large_large from ~39µs to ~11µs (3.5x), and disjoint cases from ~20µs to ~932ns (22x).
1 parent 4bfba50 commit 4ba28b9

4 files changed

Lines changed: 476 additions & 12 deletions

File tree

src/common/io/src/bitmap.rs

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,82 @@ pub fn bitmap_stats(buf: &[u8]) -> Result<BitmapStats> {
838838
}
839839
}
840840

841+
pub fn bitmap_has_all(lhs: &[u8], rhs: &[u8]) -> Result<bool> {
842+
if rhs.is_empty() {
843+
return Ok(true);
844+
}
845+
if lhs.is_empty() {
846+
return Ok(false);
847+
}
848+
validate_serialized_bitmap_header_and_length(lhs)?;
849+
validate_serialized_bitmap_header_and_length(rhs)?;
850+
// Both HybridLarge: use stats for early rejection, then check directly on serialized buffers.
851+
if is_hybrid_large(lhs) && is_hybrid_large(rhs) {
852+
let lhs_stats = reader::bitmap_stats(&lhs[HYBRID_HEADER_LEN..])?;
853+
let rhs_stats = reader::bitmap_stats(&rhs[HYBRID_HEADER_LEN..])?;
854+
if let (Some(lhs_min), Some(rhs_min)) = (lhs_stats.min, rhs_stats.min)
855+
&& lhs_min > rhs_min
856+
{
857+
return Ok(false);
858+
}
859+
if let (Some(rhs_max), Some(lhs_max)) = (rhs_stats.max, lhs_stats.max)
860+
&& rhs_max > lhs_max
861+
{
862+
return Ok(false);
863+
}
864+
if lhs_stats.len < rhs_stats.len {
865+
return Ok(false);
866+
}
867+
Ok(reader::bitmap_has_all(
868+
&lhs[HYBRID_HEADER_LEN..],
869+
&rhs[HYBRID_HEADER_LEN..],
870+
)?)
871+
} else if is_hybrid_large(lhs) || is_hybrid_large(rhs) {
872+
// Mixed path: one side is HybridLarge, the other is HybridSmall or Legacy.
873+
// Use stats for early rejection; if rhs is HybridSmall, iterate and probe lhs;
874+
// otherwise fall back to full deserialization.
875+
let lhs_stats = bitmap_stats(lhs)?;
876+
let rhs_stats = bitmap_stats(rhs)?;
877+
if let (Some(lhs_min), Some(rhs_min)) = (lhs_stats.min, rhs_stats.min)
878+
&& lhs_min > rhs_min
879+
{
880+
return Ok(false);
881+
}
882+
if let (Some(rhs_max), Some(lhs_max)) = (rhs_stats.max, lhs_stats.max)
883+
&& rhs_max > lhs_max
884+
{
885+
return Ok(false);
886+
}
887+
// lhs.len < rhs.len -> lhs cannot contain all of rhs
888+
if lhs_stats.len < rhs_stats.len {
889+
return Ok(false);
890+
}
891+
// rhs is HybridSmall: iterate its (≤31) values and probe lhs via bitmap_contains.
892+
// Each bitmap_contains call scans prefix buckets from the start, causing redundant
893+
// work across iterations. This could be improved with a hint/cursor mechanism that
894+
// tracks the last scanned position, but since HybridSmall holds at most 31 values
895+
// the overhead is bounded (~μs-level). Revisit if this path becomes a bottleneck.
896+
if is_hybrid_large(lhs) && is_hybrid(rhs) && !is_hybrid_large(rhs) {
897+
let rhs_bm = deserialize_bitmap(rhs)?;
898+
for value in rhs_bm.iter() {
899+
if !bitmap_contains(lhs, value)? {
900+
return Ok(false);
901+
}
902+
}
903+
return Ok(true);
904+
}
905+
// Fallback to deserialization and is_superset
906+
let lhs_bm = deserialize_bitmap(lhs)?;
907+
let rhs_bm = deserialize_bitmap(rhs)?;
908+
Ok(lhs_bm.is_superset(&rhs_bm))
909+
} else {
910+
// Both else: deserialize both
911+
let lhs_bm = deserialize_bitmap(lhs)?;
912+
let rhs_bm = deserialize_bitmap(rhs)?;
913+
Ok(lhs_bm.is_superset(&rhs_bm))
914+
}
915+
}
916+
841917
pub fn bitmap_has_any(lhs: &[u8], rhs: &[u8]) -> Result<bool> {
842918
if lhs.is_empty() || rhs.is_empty() {
843919
return Ok(false);
@@ -1936,4 +2012,204 @@ mod tests {
19362012
assert_eq!(stats.min, Some(42));
19372013
assert_eq!(stats.max, Some(100));
19382014
}
2015+
2016+
fn ground_truth_has_all(lhs: &[u8], rhs: &[u8]) -> bool {
2017+
let lhs_bm = deserialize_bitmap(lhs).unwrap();
2018+
let rhs_bm = deserialize_bitmap(rhs).unwrap();
2019+
lhs_bm.is_superset(&rhs_bm)
2020+
}
2021+
2022+
#[test]
2023+
fn test_bitmap_has_all() {
2024+
// Both HybridLarge, rhs is subset: 0..50000 contains 0..10000
2025+
let lhs = HybridBitmap::from_iter(0u64..50000);
2026+
let rhs = HybridBitmap::from_iter(0u64..10000);
2027+
let mut lhs_buf = Vec::new();
2028+
let mut rhs_buf = Vec::new();
2029+
lhs.serialize_into(&mut lhs_buf).unwrap();
2030+
rhs.serialize_into(&mut rhs_buf).unwrap();
2031+
assert_eq!(
2032+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2033+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2034+
);
2035+
assert!(bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2036+
2037+
// Both HybridLarge, rhs is not subset: 0..50000 vs 40000..90000
2038+
// rhs has values 50000-89999 that are not in lhs
2039+
let lhs = HybridBitmap::from_iter(0u64..50000);
2040+
let rhs = HybridBitmap::from_iter(40000u64..90000);
2041+
let mut lhs_buf = Vec::new();
2042+
let mut rhs_buf = Vec::new();
2043+
lhs.serialize_into(&mut lhs_buf).unwrap();
2044+
rhs.serialize_into(&mut rhs_buf).unwrap();
2045+
assert_eq!(
2046+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2047+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2048+
);
2049+
assert!(!bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2050+
2051+
// Both HybridLarge, disjoint: 0..50000 vs 100000..150000
2052+
let lhs = HybridBitmap::from_iter(0u64..50000);
2053+
let rhs = HybridBitmap::from_iter(100000u64..150000);
2054+
let mut lhs_buf = Vec::new();
2055+
let mut rhs_buf = Vec::new();
2056+
lhs.serialize_into(&mut lhs_buf).unwrap();
2057+
rhs.serialize_into(&mut rhs_buf).unwrap();
2058+
assert_eq!(
2059+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2060+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2061+
);
2062+
assert!(!bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2063+
2064+
// Both HybridSmall, rhs is subset: 0..31 contains 5..15
2065+
let lhs = HybridBitmap::from_iter(0u64..31);
2066+
let rhs = HybridBitmap::from_iter(5u64..15);
2067+
let mut lhs_buf = Vec::new();
2068+
let mut rhs_buf = Vec::new();
2069+
lhs.serialize_into(&mut lhs_buf).unwrap();
2070+
rhs.serialize_into(&mut rhs_buf).unwrap();
2071+
assert_eq!(
2072+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2073+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2074+
);
2075+
assert!(bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2076+
2077+
// Both HybridSmall, rhs is not subset: 0..15 vs 10..25
2078+
// rhs has values 15-24 that are not in lhs
2079+
let lhs = HybridBitmap::from_iter(0u64..15);
2080+
let rhs = HybridBitmap::from_iter(10u64..25);
2081+
let mut lhs_buf = Vec::new();
2082+
let mut rhs_buf = Vec::new();
2083+
lhs.serialize_into(&mut lhs_buf).unwrap();
2084+
rhs.serialize_into(&mut rhs_buf).unwrap();
2085+
assert_eq!(
2086+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2087+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2088+
);
2089+
assert!(!bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2090+
2091+
// Mixed (HybridLarge lhs, HybridSmall rhs) with superset: 0..50000 contains 5..15
2092+
let lhs = HybridBitmap::from_iter(0u64..50000);
2093+
let rhs = HybridBitmap::from_iter(5u64..15);
2094+
let mut lhs_buf = Vec::new();
2095+
let mut rhs_buf = Vec::new();
2096+
lhs.serialize_into(&mut lhs_buf).unwrap();
2097+
rhs.serialize_into(&mut rhs_buf).unwrap();
2098+
assert_eq!(
2099+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2100+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2101+
);
2102+
assert!(bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2103+
2104+
// Mixed (HybridLarge lhs, HybridSmall rhs not subset): 0..50000 vs 49990..50010
2105+
// rhs has values 50000-50009 that are not in lhs
2106+
let lhs = HybridBitmap::from_iter(0u64..50000);
2107+
let rhs = HybridBitmap::from_iter(49990u64..50010);
2108+
let mut lhs_buf = Vec::new();
2109+
let mut rhs_buf = Vec::new();
2110+
lhs.serialize_into(&mut lhs_buf).unwrap();
2111+
rhs.serialize_into(&mut rhs_buf).unwrap();
2112+
assert_eq!(
2113+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2114+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2115+
);
2116+
assert!(!bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2117+
2118+
// Mixed (HybridSmall lhs, HybridLarge rhs): already rejected by len fast path
2119+
let lhs = HybridBitmap::from_iter(0u64..31);
2120+
let rhs = HybridBitmap::from_iter(0u64..50000);
2121+
let mut lhs_buf = Vec::new();
2122+
let mut rhs_buf = Vec::new();
2123+
lhs.serialize_into(&mut lhs_buf).unwrap();
2124+
rhs.serialize_into(&mut rhs_buf).unwrap();
2125+
assert_eq!(
2126+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2127+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2128+
);
2129+
assert!(!bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2130+
2131+
// Empty rhs: always true
2132+
let lhs = HybridBitmap::from_iter(0u64..50000);
2133+
let mut lhs_buf = Vec::new();
2134+
lhs.serialize_into(&mut lhs_buf).unwrap();
2135+
assert!(bitmap_has_all(&lhs_buf, &[]).unwrap());
2136+
2137+
// Empty lhs with non-empty rhs: always false
2138+
let rhs = HybridBitmap::from_iter(0u64..50000);
2139+
let mut rhs_buf = Vec::new();
2140+
rhs.serialize_into(&mut rhs_buf).unwrap();
2141+
assert!(!bitmap_has_all(&[], &rhs_buf).unwrap());
2142+
2143+
// Both empty: true
2144+
assert!(bitmap_has_all(&[], &[]).unwrap());
2145+
2146+
// Mixed (Legacy lhs, HybridLarge rhs)
2147+
let mut lhs_tree = RoaringTreemap::new();
2148+
for v in 0u64..50000 {
2149+
lhs_tree.insert(v);
2150+
}
2151+
let rhs = HybridBitmap::from_iter(0u64..10000);
2152+
let mut lhs_buf = Vec::new();
2153+
let mut rhs_buf = Vec::new();
2154+
lhs_tree.serialize_into(&mut lhs_buf).unwrap();
2155+
rhs.serialize_into(&mut rhs_buf).unwrap();
2156+
assert_eq!(
2157+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2158+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2159+
);
2160+
2161+
// Mixed (HybridLarge lhs, Legacy rhs)
2162+
let lhs = HybridBitmap::from_iter(0u64..10000);
2163+
let mut rhs_tree = RoaringTreemap::new();
2164+
for v in 0u64..50000 {
2165+
rhs_tree.insert(v);
2166+
}
2167+
2168+
let mut lhs_buf = Vec::new();
2169+
let mut rhs_buf = Vec::new();
2170+
lhs.serialize_into(&mut lhs_buf).unwrap();
2171+
rhs_tree.serialize_into(&mut rhs_buf).unwrap();
2172+
assert_eq!(
2173+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2174+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2175+
);
2176+
2177+
// Both Legacy
2178+
let mut lhs_tree = RoaringTreemap::new();
2179+
for v in 0u64..100 {
2180+
lhs_tree.insert(v);
2181+
}
2182+
let mut rhs_tree = RoaringTreemap::new();
2183+
for v in 50u64..80 {
2184+
rhs_tree.insert(v);
2185+
}
2186+
let mut lhs_buf = Vec::new();
2187+
let mut rhs_buf = Vec::new();
2188+
lhs_tree.serialize_into(&mut lhs_buf).unwrap();
2189+
rhs_tree.serialize_into(&mut rhs_buf).unwrap();
2190+
assert_eq!(
2191+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2192+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2193+
);
2194+
assert!(bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2195+
2196+
// Both Legacy, rhs not subset
2197+
let mut lhs_tree = RoaringTreemap::new();
2198+
for v in 0u64..100 {
2199+
lhs_tree.insert(v);
2200+
}
2201+
let mut rhs_tree = RoaringTreemap::new();
2202+
for v in 50u64..150 {
2203+
rhs_tree.insert(v);
2204+
}
2205+
let mut lhs_buf = Vec::new();
2206+
let mut rhs_buf = Vec::new();
2207+
lhs_tree.serialize_into(&mut lhs_buf).unwrap();
2208+
rhs_tree.serialize_into(&mut rhs_buf).unwrap();
2209+
assert_eq!(
2210+
bitmap_has_all(&lhs_buf, &rhs_buf).unwrap(),
2211+
ground_truth_has_all(&lhs_buf, &rhs_buf)
2212+
);
2213+
assert!(!bitmap_has_all(&lhs_buf, &rhs_buf).unwrap());
2214+
}
19392215
}

0 commit comments

Comments
 (0)