Skip to content

Commit 0731bab

Browse files
committed
perf: add fast-path bitmap_has_any that avoids full deserialization
For large hybrid bitmaps, bitmap_has_any now checks intersection directly on the serialized buffers using container-level binary search instead of deserializing both bitmaps and computing full intersection. This reduces bitmap_has_any_large_large from ~26µs to ~2.5µs (10x), and disjoint cases from ~20µs to ~963ns (21x).
1 parent 1762aaa commit 0731bab

4 files changed

Lines changed: 525 additions & 12 deletions

File tree

src/common/io/src/bitmap.rs

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ use roaring::RoaringTreemap;
2828
use roaring::treemap::Iter;
2929
use smallvec::SmallVec;
3030

31+
pub use crate::bitmap::reader::BitmapStats;
32+
3133
mod reader;
3234

3335
// https://github.com/ClickHouse/ClickHouse/blob/516a6ed6f8bd8c5f6eed3a10e9037580b2fb6152/src/AggregateFunctions/AggregateFunctionGroupBitmapData.h#L914
@@ -776,6 +778,99 @@ pub fn bitmap_max(buf: &[u8]) -> Result<Option<u64>> {
776778
}
777779
}
778780

781+
pub fn bitmap_stats(buf: &[u8]) -> Result<BitmapStats> {
782+
if buf.is_empty() {
783+
return Ok(BitmapStats {
784+
len: 0,
785+
min: None,
786+
max: None,
787+
});
788+
}
789+
if is_hybrid_large(buf) {
790+
Ok(reader::bitmap_stats(&buf[HYBRID_HEADER_LEN..])?)
791+
} else {
792+
let bm = deserialize_bitmap(buf)?;
793+
Ok(BitmapStats {
794+
len: bm.len() as u64,
795+
min: bm.min(),
796+
max: bm.max(),
797+
})
798+
}
799+
}
800+
801+
pub fn bitmap_has_any(lhs: &[u8], rhs: &[u8]) -> Result<bool> {
802+
if lhs.is_empty() || rhs.is_empty() {
803+
return Ok(false);
804+
}
805+
806+
// Stats fast path: range rejection
807+
// If lhs.max < rhs.min OR rhs.max < lhs.min, no overlap possible
808+
if is_hybrid_large(lhs) && is_hybrid_large(rhs) {
809+
let lhs_stats = reader::bitmap_stats(&lhs[HYBRID_HEADER_LEN..])?;
810+
let rhs_stats = reader::bitmap_stats(&rhs[HYBRID_HEADER_LEN..])?;
811+
if let (Some(lhs_min), Some(rhs_max)) = (lhs_stats.min, rhs_stats.max)
812+
&& lhs_min > rhs_max
813+
{
814+
return Ok(false);
815+
}
816+
if let (Some(rhs_min), Some(lhs_max)) = (rhs_stats.min, lhs_stats.max)
817+
&& rhs_min > lhs_max
818+
{
819+
return Ok(false);
820+
}
821+
Ok(reader::bitmap_has_any(
822+
&lhs[HYBRID_HEADER_LEN..],
823+
&rhs[HYBRID_HEADER_LEN..],
824+
)?)
825+
} else if is_hybrid_large(lhs) || is_hybrid_large(rhs) {
826+
// Normalize: large is the HybridLarge side, other is the non-HybridLarge side
827+
let (large, other) = if is_hybrid_large(lhs) {
828+
(lhs, rhs)
829+
} else {
830+
(rhs, lhs)
831+
};
832+
833+
// Stats fast path: range rejection
834+
let large_stats = reader::bitmap_stats(&large[HYBRID_HEADER_LEN..])?;
835+
let other_stats = bitmap_stats(other)?;
836+
if let (Some(large_min), Some(other_max)) = (large_stats.min, other_stats.max)
837+
&& large_min > other_max
838+
{
839+
return Ok(false);
840+
}
841+
if let (Some(other_min), Some(large_max)) = (other_stats.min, large_stats.max)
842+
&& other_min > large_max
843+
{
844+
return Ok(false);
845+
}
846+
847+
// other is HybridSmall: iterate its (≤31) values through bitmap_contains on large.
848+
// Each bitmap_contains call scans prefix buckets from the start, causing redundant
849+
// work across iterations. This could be improved with a hint/cursor mechanism that
850+
// tracks the last scanned position, but since HybridSmall holds at most 31 values
851+
// the overhead is bounded (~μs-level). Revisit if this path becomes a bottleneck.
852+
if is_hybrid(other) && !is_hybrid_large(other) {
853+
let other_bm = deserialize_bitmap(other)?;
854+
for value in other_bm.iter() {
855+
if bitmap_contains(large, value)? {
856+
return Ok(true);
857+
}
858+
}
859+
return Ok(false);
860+
}
861+
862+
// other is Legacy (potentially large): fallback to full deserialization
863+
let lhs_bm = deserialize_bitmap(lhs)?;
864+
let rhs_bm = deserialize_bitmap(rhs)?;
865+
Ok(lhs_bm.intersection_len(&rhs_bm) != 0)
866+
} else {
867+
// Both else: deserialize both
868+
let lhs_bm = deserialize_bitmap(lhs)?;
869+
let rhs_bm = deserialize_bitmap(rhs)?;
870+
Ok(lhs_bm.intersection_len(&rhs_bm) != 0)
871+
}
872+
}
873+
779874
fn parse_bitmap_rhs(buf: &[u8]) -> Result<BitmapRhsView<'_>> {
780875
if buf.is_empty() {
781876
return Ok(BitmapRhsView::Empty);
@@ -1607,4 +1702,195 @@ mod tests {
16071702
// Empty buffer
16081703
assert_eq!(bitmap_max(&[]).unwrap(), None);
16091704
}
1705+
1706+
fn ground_truth_has_any(lhs: &[u8], rhs: &[u8]) -> bool {
1707+
let lhs_bm = deserialize_bitmap(lhs).unwrap();
1708+
let rhs_bm = deserialize_bitmap(rhs).unwrap();
1709+
lhs_bm.intersection_len(&rhs_bm) != 0
1710+
}
1711+
1712+
#[test]
1713+
fn test_bitmap_has_any() {
1714+
// Both HybridLarge with overlap: 0..50000 and 40000..90000 -> overlap at 40000-49999
1715+
let lhs = HybridBitmap::from_iter(0u64..50000);
1716+
let rhs = HybridBitmap::from_iter(40000u64..90000);
1717+
let mut lhs_buf = Vec::new();
1718+
let mut rhs_buf = Vec::new();
1719+
lhs.serialize_into(&mut lhs_buf).unwrap();
1720+
rhs.serialize_into(&mut rhs_buf).unwrap();
1721+
assert_eq!(
1722+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1723+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1724+
);
1725+
assert!(bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1726+
1727+
// Both HybridLarge disjoint: 0..50000 and 100000..150000
1728+
let lhs = HybridBitmap::from_iter(0u64..50000);
1729+
let rhs = HybridBitmap::from_iter(100000u64..150000);
1730+
let mut lhs_buf = Vec::new();
1731+
let mut rhs_buf = Vec::new();
1732+
lhs.serialize_into(&mut lhs_buf).unwrap();
1733+
rhs.serialize_into(&mut rhs_buf).unwrap();
1734+
assert_eq!(
1735+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1736+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1737+
);
1738+
assert!(!bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1739+
1740+
// Both HybridSmall with overlap: 0..31 and 20..51 -> overlap at 20-30
1741+
let lhs = HybridBitmap::from_iter(0u64..31);
1742+
let rhs = HybridBitmap::from_iter(20u64..51);
1743+
let mut lhs_buf = Vec::new();
1744+
let mut rhs_buf = Vec::new();
1745+
lhs.serialize_into(&mut lhs_buf).unwrap();
1746+
rhs.serialize_into(&mut rhs_buf).unwrap();
1747+
assert_eq!(
1748+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1749+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1750+
);
1751+
assert!(bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1752+
1753+
// Both HybridSmall disjoint: 0..15 and 20..35
1754+
let lhs = HybridBitmap::from_iter(0u64..15);
1755+
let rhs = HybridBitmap::from_iter(20u64..35);
1756+
let mut lhs_buf = Vec::new();
1757+
let mut rhs_buf = Vec::new();
1758+
lhs.serialize_into(&mut lhs_buf).unwrap();
1759+
rhs.serialize_into(&mut rhs_buf).unwrap();
1760+
assert_eq!(
1761+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1762+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1763+
);
1764+
assert!(!bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1765+
1766+
// Mixed (HybridLarge lhs, HybridSmall rhs) with overlap: 0..50000 and 10..41
1767+
let lhs = HybridBitmap::from_iter(0u64..50000);
1768+
let rhs = HybridBitmap::from_iter(10u64..41);
1769+
let mut lhs_buf = Vec::new();
1770+
let mut rhs_buf = Vec::new();
1771+
lhs.serialize_into(&mut lhs_buf).unwrap();
1772+
rhs.serialize_into(&mut rhs_buf).unwrap();
1773+
assert_eq!(
1774+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1775+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1776+
);
1777+
assert!(bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1778+
1779+
// Mixed (HybridSmall lhs, HybridLarge rhs) with overlap: 10..41 and 0..50000
1780+
let lhs = HybridBitmap::from_iter(10u64..41);
1781+
let rhs = HybridBitmap::from_iter(0u64..50000);
1782+
let mut lhs_buf = Vec::new();
1783+
let mut rhs_buf = Vec::new();
1784+
lhs.serialize_into(&mut lhs_buf).unwrap();
1785+
rhs.serialize_into(&mut rhs_buf).unwrap();
1786+
assert_eq!(
1787+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1788+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1789+
);
1790+
assert!(bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1791+
1792+
// Empty lhs
1793+
let rhs = HybridBitmap::from_iter(0u64..50000);
1794+
let mut rhs_buf = Vec::new();
1795+
rhs.serialize_into(&mut rhs_buf).unwrap();
1796+
assert!(!bitmap_has_any(&[], &rhs_buf).unwrap());
1797+
1798+
// Empty rhs
1799+
let lhs = HybridBitmap::from_iter(0u64..50000);
1800+
let mut lhs_buf = Vec::new();
1801+
lhs.serialize_into(&mut lhs_buf).unwrap();
1802+
assert!(!bitmap_has_any(&lhs_buf, &[]).unwrap());
1803+
1804+
// Both empty
1805+
assert!(!bitmap_has_any(&[], &[]).unwrap());
1806+
1807+
// Mixed (Legacy lhs, HybridLarge rhs) with overlap
1808+
let mut lhs_tree = RoaringTreemap::new();
1809+
for v in [0u64, 2500, 40000] {
1810+
lhs_tree.insert(v);
1811+
}
1812+
let rhs = HybridBitmap::from_iter(0u64..50000);
1813+
let mut lhs_buf = Vec::new();
1814+
let mut rhs_buf = Vec::new();
1815+
lhs_tree.serialize_into(&mut lhs_buf).unwrap();
1816+
rhs.serialize_into(&mut rhs_buf).unwrap();
1817+
assert_eq!(
1818+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1819+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1820+
);
1821+
assert!(bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1822+
1823+
// Mixed (HybridLarge lhs, Legacy rhs) with overlap
1824+
let lhs = HybridBitmap::from_iter(0u64..50000);
1825+
let mut rhs_tree = RoaringTreemap::new();
1826+
for v in [0u64, 2500, 40000] {
1827+
rhs_tree.insert(v);
1828+
}
1829+
let mut lhs_buf = Vec::new();
1830+
let mut rhs_buf = Vec::new();
1831+
lhs.serialize_into(&mut lhs_buf).unwrap();
1832+
rhs_tree.serialize_into(&mut rhs_buf).unwrap();
1833+
assert_eq!(
1834+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1835+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1836+
);
1837+
assert!(bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1838+
1839+
// Both Legacy with overlap
1840+
let mut lhs_tree = RoaringTreemap::new();
1841+
for v in 0u64..100 {
1842+
lhs_tree.insert(v);
1843+
}
1844+
let mut rhs_tree = RoaringTreemap::new();
1845+
for v in 50u64..150 {
1846+
rhs_tree.insert(v);
1847+
}
1848+
let mut lhs_buf = Vec::new();
1849+
let mut rhs_buf = Vec::new();
1850+
lhs_tree.serialize_into(&mut lhs_buf).unwrap();
1851+
rhs_tree.serialize_into(&mut rhs_buf).unwrap();
1852+
assert_eq!(
1853+
bitmap_has_any(&lhs_buf, &rhs_buf).unwrap(),
1854+
ground_truth_has_any(&lhs_buf, &rhs_buf)
1855+
);
1856+
assert!(bitmap_has_any(&lhs_buf, &rhs_buf).unwrap());
1857+
}
1858+
1859+
#[test]
1860+
fn test_bitmap_stats() {
1861+
// HybridLarge stats
1862+
let large = HybridBitmap::from_iter(0u64..50000);
1863+
let mut buf = Vec::new();
1864+
large.serialize_into(&mut buf).unwrap();
1865+
let stats = bitmap_stats(&buf).unwrap();
1866+
assert_eq!(stats.len, 50000);
1867+
assert_eq!(stats.min, Some(0));
1868+
assert_eq!(stats.max, Some(49999));
1869+
1870+
// HybridSmall stats
1871+
let small = HybridBitmap::from_iter(100u64..131);
1872+
let mut buf = Vec::new();
1873+
small.serialize_into(&mut buf).unwrap();
1874+
let stats = bitmap_stats(&buf).unwrap();
1875+
assert_eq!(stats.len, 31);
1876+
assert_eq!(stats.min, Some(100));
1877+
assert_eq!(stats.max, Some(130));
1878+
1879+
// Empty stats
1880+
let stats = bitmap_stats(&[]).unwrap();
1881+
assert_eq!(stats.len, 0);
1882+
assert_eq!(stats.min, None);
1883+
assert_eq!(stats.max, None);
1884+
1885+
// Legacy stats
1886+
let mut tree = RoaringTreemap::new();
1887+
tree.insert(42);
1888+
tree.insert(100);
1889+
let mut buf = Vec::new();
1890+
tree.serialize_into(&mut buf).unwrap();
1891+
let stats = bitmap_stats(&buf).unwrap();
1892+
assert_eq!(stats.len, 2);
1893+
assert_eq!(stats.min, Some(42));
1894+
assert_eq!(stats.max, Some(100));
1895+
}
16101896
}

0 commit comments

Comments
 (0)