Skip to content

Commit 72e1ca7

Browse files
bors[bot]saik0
andauthored
Merge #127
127: Add scalar optimizations from CRoaring / arXiv:1709.07821 section 3 r=Kerollmops a=saik0 ### Purpose This PR adds some optimizations from CRoaring as outlined in arXiv:1709.07821 section 3 ### Overview * All inserts and removes are now branchless (!in arXiv:1709.0782, in CRoaring) * Section 3.1 was already implemented, except for `BitmapIter`. This is covered in #125 * Implement Array-Bitset aggregates as outlined in section 3.2 * Also branchless 😎 * Tracks bitmap cardinality while performing bitmap-bitmap ops * This is a deviation from CRoaring, and will need to be benchmarked further before this Draft PR is ready * Curious to hear what you think about this `@lemire` * In order to track bitmap cardinality the len field had to moved into `Store::Bitmap` * This is unfortunately a cross cutting change * `Store` was quite large (LoC) and had many responsibilities. The largest change in this draft is decomposing `Store` such hat it's field variants are two new container types: each responsible for maintaining their invariants and implementing `ops` * `Bitmap8K` keeps track of it's cardinality * `SortedU16Vec` maintains its sorting * `Store` now only delegates to these containers * My hope is that this will be useful when implementing run containers. 🤞 * Unfortunately so much code was moved this PR is _HUGE_ ### Out of scope * Inline ASM for Array-Bitset aggregates * Section 4 (explicit SIMD). As noted by the paper authors: The compiler does a decent job of autovectorization, though not as good as hand-tuned ### Notes * I attempted to emulate the inline ASM Array-Bitset aggregates by using a mix of unsafe ptr arithmetic and x86-64 intrinsics, hoping to compile to the same instructions. I was unable to get it under 13 instructions per iteration (compared to the papers 5). While it was an improvement, I abandoned the effort in favor of waiting for the `asm!` macro to stabilize. rust-lang/rust#72016 Co-authored-by: saik0 <[email protected]> Co-authored-by: Joel Pedraza <[email protected]>
2 parents 5bd5fc5 + dbc246e commit 72e1ca7

File tree

9 files changed

+1395
-1072
lines changed

9 files changed

+1395
-1072
lines changed

src/bitmap/container.rs

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const ARRAY_LIMIT: u64 = 4096;
1111
#[derive(PartialEq, Clone)]
1212
pub struct Container {
1313
pub key: u16,
14-
pub len: u64,
1514
pub store: Store,
1615
}
1716

@@ -22,14 +21,17 @@ pub struct Iter<'a> {
2221

2322
impl Container {
2423
pub fn new(key: u16) -> Container {
25-
Container { key, len: 0, store: Store::Array(Vec::new()) }
24+
Container { key, store: Store::new() }
2625
}
2726
}
2827

2928
impl Container {
29+
pub fn len(&self) -> u64 {
30+
self.store.len()
31+
}
32+
3033
pub fn insert(&mut self, index: u16) -> bool {
3134
if self.store.insert(index) {
32-
self.len += 1;
3335
self.ensure_correct_store();
3436
true
3537
} else {
@@ -39,7 +41,6 @@ impl Container {
3941

4042
pub fn insert_range(&mut self, range: RangeInclusive<u16>) -> u64 {
4143
let inserted = self.store.insert_range(range);
42-
self.len += inserted;
4344
self.ensure_correct_store();
4445
inserted
4546
}
@@ -49,7 +50,6 @@ impl Container {
4950
/// Returns whether the `index` was effectively pushed.
5051
pub fn push(&mut self, index: u16) -> bool {
5152
if self.store.push(index) {
52-
self.len += 1;
5353
self.ensure_correct_store();
5454
true
5555
} else {
@@ -59,7 +59,6 @@ impl Container {
5959

6060
pub fn remove(&mut self, index: u16) -> bool {
6161
if self.store.remove(index) {
62-
self.len -= 1;
6362
self.ensure_correct_store();
6463
true
6564
} else {
@@ -69,7 +68,6 @@ impl Container {
6968

7069
pub fn remove_range(&mut self, range: RangeInclusive<u16>) -> u64 {
7170
let result = self.store.remove_range(range);
72-
self.len -= result;
7371
self.ensure_correct_store();
7472
result
7573
}
@@ -83,7 +81,7 @@ impl Container {
8381
}
8482

8583
pub fn is_subset(&self, other: &Self) -> bool {
86-
self.len <= other.len && self.store.is_subset(&other.store)
84+
self.len() <= other.len() && self.store.is_subset(&other.store)
8785
}
8886

8987
pub fn min(&self) -> Option<u16> {
@@ -95,14 +93,18 @@ impl Container {
9593
}
9694

9795
fn ensure_correct_store(&mut self) {
98-
let new_store = match (&self.store, self.len) {
99-
(store @ &Store::Bitmap(..), len) if len <= ARRAY_LIMIT => Some(store.to_array()),
100-
(store @ &Store::Array(..), len) if len > ARRAY_LIMIT => Some(store.to_bitmap()),
101-
_ => None,
96+
match &self.store {
97+
Store::Bitmap(ref bits) => {
98+
if bits.len() <= ARRAY_LIMIT {
99+
self.store = Store::Array(bits.to_array_store())
100+
}
101+
}
102+
Store::Array(ref vec) => {
103+
if vec.len() as u64 > ARRAY_LIMIT {
104+
self.store = Store::Bitmap(vec.to_bitmap_store())
105+
}
106+
}
102107
};
103-
if let Some(new_store) = new_store {
104-
self.store = new_store;
105-
}
106108
}
107109
}
108110

@@ -111,7 +113,7 @@ impl BitOr<&Container> for &Container {
111113

112114
fn bitor(self, rhs: &Container) -> Container {
113115
let store = BitOr::bitor(&self.store, &rhs.store);
114-
let mut container = Container { key: self.key, len: store.len(), store };
116+
let mut container = Container { key: self.key, store };
115117
container.ensure_correct_store();
116118
container
117119
}
@@ -120,15 +122,13 @@ impl BitOr<&Container> for &Container {
120122
impl BitOrAssign<Container> for Container {
121123
fn bitor_assign(&mut self, rhs: Container) {
122124
BitOrAssign::bitor_assign(&mut self.store, rhs.store);
123-
self.len = self.store.len();
124125
self.ensure_correct_store();
125126
}
126127
}
127128

128129
impl BitOrAssign<&Container> for Container {
129130
fn bitor_assign(&mut self, rhs: &Container) {
130131
BitOrAssign::bitor_assign(&mut self.store, &rhs.store);
131-
self.len = self.store.len();
132132
self.ensure_correct_store();
133133
}
134134
}
@@ -138,7 +138,7 @@ impl BitAnd<&Container> for &Container {
138138

139139
fn bitand(self, rhs: &Container) -> Container {
140140
let store = BitAnd::bitand(&self.store, &rhs.store);
141-
let mut container = Container { key: self.key, len: store.len(), store };
141+
let mut container = Container { key: self.key, store };
142142
container.ensure_correct_store();
143143
container
144144
}
@@ -147,15 +147,13 @@ impl BitAnd<&Container> for &Container {
147147
impl BitAndAssign<Container> for Container {
148148
fn bitand_assign(&mut self, rhs: Container) {
149149
BitAndAssign::bitand_assign(&mut self.store, rhs.store);
150-
self.len = self.store.len();
151150
self.ensure_correct_store();
152151
}
153152
}
154153

155154
impl BitAndAssign<&Container> for Container {
156155
fn bitand_assign(&mut self, rhs: &Container) {
157156
BitAndAssign::bitand_assign(&mut self.store, &rhs.store);
158-
self.len = self.store.len();
159157
self.ensure_correct_store();
160158
}
161159
}
@@ -165,7 +163,7 @@ impl Sub<&Container> for &Container {
165163

166164
fn sub(self, rhs: &Container) -> Container {
167165
let store = Sub::sub(&self.store, &rhs.store);
168-
let mut container = Container { key: self.key, len: store.len(), store };
166+
let mut container = Container { key: self.key, store };
169167
container.ensure_correct_store();
170168
container
171169
}
@@ -174,7 +172,6 @@ impl Sub<&Container> for &Container {
174172
impl SubAssign<&Container> for Container {
175173
fn sub_assign(&mut self, rhs: &Container) {
176174
SubAssign::sub_assign(&mut self.store, &rhs.store);
177-
self.len = self.store.len();
178175
self.ensure_correct_store();
179176
}
180177
}
@@ -184,7 +181,7 @@ impl BitXor<&Container> for &Container {
184181

185182
fn bitxor(self, rhs: &Container) -> Container {
186183
let store = BitXor::bitxor(&self.store, &rhs.store);
187-
let mut container = Container { key: self.key, len: store.len(), store };
184+
let mut container = Container { key: self.key, store };
188185
container.ensure_correct_store();
189186
container
190187
}
@@ -193,15 +190,13 @@ impl BitXor<&Container> for &Container {
193190
impl BitXorAssign<Container> for Container {
194191
fn bitxor_assign(&mut self, rhs: Container) {
195192
BitXorAssign::bitxor_assign(&mut self.store, rhs.store);
196-
self.len = self.store.len();
197193
self.ensure_correct_store();
198194
}
199195
}
200196

201197
impl BitXorAssign<&Container> for Container {
202198
fn bitxor_assign(&mut self, rhs: &Container) {
203199
BitXorAssign::bitxor_assign(&mut self.store, &rhs.store);
204-
self.len = self.store.len();
205200
self.ensure_correct_store();
206201
}
207202
}
@@ -236,6 +231,6 @@ impl<'a> Iterator for Iter<'a> {
236231

237232
impl fmt::Debug for Container {
238233
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
239-
format!("Container<{:?} @ {:?}>", self.len, self.key).fmt(formatter)
234+
format!("Container<{:?} @ {:?}>", self.len(), self.key).fmt(formatter)
240235
}
241236
}

src/bitmap/inherent.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl RoaringBitmap {
169169
match self.containers.binary_search_by_key(&key, |c| c.key) {
170170
Ok(loc) => {
171171
if self.containers[loc].remove(index) {
172-
if self.containers[loc].len == 0 {
172+
if self.containers[loc].len() == 0 {
173173
self.containers.remove(loc);
174174
}
175175
true
@@ -214,7 +214,7 @@ impl RoaringBitmap {
214214
let a = if key == start_container_key { start_index } else { 0 };
215215
let b = if key == end_container_key { end_index } else { u16::MAX };
216216
removed += self.containers[index].remove_range(a..=b);
217-
if self.containers[index].len == 0 {
217+
if self.containers[index].len() == 0 {
218218
self.containers.remove(index);
219219
continue;
220220
}
@@ -297,7 +297,7 @@ impl RoaringBitmap {
297297
/// assert_eq!(rb.len(), 2);
298298
/// ```
299299
pub fn len(&self) -> u64 {
300-
self.containers.iter().map(|container| container.len).sum()
300+
self.containers.iter().map(|container| container.len()).sum()
301301
}
302302

303303
/// Returns the minimum value in the set (if the set is non-empty).
@@ -447,7 +447,7 @@ mod tests {
447447
let inserted = b.insert_range(u16::MAX as u32 + 1..=u16::MAX as u32 + 1);
448448
assert_eq!(inserted, 1);
449449

450-
assert_eq!(b.containers[0].len, 1);
450+
assert_eq!(b.containers[0].len(), 1);
451451
assert_eq!(b.containers.len(), 1);
452452

453453
let removed = b.remove_range(u16::MAX as u32 + 1..=u16::MAX as u32 + 1);

src/bitmap/iter.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ pub struct IntoIter {
2323

2424
impl Iter<'_> {
2525
fn new(containers: &[Container]) -> Iter {
26-
let size_hint = containers.iter().map(|c| c.len).sum();
26+
let size_hint = containers.iter().map(|c| c.len()).sum();
2727
Iter { inner: containers.iter().flat_map(identity as _), size_hint }
2828
}
2929
}
3030

3131
impl IntoIter {
3232
fn new(containers: Vec<Container>) -> IntoIter {
33-
let size_hint = containers.iter().map(|c| c.len).sum();
33+
let size_hint = containers.iter().map(|c| c.len()).sum();
3434
IntoIter { inner: containers.into_iter().flat_map(identity as _), size_hint }
3535
}
3636
}

src/bitmap/ops.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ impl BitAnd<&RoaringBitmap> for &RoaringBitmap {
276276
for pair in Pairs::new(&self.containers, &rhs.containers) {
277277
if let (Some(lhs), Some(rhs)) = pair {
278278
let container = BitAnd::bitand(lhs, rhs);
279-
if container.len != 0 {
279+
if container.len() != 0 {
280280
containers.push(container);
281281
}
282282
}
@@ -301,7 +301,7 @@ impl BitAndAssign<RoaringBitmap> for RoaringBitmap {
301301
let rhs_cont = &mut rhs.containers[loc];
302302
let rhs_cont = mem::replace(rhs_cont, Container::new(rhs_cont.key));
303303
BitAndAssign::bitand_assign(cont, rhs_cont);
304-
cont.len != 0
304+
cont.len() != 0
305305
}
306306
Err(_) => false,
307307
}
@@ -317,7 +317,7 @@ impl BitAndAssign<&RoaringBitmap> for RoaringBitmap {
317317
match rhs.containers.binary_search_by_key(&key, |c| c.key) {
318318
Ok(loc) => {
319319
BitAndAssign::bitand_assign(cont, &rhs.containers[loc]);
320-
cont.len != 0
320+
cont.len() != 0
321321
}
322322
Err(_) => false,
323323
}
@@ -367,7 +367,7 @@ impl Sub<&RoaringBitmap> for &RoaringBitmap {
367367
(None, Some(_)) => (),
368368
(Some(lhs), Some(rhs)) => {
369369
let container = Sub::sub(lhs, rhs);
370-
if container.len != 0 {
370+
if container.len() != 0 {
371371
containers.push(container);
372372
}
373373
}
@@ -393,7 +393,7 @@ impl SubAssign<&RoaringBitmap> for RoaringBitmap {
393393
match rhs.containers.binary_search_by_key(&cont.key, |c| c.key) {
394394
Ok(loc) => {
395395
SubAssign::sub_assign(cont, &rhs.containers[loc]);
396-
cont.len != 0
396+
cont.len() != 0
397397
}
398398
Err(_) => true,
399399
}
@@ -443,7 +443,7 @@ impl BitXor<&RoaringBitmap> for &RoaringBitmap {
443443
(None, Some(rhs)) => containers.push(rhs.clone()),
444444
(Some(lhs), Some(rhs)) => {
445445
let container = BitXor::bitxor(lhs, rhs);
446-
if container.len != 0 {
446+
if container.len() != 0 {
447447
containers.push(container);
448448
}
449449
}
@@ -462,7 +462,7 @@ impl BitXorAssign<RoaringBitmap> for RoaringBitmap {
462462
match pair {
463463
(Some(mut lhs), Some(rhs)) => {
464464
BitXorAssign::bitxor_assign(&mut lhs, rhs);
465-
if lhs.len != 0 {
465+
if lhs.len() != 0 {
466466
self.containers.push(lhs);
467467
}
468468
}
@@ -481,7 +481,7 @@ impl BitXorAssign<&RoaringBitmap> for RoaringBitmap {
481481
match pair {
482482
(Some(mut lhs), Some(rhs)) => {
483483
BitXorAssign::bitxor_assign(&mut lhs, rhs);
484-
if lhs.len != 0 {
484+
if lhs.len() != 0 {
485485
self.containers.push(lhs);
486486
}
487487
}

src/bitmap/serialization.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
33
use std::io;
44

55
use super::container::Container;
6-
use super::store::Store;
6+
use crate::bitmap::store::{ArrayStore, BitmapStore, Store};
77
use crate::RoaringBitmap;
88

99
const SERIAL_COOKIE_NO_RUNCONTAINER: u32 = 12346;
@@ -32,7 +32,7 @@ impl RoaringBitmap {
3232
.containers
3333
.iter()
3434
.map(|container| match container.store {
35-
Store::Array(ref values) => 8 + values.len() * 2,
35+
Store::Array(ref values) => 8 + values.len() as usize * 2,
3636
Store::Bitmap(..) => 8 + 8 * 1024,
3737
})
3838
.sum();
@@ -64,7 +64,7 @@ impl RoaringBitmap {
6464

6565
for container in &self.containers {
6666
writer.write_u16::<LittleEndian>(container.key)?;
67-
writer.write_u16::<LittleEndian>((container.len - 1) as u16)?;
67+
writer.write_u16::<LittleEndian>((container.len() - 1) as u16)?;
6868
}
6969

7070
let mut offset = 8 + 8 * self.containers.len() as u32;
@@ -83,12 +83,12 @@ impl RoaringBitmap {
8383
for container in &self.containers {
8484
match container.store {
8585
Store::Array(ref values) => {
86-
for &value in values {
86+
for &value in values.iter() {
8787
writer.write_u16::<LittleEndian>(value)?;
8888
}
8989
}
90-
Store::Bitmap(ref values) => {
91-
for &value in values.iter() {
90+
Store::Bitmap(ref bits) => {
91+
for &value in bits.as_array() {
9292
writer.write_u64::<LittleEndian>(value)?;
9393
}
9494
}
@@ -152,15 +152,15 @@ impl RoaringBitmap {
152152
let mut values = vec![0; len as usize];
153153
reader.read_exact(cast_slice_mut(&mut values))?;
154154
values.iter_mut().for_each(|n| *n = u16::from_le(*n));
155-
Store::Array(values)
155+
Store::Array(ArrayStore::from_vec_unchecked(values))
156156
} else {
157157
let mut values = Box::new([0; 1024]);
158158
reader.read_exact(cast_slice_mut(&mut values[..]))?;
159159
values.iter_mut().for_each(|n| *n = u64::from_le(*n));
160-
Store::Bitmap(values)
160+
Store::Bitmap(BitmapStore::from_unchecked(len, values))
161161
};
162162

163-
containers.push(Container { key, len, store });
163+
containers.push(Container { key, store });
164164
}
165165

166166
Ok(RoaringBitmap { containers })

0 commit comments

Comments
 (0)